X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/0d5eda9ccdc4c24ac09f58bd0cb6a0709e9ead72..e2758e21753c00b8e36f11c003fd6cd0e5137391:/utils/screenshotgen/src/autocapture.h diff --git a/utils/screenshotgen/src/autocapture.h b/utils/screenshotgen/src/autocapture.h index 6893cc498d..20f16415e5 100644 --- a/utils/screenshotgen/src/autocapture.h +++ b/utils/screenshotgen/src/autocapture.h @@ -6,87 +6,327 @@ // Licence: wxWindows license ///////////////////////////////////////////////////////////////////////////// -#ifndef AUTOCAP_H -#define AUTOCAP_H +#ifndef _AUTOCAPTURE_H_ +#define _AUTOCAPTURE_H_ -#include -#include +#include "wx/beforestd.h" #include -#include +#include "wx/afterstd.h" -// Global helper functions. to take screenshot for a rect region -wxBitmap Capture(wxRect rect); -wxBitmap Capture(int x, int y, int width, int height); +#include "wx/gdicmn.h" +class wxBitmap; +class wxFlexGridSizer; +class wxWindow; +class wxNotebook; + +/** + GlobalAdjustFlags works with AutoCaptureMechanism's constructor, to disbale/enable + some auto-adjustment for all controls. + + 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. + + If AJ_DisableRegionAdjust and AJ_AlwaysRegionAdjust are both specified, current + implemetation will ignore AJ_DisableRegionAdjust. + */ + 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 modes/states of + a control into one image. 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. + + The filename of the generated screenshot is the name of the first control in the series. + */ 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 + + 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: - AutoCaptureMechanism(wxNotebook * notebook, - wxString directory = wxT("screenshots"), - int border = 5) - : m_notebook(notebook), m_dir(directory), m_border(border) {} - ~AutoCaptureMechanism(){} + /** + Constructor. - /* - If wxRTTI can't get the name correctly, specify name; - If wxWindow::GetScreenRect doesn't get the rect correctly, set flag to AJ_RegionAdjust + @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, + int flag = AJ_NormalAll, + int margin = 5); + + ~AutoCaptureMechanism() { } + + /** + 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 = wxT(""), 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); } + /** + 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)); } - void CaptureAll() - { - m_notebook->SetSelection(0); - wxYield(); - - for(ControlList::iterator it = m_controlList.begin(); - it != m_controlList.end(); - ++it) - { - Control & ctrl = *it; - - if(ctrl.flag == AJ_TurnPage) // Turn to next page - { - m_notebook->SetSelection(m_notebook->GetSelection() + 1); - wxYield(); - continue; - } - - wxBitmap screenshot = Capture(ctrl); - - if(ctrl.flag & AJ_Union) - { - screenshot = Union(screenshot, Capture(*(++it))); - } - - Save(screenshot, ctrl.name); - } - } + /** + Capture all registered controls of the associated wxNotebook. + */ + void CaptureAll(); + + /* + Static Members + */ + /** + Take a screenshot for the given region. + + @param bitmap + Bitmap to save the screenshot to. + @param rect + Given rectangular region. + @param delay + 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 bool Capture(wxBitmap* bitmap, wxRect rect, int delay = 0); + + /** + Take a screenshot for the given region. + + @see Capture(wxBitmap*,wxRect,int) + */ + static bool Capture(wxBitmap* bitmap, int x, int y, int width, int height, int delay = 0); + + /** + Save the screenshot as the name of @a fileName in the default directory. + + @a fileName should be without ".png". + */ + static void Save(wxBitmap* screenshot, const wxString& fileName); + + /** + Set the default directory where the screenshots will be generated. + */ + static void SetDefaultDirectory(const 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(); private: + + /* + Internal Data Structures + + They might go public in future to provide reuse of ControlList. + */ struct Control { Control() {} @@ -100,129 +340,80 @@ private: }; typedef std::vector ControlList; - 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; - wxFlexGridSizer* m_grid; + /* + Internal Functions - wxString m_dir; - int m_border; + 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(). + */ + bool Capture(wxBitmap* bitmap, Control& ctrl); + /* + Get the correct rectangular region that the control occupies. Used by + Capture(Control & ctrl). - wxBitmap Capture(Control & ctrl) - { - if(ctrl.name == wxT("")) //no mannual specification for the control name - { - //Get name from wxRTTI - ctrl.name = ctrl.ctrl->GetClassInfo()->GetClassName(); - } - - int choice = wxNO; - - if(ctrl.flag & AJ_Dropdown) - { - wxString caption = _("Do you wish to capture the dropdown list of ") + ctrl.name + _("?"); - wxString notice = _("Click YES to capture it.\nAnd you MUST drop down the ") + ctrl.name + _(" in 3 seconds after close me.\n"); - notice += _("Click NO otherwise."); - - choice = wxMessageBox(notice, caption, wxYES_NO, m_notebook); - - if(choice == wxYES) - { - //Wait for 3 seconds - using std::clock; - using std::clock_t; - - clock_t start = clock(); - while(clock() - start < CLOCKS_PER_SEC * 3) - { - wxYieldIfNeeded(); - } - } - } - - wxRect rect = GetRect(ctrl.ctrl, ctrl.flag); - - //Do some rect adjust so it can include the dropdown list - //Currently it only works well under MSW, not adjusted for Linux and Mac OS - if(ctrl.flag & AJ_Dropdown && choice == wxYES) - { -// #ifdef __WXMSW__ - int h = rect.GetHeight(); - rect.SetHeight(h * 4); -// #endif - } - - //cut off "wx" and change them into lowercase. - // e.g. wxButton will have a name of "button" at the end - ctrl.name.StartsWith(_T("wx"), &(ctrl.name)); - ctrl.name.MakeLower(); - - wxBitmap screenshot = ::Capture(rect); - - if(ctrl.flag & AJ_RegionAdjust) - { - PutBack(ctrl.ctrl); - } - - return screenshot; - } + If AJ_RegionAdjust is specified, it will use the "label trick" to perform + region auto adjustment. - //if AJ_RegionAdjust is specified, the following line will use the label trick to adjust - //the region position and size - wxRect GetRect(wxWindow* ctrl, int flag); - //put the control back after the label trick(Using reparent/resizer approach) - void PutBack(wxWindow * ctrl); + 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: - wxBitmap Union(wxBitmap pic1, wxBitmap pic2) - { - int w1, w2, h1, h2, w, h; - w1 = pic1.GetWidth(); - w2 = pic2.GetWidth(); - h1 = pic1.GetHeight(); - h2 = pic2.GetHeight(); + +---------+-----------+---------+ + | 0 | label | 1 | + +---------+-----------+---------+ + | label | ctrl | label | + +---------+-----------+---------+ + | 2 | label | 3 | + +---------+-----------+---------+ - const int gap_between = 20; + 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. - w = (w1 >= w2) ? w1 : w2; - h = h1 + h2 + gap_between; + If AJ_RegionAdjust isn't specified, it will simply call wxWindow::GetScreenRect(). + */ + wxRect GetRect(wxWindow* ctrl, int flag); - wxBitmap result(w, h, -1); + /* + Put the control back after the label trick used in GetRect(). Used by + Capture(Control & ctrl). + */ + void PutBack(wxWindow * ctrl); - wxMemoryDC dstDC; - dstDC.SelectObject(result); + /* + Union two screenshots in the vertical direction, and leave a gap between the + screenshots. Used by CaptureAll(). - dstDC.DrawBitmap(pic1, 0, 0, false); - dstDC.DrawBitmap(pic2, 0, h1 + gap_between, false); + The gap is 20 pixels by default. Currently it isn't configurable. + */ + static bool Union(wxBitmap* top, wxBitmap* bottom, wxBitmap* result); - dstDC.SelectObject(wxNullBitmap); + /* + Delay a few seconds without blocking GUI. + */ + static void Delay(int seconds); - wxMemoryDC maskDC; - wxBitmap mask(w, h, 1); - maskDC.SelectObject(mask); + /* + Data Members + */ + ControlList m_controlList; - maskDC.SetPen(*wxTRANSPARENT_PEN); - maskDC.SetBrush(*wxBLACK_BRUSH); - maskDC.DrawRectangle(0, 0, w + 1, h + 1); + wxNotebook* m_notebook; - maskDC.SetBrush(*wxWHITE_BRUSH); - maskDC.DrawRectangle(0, 0, w1, h1); - maskDC.DrawRectangle(0, h1 + gap_between, w2, h2); - maskDC.SelectObject(wxNullBitmap); + int m_flag; - result.SetMask(new wxMask(mask)); + int m_margin; - return result; - } + wxFlexGridSizer* m_grid; - void Save(wxBitmap screenshot, wxString fileName); + static wxString default_dir; }; -#endif // AUTOCAP_H +#endif // _AUTOCAPTURE_H_