From: Francesco Montorsi Date: Wed, 14 Jan 2009 13:26:37 +0000 (+0000) Subject: patch from Utensil Candel which refactors and documents the AutoCaptureMechanism... X-Git-Url: https://git.saurik.com/wxWidgets.git/commitdiff_plain/17ad109b8b14bc11b347530836d83c4e3df44050 patch from Utensil Candel which refactors and documents the AutoCaptureMechanism class git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@58091 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- diff --git a/utils/screenshotgen/src/autocapture.cpp b/utils/screenshotgen/src/autocapture.cpp index 62ae1b88dc..3a4abe2e3e 100644 --- a/utils/screenshotgen/src/autocapture.cpp +++ b/utils/screenshotgen/src/autocapture.cpp @@ -31,6 +31,9 @@ // AutoCaptureMechanism // ---------------------------------------------------------------------------- +/* static */ +wxString AutoCaptureMechanism::default_dir = _T("screenshots"); + /* static */ void AutoCaptureMechanism::Delay(int seconds) { @@ -73,6 +76,8 @@ wxBitmap AutoCaptureMechanism::Capture(int x, int y, int width, int height, int #else // Under other paltforms, take a real screenshot + wxUnusedVar(delay); + // Create a DC for the whole screen area wxScreenDC dcScreen; @@ -114,6 +119,23 @@ wxBitmap AutoCaptureMechanism::Capture(wxRect rect, int delay) return Capture(origin.x, origin.y, rect.GetWidth(), rect.GetHeight(), delay); } +/* static */ +void AutoCaptureMechanism::Save(wxBitmap screenshot, wxString fileName) +{ + // make sure default_dir exists + if (!wxDirExists(default_dir)) + wxMkdir(default_dir); + + wxFileName fullFileName(default_dir, fileName + ".png"); + + // do not overwrite already existing files with this name + while (fullFileName.FileExists()) + fullFileName.SetName(fullFileName.GetName() + "_"); + + // save the screenshot as a PNG + screenshot.SaveFile(fullFileName.GetFullPath(), wxBITMAP_TYPE_PNG); +} + void AutoCaptureMechanism::CaptureAll() { // start from the first page @@ -133,10 +155,25 @@ void AutoCaptureMechanism::CaptureAll() continue; } +// // create the screenshot +// wxBitmap screenshot = Capture(ctrl); +// if (ctrl.flag & AJ_Union) +// screenshot = Union(screenshot, Capture(*(++it))); +// +// // and save it +// Save(screenshot, ctrl.name); // create the screenshot wxBitmap screenshot = Capture(ctrl); - if (ctrl.flag & AJ_Union) - screenshot = Union(screenshot, Capture(*(++it))); + + if(ctrl.flag & AJ_Union) + { + do + { + ctrl = *(++it); + screenshot = Union(screenshot, Capture(ctrl)); + } + while(!(ctrl.flag & AJ_UnionEnd)); + } // and save it Save(screenshot, ctrl.name); @@ -209,14 +246,8 @@ wxBitmap AutoCaptureMechanism::Union(wxBitmap pic1, wxBitmap pic2) wxBitmap result(w, h, -1); - wxMemoryDC dstDC; - dstDC.SelectObject(result); - - dstDC.DrawBitmap(pic1, 0, 0, false); - dstDC.DrawBitmap(pic2, 0, h1 + gap_between, false); - - dstDC.SelectObject(wxNullBitmap); - +#if 0 + //Mask the bitmap "result" wxMemoryDC maskDC; wxBitmap mask(w, h, 1); maskDC.SelectObject(mask); @@ -231,24 +262,20 @@ wxBitmap AutoCaptureMechanism::Union(wxBitmap pic1, wxBitmap pic2) maskDC.SelectObject(wxNullBitmap); result.SetMask(new wxMask(mask)); +#endif - return result; -} - -void AutoCaptureMechanism::Save(wxBitmap screenshot, wxString fileName) -{ - // make sure m_dir exists - if (!wxDirExists(m_dir)) - wxMkdir(m_dir); + wxMemoryDC dstDC; + dstDC.SelectObject(result); - wxFileName fullFileName(m_dir, fileName + ".png"); + dstDC.SetPen(*wxTRANSPARENT_PEN); + dstDC.SetBrush(*wxWHITE_BRUSH); + dstDC.DrawRectangle(-1, -1, w + 1, h + 1); + dstDC.DrawBitmap(pic1, 0, 0, false); + dstDC.DrawBitmap(pic2, 0, h1 + gap_between, false); - // do not overwrite already existing files with this name - while (fullFileName.FileExists()) - fullFileName.SetName(fullFileName.GetName() + "_"); + dstDC.SelectObject(wxNullBitmap); - // save the screenshot as a PNG - screenshot.SaveFile(fullFileName.GetFullPath(), wxBITMAP_TYPE_PNG); + return result; } wxRect AutoCaptureMechanism::GetRect(wxWindow* ctrl, int flag) @@ -272,7 +299,7 @@ wxRect AutoCaptureMechanism::GetRect(wxWindow* ctrl, int flag) +---------+-----------+---------+ */ - m_grid = new wxFlexGridSizer(3, 3, m_border, m_border); + m_grid = new wxFlexGridSizer(3, 3, m_margin, m_margin); wxStaticText* l[4]; @@ -302,12 +329,12 @@ wxRect AutoCaptureMechanism::GetRect(wxWindow* ctrl, int flag) } else // Actually it won't get here working with the current guiframe.h/guiframe.cpp { - return ctrl->GetScreenRect().Inflate(m_border); + return ctrl->GetScreenRect().Inflate(m_margin); } } else { - return ctrl->GetScreenRect().Inflate(m_border); + return ctrl->GetScreenRect().Inflate(m_margin); } } diff --git a/utils/screenshotgen/src/autocapture.h b/utils/screenshotgen/src/autocapture.h index e0aed4d36e..4adf3936b8 100644 --- a/utils/screenshotgen/src/autocapture.h +++ b/utils/screenshotgen/src/autocapture.h @@ -14,65 +14,315 @@ #include +/** + GlobalAdjustFlags works with AutoCaptureMechanism's constructor, to disbale/enable + some auto-adjustment for all controls. -// TODO: document what these flags mean + They are used to make AutoCaptureMechanism more configurable and provide a fallback + to detect the bugs that the adjustments intended to avoid. + + @see AdjustFlags +*/ +enum GlobalAdjustFlags +{ + /** + This is the default. All adjustments instructed in + AutoCaptureMechanism::RegisterControl() will be performed. + */ + AJ_NormalAll = 0, + + /** + Disable region adjustment for all controls. + */ + AJ_DisableRegionAdjust = 1 << 0, + + /** + Enable region adjustment for all controls. + */ + AJ_AlwaysRegionAdjust = 1 << 1, + + /** + Disable name adjustment for all controls. + */ + AJ_DisableNameAdjust = 1 << 2, + + /** + For all the "Drop-down Controls", e.g. wxChoice, do not prompt the user about whether + to capture their drop-down state, and always capture only its non-drop-down state. + */ + AJ_DisableDropdown = 1 << 3 +}; + +/** + AdjustFlags works with AutoCaptureMechanism::RegisterControl() to specify how to + adjust the screenshot of the current control. + + They are used to avoid bugs, look better or interact with user etc. + + @see GlobalAdjustFlags +*/ enum AdjustFlags { + /** + This is the default. Perform no adjustment for this control. + */ AJ_Normal = 0, + + /** + Perform region adjustment for this control. + + On some platforms and for some controls, wxWindow::GetScreenRect() will return + a smaller or deflected region. In these cases, the screenshots we get are incomplete. + It's recommended for everyone to fix the controls' code, yet this flag provides a + workaround to get a guaranteed correct region without using wxWindow::GetScreenRect(). + + This workaround("label trick") is inspired by (or say stolen from) Auria's work. + */ AJ_RegionAdjust = 1 << 0, + + /** + This flag provides a way to capture the drop-down state of "Drop-down Controls", + e.g. wxChoice. + + For all the "Drop-down Controls", prompt the user about whether to capture their + drop-down state, if the user chooses YES, he should drop down the control in about + 3 seconds and wait util it's captured in that state. + */ AJ_Dropdown = 1 << 1, + + /** + This flag is used internally by RegisterPageTurn(). Don't use it directly unless you + know what you are doing. + */ AJ_TurnPage = 1 << 2, + + /** + This flag provides a functionality to union screenshots of different controls into + one image. + + It's especially useful to demonstrate different modes/states of a control, + e.g. the single-line/multi-line modes of a wxTextCtrl. + + For a series of controls to be unioned, you should specify AJ_Union for the first, + and AJ_UnionEnd for the last. For the controls between them, you can either specify + AJ_Union or not. + */ AJ_Union = 1 << 3, + + /** + @see AJ_Union. + */ AJ_UnionEnd = 1 << 4 }; +/** + @class AutoCaptureMechanism + + AutoCaptureMechanism provides an easy-to-use and adjustable facility to take the screenshots + for all controls fully automaticly and correctly. It also provides an advanced feature to + union screenshots of different states/modes of a control. + + @section tag_filename_convention Screenshot File Name Convention + + All screenshots are generated as PNG files. For a control named wxName, its screenshot file + name would be "name.png", e.g. "button.png" for wxButton. This is the protocol with the + doxygen document of wxWidgets. + + By default, screenshots are generated under the subdirectory "screenshots" of current working + directory. During updating or adding new screenshots, first make sure screenshots are generated + correctly, and then copy them to the following subdirectory of docs/doxygen/images: + + "wxmsw" for MS Windows, "wxgtk" for Linux and "wxmac" for Mac OS. + + @section tag_gui_assumption The Assumption of GUI + + Unfortunately, this class have an assumption about the structure of GUI: + It must have the follwoing top-down structure: + + wxNotebook->wxPanel->wxSizer->wxControl + + That means, in the wxNotebook associated with this class, controls that needs to be + taken screenshots are placed on different panels(for grouping) and layed out by wxSizers. + + @section tag_tutorial Tutorial + + In the contruction, you should associate a wxNotebook with this class, in that wxNotebook, + controls that needs to be captured are placed on different panels(for grouping). + + When you register controls, you should do it in order: Register the controls on the first + panel(using RegisterControl()), and then register a page turn(using RegisterPageTurn()), + so this class can turn a page of the wxNotebook to present the second page. And then + you register the controls on the second panel, then a page turn, and so on. + + When you are done, simply call CaptureAll(), then screenshots of all controls will be + automaticly generated. + + @section tag_autoadjust Make Use of Auto Adjustments -// ---------------------------------------------------------------------------- -// class AutoCaptureMechanism -// ---------------------------------------------------------------------------- + First take a look at the document of RegisterControl(), enum AdjustFlags and + GlobalAdjustFlags. + And then, ScreenshotFrame::OnCaptureAllControls() is a good example of making use of + auto adjustment. Taking a look at it will get you started. + + @section tag_developer_note Notes for Developers + + @subsection tag_cnc CaptureAll() and Capture() + + The implementation of Auto Adjustments is in CaptureAll() and Capture(), the code is + short, quite readable and well commented, please read the codes before any modification. + + If you need the class to do something sepcial for you, consider introducing a new flag + and implement it in them. For an operation performed on multiple controls, implemente + its logic in CaptureAll(), otherwise in the private member Capture(). + + @subsection tag_yield_issue wxYield Issues + + Not quite a good habit, but this class made a lot of use of wxYield()/wxYieldIfNeeded(). + They are used to ensure the update of GUI(e.g. the page turn of wxNotebook) is done + before any further screenshot-taking, or to do the timing(in Delay()). Without their use, + there would be subtle bugs. + + I've read documents about wxYield() and understand the down side of it before using it. + But I didn't find a better approach to do those things, and I used them carefully. So + please DO NOT remove any of these wxYield()s unless you're sure that it won't cause problems + on all of MS Windows XP/Vista, Linux(Ubuntu/Fedora), Mac OS Tiger/Leopard. And please + help me to find a better approach, thank you :) +*/ class AutoCaptureMechanism { public: + /** + Constructor. + + @param notebook + The wxNotebook associated with this class.Please see @ref tag_gui_assumption + and @ref tag_tutorial. + + @param flag + It's one of or a combination of GlobalAdjustFlags, to disbale/enable some auto-adjustment + for all controls. + + @param margin + It's the margin around every control in the sreenshots. + */ AutoCaptureMechanism(wxNotebook *notebook, - wxString directory = wxT("screenshots"), - int border = 5) - : m_notebook(notebook), m_dir(directory), m_border(border) {} + int flag = AJ_NormalAll, + int margin = 5) + : m_notebook(notebook), m_flag(flag), + m_margin(margin), m_grid(NULL) {} ~AutoCaptureMechanism(){} - /* - If wxRTTI can't get the name correctly, specify name; - If wxWindow::GetScreenRect doesn't get the rect correctly, set flag to AJ_RegionAdjust + /** + Register a control and perform specifid auto adjustments. + + @param ctrl + The pointer to the control to be taken a screenshot. + + @param name + If you find out that the screenshot for this control was generated under an incorrect + file name, specify @a name. e.g. for wxButton, "wxButton" or "button" are both OK. + + @param flag + + If you end up with an a smaller or deflected screenshot, use AJ_RegionAdjust. + + If you want to caputure the "drop-down" state of a "drop-down" control, use AJ_Dropdown. + + If you want to present different states of a control in one screenshot, use AJ_Union + and AJ_UnionEnd. + + Please read the document of enum AdjustFlags, and notice that this flag could be enabled/ + disabled by global flag GlobalAdjustFlags. */ - void RegisterControl(wxWindow * ctrl, wxString name = wxT(""), - int flag = AJ_Normal) + void RegisterControl(wxWindow * ctrl, wxString name = _T(""), int flag = AJ_Normal) { m_controlList.push_back(Control(ctrl, name, flag)); } + /** + Register a control and perform specifid auto adjustments. + + This is the same as RegisterControl(wxWindow * ctrl, wxString name, int flag), + But with it, you won't have to specify the name if you only want to auto-adjust something + other than name adjustment. + */ void RegisterControl(wxWindow * ctrl, int flag) { - RegisterControl(ctrl, wxT(""), flag); + RegisterControl(ctrl, _T(""), flag); } + /** + Register a page turn. + + When you finished registering the controls on a panel, remember to call it to turn the + wxNotebook to the next panel. + */ void RegisterPageTurn() { - m_controlList.push_back(Control(0, wxT(""), AJ_TurnPage)); + m_controlList.push_back(Control(0, _T(""), AJ_TurnPage)); } - // capture all controls of the associated notebook + /** + Capture all registered controls of the associated wxNotebook. + */ void CaptureAll(); - // take a screenshot only of the given rect - // delay is only useful for Mac, for fixing a delay bug + /* + Static Members + */ + /** + Take a screenshot for the given region. + + @param rect is the given rectangular region. + + @param delay is only useful for Mac, for fixing a delay bug. It seems that it didn't + fix the bug, so it might be removed soon. + */ static wxBitmap Capture(wxRect rect, int delay = 0); + + /** + Take a screenshot for the given region. + + @see Capture(wxRect rect, int delay) + */ static wxBitmap Capture(int x, int y, int width, int height, int delay = 0); - static void Delay(int seconds); + /** + Save the screenshot as the name of @a fileName in the default directory. + + @a fileName should be without ".png". + */ + static void Save(wxBitmap screenshot, wxString fileName); + + /** + Set the default directory where the screenshots will be generated. + */ + static void SetDefaultDirectory(wxString dir) { default_dir = dir; } + + /** + Get the default directory where the screenshots will be generated. + */ + static wxString GetDefaultDirectory() { return default_dir; } + + /** + Get the absolute path of the default directory where the screenshots will be generated. + */ + static wxString GetDefaultDirectoryAbsPath() + { + wxFileName output = wxFileName::DirName(GetDefaultDirectory()); + output.MakeAbsolute(); + return output.GetFullPath(); + } +private: -private: // internal utils + /* + Internal Data Structures + + They might go public in future to provide reuse of ControlList. + */ struct Control { Control() {} @@ -85,31 +335,76 @@ private: // internal utils int flag; }; + typedef std::vector ControlList; + + /* + Internal Functions + + They are only used to clearify the logic of some public functions and it's nonsense + to call them elsewhere. + */ + + /* + Capture and auto adjust the control. Used by CaptureAll(). + */ wxBitmap Capture(Control & ctrl); - // if AJ_RegionAdjust is specified, the following line will use the label - // trick to adjust the region position and size + /* + Get the correct rectangular region that the control occupies. Used by + Capture(Control & ctrl). + + If AJ_RegionAdjust is specified, it will use the "label trick" to perform + region auto adjustment. + + The "label trick" is to reattach the control to a wxFlexGridSizer m_grid, + surround the control with labels and get the control's region by label's positions. + Just like this: + + +---------+-----------+---------+ + | 0 | label | 1 | + +---------+-----------+---------+ + | label | ctrl | label | + +---------+-----------+---------+ + | 2 | label | 3 | + +---------+-----------+---------+ + + So, there will be a side effect: the control is moved to a new position. So after taking the + screenshot, Capture(Control & ctrl) should call PutBack(wxWindow * ctrl) to put it back. + + If AJ_RegionAdjust isn't specified, it will simply call wxWindow::GetScreenRect(). + */ wxRect GetRect(wxWindow* ctrl, int flag); - // put the control back after the label trick(Using reparent/resizer approach) + /* + Put the control back after the label trick used in GetRect(). Used by + Capture(Control & ctrl). + */ void PutBack(wxWindow * ctrl); - wxBitmap Union(wxBitmap pic1, wxBitmap pic2); + /* + Union two screenshots in the vertical direction, and leave a gap between the + screenshots. Used by CaptureAll(). + + The gap is 20 pixels by default. Currently it isn't configurable. + */ + static wxBitmap Union(wxBitmap pic1, wxBitmap pic2); - void Save(wxBitmap screenshot, wxString fileName); + static void Delay(int seconds); - typedef std::vector ControlList; + /* + Data Members + */ ControlList m_controlList; - // here we introduce the dependency on wxNotebook. - // The assumption of this whole class is that the gui has the following top-down structure - // wxNotebook wxPanel wxSizer wxControls wxNotebook* m_notebook; + int m_flag; + + int m_margin; + wxFlexGridSizer* m_grid; - wxString m_dir; - int m_border; + static wxString default_dir; }; #endif // _AUTOCAPTURE_H_ diff --git a/utils/screenshotgen/src/screenshot_main.cpp b/utils/screenshotgen/src/screenshot_main.cpp index 0970262c1a..6814f68b7b 100644 --- a/utils/screenshotgen/src/screenshot_main.cpp +++ b/utils/screenshotgen/src/screenshot_main.cpp @@ -54,15 +54,12 @@ void ScreenshotFrame::OnQuit(wxCommandEvent& WXUNUSED(event)) void ScreenshotFrame::OnSeeScreenshots(wxCommandEvent& WXUNUSED(event)) { - wxFileName defaultDir = wxFileName::DirName(GetDefaultDirectory()); - defaultDir.MakeAbsolute(); + wxString defaultDir = AutoCaptureMechanism::GetDefaultDirectoryAbsPath(); - // Check if defaultDir already existed - if (!defaultDir.DirExists()) - defaultDir.Mkdir(); - - // Use the native file browser to open defaultDir - wxLaunchDefaultBrowser(defaultDir.GetFullPath()); + if (wxFileName::DirExists(defaultDir)) + wxLaunchDefaultBrowser(defaultDir); + else + wxMessageBox(_("There isn't any screenshots yet.")); } void ScreenshotFrame::OnAbout(wxCommandEvent& WXUNUSED(event)) @@ -85,20 +82,18 @@ void ScreenshotFrame::OnCaptureFullScreen(wxCommandEvent& WXUNUSED(event)) wxCoord screenWidth, screenHeight; dcScreen.GetSize(&screenWidth, &screenHeight); - const wxString fullscreen_filename = GetDefaultDirectoryAbsPath() + _T("fullscreen.png"); - wxBitmap fullscreen = AutoCaptureMechanism::Capture(0, 0, screenWidth, screenHeight); - fullscreen.SaveFile(fullscreen_filename, wxBITMAP_TYPE_PNG); + AutoCaptureMechanism::Save(fullscreen, _T("fullscreen")); wxMessageBox(_("A screenshot of the entire screen was saved as:\n\n ") - + fullscreen_filename, + + AutoCaptureMechanism::GetDefaultDirectoryAbsPath() + _T("fullscreen.png"), _("Full screen capture"), wxICON_INFORMATION|wxOK, this); } void ScreenshotFrame::OnCaptureAllControls(wxCommandEvent& WXUNUSED(event)) { - wxString dir = GetDefaultDirectoryAbsPath(); + wxString dir = AutoCaptureMechanism::GetDefaultDirectoryAbsPath(); // check if there are other screenshots taken before if (wxFileName::DirExists(dir)) diff --git a/utils/screenshotgen/src/screenshot_main.h b/utils/screenshotgen/src/screenshot_main.h index a81ce1352b..c44fb51744 100644 --- a/utils/screenshotgen/src/screenshot_main.h +++ b/utils/screenshotgen/src/screenshot_main.h @@ -26,20 +26,6 @@ protected: // event handlers virtual void OnCaptureFullScreen( wxCommandEvent& event ); virtual void OnCaptureAllControls( wxCommandEvent& event ); - - -private: - - // Before a config class is written, these two functions are placed here. - // It's only a transition and they wil be removed soon - wxString GetDefaultDirectory() const { return _T("screenshots"); } - - wxString GetDefaultDirectoryAbsPath() const - { - wxFileName output = wxFileName::DirName(GetDefaultDirectory()); - output.MakeAbsolute(); - return output.GetFullPath(); - } }; #endif // _SCREENSHOT_MAIN_H_