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
This commit is contained in:
Francesco Montorsi
2009-01-14 13:26:37 +00:00
parent b466e85a7e
commit 17ad109b8b
4 changed files with 389 additions and 86 deletions

View File

@@ -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)));
{
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,26 +262,22 @@ wxBitmap AutoCaptureMechanism::Union(wxBitmap pic1, wxBitmap pic2)
maskDC.SelectObject(wxNullBitmap);
result.SetMask(new wxMask(mask));
#endif
wxMemoryDC dstDC;
dstDC.SelectObject(result);
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);
dstDC.SelectObject(wxNullBitmap);
return result;
}
void AutoCaptureMechanism::Save(wxBitmap screenshot, wxString fileName)
{
// make sure m_dir exists
if (!wxDirExists(m_dir))
wxMkdir(m_dir);
wxFileName fullFileName(m_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);
}
wxRect AutoCaptureMechanism::GetRect(wxWindow* ctrl, int flag)
{
if (flag & AJ_RegionAdjust)
@@ -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);
}
}

View File

@@ -14,65 +14,315 @@
#include <wx/notebook.h>
/**
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
// ----------------------------------------------------------------------------
// 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:
/**
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);
private: // internal utils
/**
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:
/*
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<Control> 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().
void Save(wxBitmap screenshot, wxString fileName);
The gap is 20 pixels by default. Currently it isn't configurable.
*/
static wxBitmap Union(wxBitmap pic1, wxBitmap pic2);
typedef std::vector<Control> ControlList;
static void Delay(int seconds);
/*
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_

View File

@@ -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))

View File

@@ -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_