]>
Commit | Line | Data |
---|---|---|
0d5eda9c FM |
1 | ///////////////////////////////////////////////////////////////////////////// |
2 | // Name: ctrlmaskout.cpp | |
4bae10bd | 3 | // Purpose: Implement CtrlMaskOut class |
0d5eda9c FM |
4 | // Author: Utensil Candel (UtensilCandel@@gmail.com) |
5 | // RCS-ID: $Id$ | |
6 | // Licence: wxWindows license | |
7 | ///////////////////////////////////////////////////////////////////////////// | |
8 | ||
9 | // For compilers that support precompilation, includes "wx/wx.h". | |
10 | #include "wx/wxprec.h" | |
11 | ||
12 | #ifdef __BORLANDC__ | |
13 | #pragma hdrstop | |
14 | #endif | |
15 | ||
16 | // for all others, include the necessary headers | |
17 | #ifndef WX_PRECOMP | |
18 | #include "wx/wx.h" | |
19 | #endif | |
20 | ||
21 | #ifdef __WXMAC__ // See CreateMask() | |
22 | #include <wx/minifram.h> | |
23 | #include <cstdlib> | |
24 | #endif | |
25 | ||
26 | #include <wx/filename.h> | |
27 | #include <wx/hashset.h> | |
28 | ||
29 | #include "ctrlmaskout.h" | |
30 | ||
31 | ||
32 | // It's copied from src/aui/framemanager.cpp and modified, a failed attempt to | |
33 | // visualize the process of taking the screenshot when wxTopLevelWindow::CanSetTransparent | |
4bae10bd | 34 | // returns false. see CtrlMaskOut::CreateMask for more info |
0d5eda9c FM |
35 | // now it shows nothing and does nothing |
36 | ///////////////////////////////////////////////////////////////////////////// | |
37 | ||
38 | class wxPseudoTransparentFrame : public wxFrame | |
39 | { | |
40 | public: | |
41 | wxPseudoTransparentFrame(wxWindow* parent = NULL, | |
42 | wxWindowID id = wxID_ANY, | |
43 | const wxString& title = wxEmptyString, | |
44 | const wxPoint& pos = wxDefaultPosition, | |
45 | const wxSize& size = wxDefaultSize, | |
46 | long style = wxDEFAULT_FRAME_STYLE, | |
47 | const wxString &name = wxT("frame")) | |
48 | : wxFrame(parent, id, title, pos, size, style | wxFRAME_SHAPED, name) | |
49 | { | |
50 | m_lastWidth=0; | |
51 | m_lastHeight=0; | |
52 | } | |
53 | ||
54 | virtual bool SetTransparent(wxByte alpha) | |
55 | { | |
56 | return true; | |
57 | } | |
58 | ||
59 | void OnPaint(wxPaintEvent& WXUNUSED(event)){} | |
60 | ||
61 | virtual void SetSize(const wxRect& rect){} | |
62 | virtual void SetSize(int x, int y, int width, int height, int sizeFlags = wxSIZE_AUTO){} | |
63 | virtual void SetSize(int width, int height){} | |
64 | virtual void SetSize(const wxSize& size){} | |
65 | ||
66 | virtual bool Show(bool show = true){ return true;} | |
67 | ||
68 | void OnSize(wxSizeEvent& event) | |
69 | { | |
70 | // // We sometimes get surplus size events | |
71 | // if ((event.GetSize().GetWidth() == m_lastWidth) && | |
72 | // (event.GetSize().GetHeight() == m_lastHeight)) | |
73 | // { | |
74 | // event.Skip(); | |
75 | // return; | |
76 | // } | |
77 | // | |
78 | // Show(false); | |
79 | // | |
80 | // GetParent()->Update(); | |
81 | // | |
82 | // Show(true); | |
83 | // | |
84 | // | |
85 | // m_lastWidth = event.GetSize().GetWidth(); | |
86 | // m_lastHeight = event.GetSize().GetHeight(); | |
87 | } | |
88 | ||
89 | private: | |
90 | int m_lastWidth,m_lastHeight; | |
91 | ||
92 | DECLARE_DYNAMIC_CLASS(wxPseudoTransparentFrame) | |
93 | DECLARE_EVENT_TABLE() | |
94 | }; | |
95 | ||
96 | IMPLEMENT_DYNAMIC_CLASS(wxPseudoTransparentFrame, wxFrame) | |
97 | ||
98 | BEGIN_EVENT_TABLE(wxPseudoTransparentFrame, wxFrame) | |
99 | EVT_PAINT(wxPseudoTransparentFrame::OnPaint) | |
100 | EVT_SIZE(wxPseudoTransparentFrame::OnSize) | |
101 | END_EVENT_TABLE() | |
102 | ||
103 | ||
104 | ||
105 | // ---------------------------------------------------------------------------- | |
4bae10bd | 106 | // CtrlMaskOut |
0d5eda9c FM |
107 | // ---------------------------------------------------------------------------- |
108 | ||
4bae10bd | 109 | CtrlMaskOut::CtrlMaskOut() |
0d5eda9c FM |
110 | : m_defaultDir(_T("screenshots")), |
111 | m_controlName(_T("")), | |
112 | m_currentRect(0, 0, 0, 0), | |
113 | m_inflateBorder(5), | |
114 | m_mask(NULL), | |
115 | m_isTiming(false) | |
116 | { | |
117 | } | |
118 | ||
4bae10bd | 119 | CtrlMaskOut::~CtrlMaskOut() |
0d5eda9c FM |
120 | { |
121 | if (m_mask != NULL) | |
122 | { | |
123 | m_mask->Destroy(); | |
124 | m_mask = NULL; | |
125 | } | |
126 | } | |
127 | ||
4bae10bd | 128 | void CtrlMaskOut::OnLeftButtonDown(wxMouseEvent& event) |
0d5eda9c FM |
129 | { |
130 | if (m_isTiming) event.Skip(); | |
131 | ||
132 | // Start draging at the left-top corner. | |
133 | wxWindow * thePanel = (wxWindow *)event.GetEventObject(); | |
134 | m_currentRect.SetPosition(thePanel->ClientToScreen(event.GetPosition())); | |
135 | m_currentRect.SetSize(wxSize(1, 1)); | |
136 | ||
137 | // Create a transparent mask to visulize the process of specifying a rect region.. | |
138 | CreateMask(thePanel); | |
139 | } | |
140 | ||
4bae10bd | 141 | void CtrlMaskOut::OnMouseMoving(wxMouseEvent& event) |
0d5eda9c FM |
142 | { |
143 | if (!event.Dragging()) | |
144 | { | |
145 | event.Skip(); | |
146 | return; | |
147 | } | |
148 | ||
149 | wxWindow * thePanel = (wxWindow *)event.GetEventObject(); | |
150 | ||
151 | // Determine the current rect region | |
152 | m_currentRect.SetBottomRight(thePanel->ClientToScreen(event.GetPosition()) - wxPoint(5, 5)); | |
153 | // /Because Mac can't get the left btn up event fired if we don't minus wxPoint(5, 5), | |
154 | // /May fix it by making the panel handle this event later | |
155 | // /This is the current trick | |
156 | // /See also line 200 in CreateMask | |
157 | ||
158 | // Visualize the current rect region | |
159 | m_mask->SetSize(m_currentRect); | |
160 | } | |
161 | ||
4bae10bd | 162 | void CtrlMaskOut::OnLeftButtonUp(wxMouseEvent& event) |
0d5eda9c FM |
163 | { |
164 | if (m_mask == NULL)// Which means it's not after specifying a rect region | |
165 | { | |
166 | event.Skip(); | |
167 | return; | |
168 | } | |
169 | ||
170 | DestroyMask(); | |
171 | ||
172 | // End dragging at the right-bottom corner. | |
173 | wxWindow * thePanel = (wxWindow *)event.GetEventObject(); | |
174 | m_currentRect.SetRightBottom(thePanel->ClientToScreen(event.GetPosition())); | |
175 | ||
176 | if (event.ControlDown()) | |
177 | { | |
178 | m_isTiming = true; | |
179 | (new wxTimer(this))->Start(3000, wxTIMER_ONE_SHOT); | |
4bae10bd | 180 | this->Connect(wxEVT_TIMER, wxTimerEventHandler( CtrlMaskOut::OnTimingFinished ), NULL, this); |
0d5eda9c FM |
181 | } |
182 | else | |
183 | { | |
184 | // The final rect region is determined. | |
185 | DetermineCtrlNameAndRect(); | |
186 | ||
187 | Capture(m_currentRect, m_controlName); | |
188 | } | |
189 | } | |
190 | ||
4bae10bd | 191 | void CtrlMaskOut::OnTimingFinished(wxTimerEvent& event) |
0d5eda9c FM |
192 | { |
193 | // The final rect region is determined. | |
194 | DetermineCtrlNameAndRect(); | |
195 | ||
196 | Capture(m_currentRect, m_controlName); | |
197 | ||
198 | m_isTiming = false; | |
4bae10bd | 199 | this->Disconnect(wxEVT_TIMER, wxTimerEventHandler( CtrlMaskOut::OnTimingFinished ), NULL, this); |
0d5eda9c FM |
200 | } |
201 | ||
4bae10bd | 202 | void CtrlMaskOut::Capture(int x, int y, int width, int height, wxString fileName) |
0d5eda9c FM |
203 | { |
204 | // Somehow wxScreenDC.Blit() doesn't work under Mac for now. Here is a trick. | |
205 | #ifdef __WXMAC__ | |
206 | ||
207 | // wxExecute(_T("screencapture -x ") + tempfile, wxEXEC_SYNC); | |
208 | ||
209 | system("screencapture -x /tmp/wx_screen_capture.png"); | |
210 | ||
211 | wxBitmap fullscreen; | |
212 | ||
213 | do | |
214 | { | |
215 | fullscreen = wxBitmap(_T("/tmp/wx_screen_capture.png"), wxBITMAP_TYPE_PNG); | |
216 | } | |
217 | while(!fullscreen.IsOk()); | |
218 | ||
219 | wxBitmap screenshot = fullscreen.GetSubBitmap(wxRect(x,y,width,height)); | |
220 | ||
221 | #else // Under other paltforms, take a real screenshot | |
222 | ||
223 | // Create a DC for the whole screen area | |
224 | wxScreenDC dcScreen; | |
225 | ||
226 | // Create a Bitmap that will later on hold the screenshot image | |
227 | // Note that the Bitmap must have a size big enough to hold the screenshot | |
228 | // -1 means using the current default colour depth | |
229 | wxBitmap screenshot(width, height, -1); | |
230 | ||
231 | // Create a memory DC that will be used for actually taking the screenshot | |
232 | wxMemoryDC memDC; | |
233 | // Tell the memory DC to use our Bitmap | |
234 | // all drawing action on the memory DC will go to the Bitmap now | |
235 | memDC.SelectObject(screenshot); | |
236 | // Blit (in this case copy) the actual screen on the memory DC | |
237 | // and thus the Bitmap | |
238 | memDC.Blit( 0, // Copy to this X coordinate | |
239 | 0, // Copy to this Y coordinate | |
240 | width, // Copy this width | |
241 | height, // Copy this height | |
242 | &dcScreen, // From where do we copy? | |
243 | x, // What's the X offset in the original DC? | |
244 | y // What's the Y offset in the original DC? | |
245 | ); | |
246 | // Select the Bitmap out of the memory DC by selecting a new | |
247 | // uninitialized Bitmap | |
248 | memDC.SelectObject(wxNullBitmap); | |
249 | ||
250 | #endif // #ifdef __WXMAC__ | |
251 | ||
252 | // Check if m_defaultDir already existed | |
253 | if (!wxDirExists(m_defaultDir)) | |
254 | wxMkdir(m_defaultDir); | |
255 | ||
256 | // Our Bitmap now has the screenshot, so let's save it as an png | |
257 | // The filename itself is without extension. | |
258 | screenshot.SaveFile(m_defaultDir + wxFileName::GetPathSeparator() + | |
259 | fileName + _T(".png"), wxBITMAP_TYPE_PNG); | |
260 | } | |
261 | ||
4bae10bd | 262 | void CtrlMaskOut::Capture(wxRect rect, wxString fileName) |
0d5eda9c FM |
263 | { |
264 | wxPoint origin = rect.GetPosition(); | |
265 | Capture(origin.x, origin.y, rect.GetWidth(), rect.GetHeight(), fileName); | |
266 | } | |
267 | ||
4bae10bd | 268 | void CtrlMaskOut::DetermineCtrlNameAndRect() |
0d5eda9c FM |
269 | { |
270 | // Detect windows using (n-1)*(n-1) points | |
271 | const int n = 5; | |
272 | ||
273 | wxPoint pt0 = m_currentRect.GetPosition(); | |
274 | ||
275 | int dx = m_currentRect.GetWidth() / n; | |
276 | int dy = m_currentRect.GetHeight() / n; | |
277 | ||
278 | wxPoint pt; | |
279 | wxWindow * ctrl; | |
280 | wxString ctrlName; | |
281 | wxRect ctrlSize; | |
282 | ||
283 | // use a set to make sure the same control won't be inserted twice | |
284 | WX_DECLARE_HASH_SET(wxWindow*, wxPointerHash, wxPointerEqual, CtrlSet); | |
285 | ||
286 | CtrlSet ctrls; | |
287 | ||
288 | int i, j; | |
289 | for (i = 1; i <= n - 1; ++i) | |
290 | { | |
291 | for (j = 1; j <= n - 1; ++j) | |
292 | { | |
293 | pt = wxPoint(pt0.x + i * dx, pt0.y + j * dy); | |
294 | ctrls.insert(wxFindWindowAtPoint(pt)); | |
295 | } | |
296 | } | |
297 | ||
298 | // Store the manual specified rect region, we might need it | |
299 | wxRect backup = m_currentRect; | |
300 | ||
301 | // Reset the control region and control name | |
302 | m_currentRect = wxRect(0, 0, 0, 0); | |
303 | m_controlName = _T(""); | |
304 | ||
305 | for (CtrlSet::iterator it = ctrls.begin(); it != ctrls.end(); ++it ) | |
306 | { | |
307 | ctrl = *it; | |
308 | if (ctrl == NULL) | |
309 | // which means no control was detected. | |
310 | // This seldom happens, but without dealing with it, it will CRASH the program | |
311 | { | |
312 | static int count = 0; | |
313 | ++count; | |
314 | m_controlName = _T("nothing_"); | |
315 | m_controlName << count; | |
316 | m_currentRect = backup; | |
317 | return; | |
318 | } | |
319 | ctrlName = ctrl->GetClassInfo()->GetClassName(); | |
320 | ||
321 | if (ctrlName != _T("wxPanel")) // Avoid detecting "wxPanel", which is only the backgroud | |
322 | { | |
323 | // Get a rect region to contain every window detected by unioning one by one. | |
324 | m_currentRect.Union(ctrl->GetScreenRect()); | |
325 | ||
326 | // Get the name of the control, cut off "wx" and change them into lowercase. | |
327 | // e.g. wxButton will have a filename of "button" and be saved as "button.png" | |
328 | ctrlName.StartsWith(_T("wx"), &ctrlName); | |
329 | ctrlName.MakeLower(); | |
330 | ||
331 | if (m_controlName.IsEmpty() || m_controlName == ctrlName) | |
332 | { | |
333 | m_controlName = ctrlName; | |
334 | } | |
335 | else // more than one types of controls are in the region selected | |
336 | { | |
337 | m_controlName += _T("_"); | |
338 | m_controlName += ctrlName; | |
339 | } | |
340 | } | |
341 | } | |
342 | ||
343 | if (m_controlName.IsEmpty() && backup.GetHeight() > 15 && backup.GetWidth() > 15) | |
344 | // which means no control other than the wxPanel was detected. | |
345 | // if the size of the specified rect(backup) is smaller than 15x15, | |
346 | // then it could be just an accident | |
347 | { | |
348 | static int count = 0; | |
349 | ++count; | |
350 | m_controlName = _T("unknown_"); | |
351 | m_controlName << count; | |
352 | m_currentRect = backup; | |
353 | return; | |
354 | } | |
355 | ||
356 | // Increase its border so that it looks nicer. | |
357 | m_currentRect.Inflate(m_inflateBorder); | |
358 | } | |
359 | ||
4bae10bd | 360 | void CtrlMaskOut::CreateMask(wxWindow* parent) |
0d5eda9c FM |
361 | { |
362 | if (m_mask != NULL) | |
363 | m_mask->Destroy(); | |
364 | ||
365 | // Make a frame to visualize the process of specifying the rect region. | |
366 | #if defined(__WXMSW__) || defined(__WXGTK__) | |
367 | m_mask = new wxFrame(parent, wxID_ANY, wxEmptyString, | |
368 | m_currentRect.GetPosition(), m_currentRect.GetSize(), | |
369 | wxFRAME_TOOL_WINDOW | | |
370 | wxFRAME_FLOAT_ON_PARENT | | |
371 | wxFRAME_NO_TASKBAR); | |
372 | // wxNO_BORDER); | |
373 | // with the border, it looks better under windows | |
374 | ||
375 | m_mask->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_ACTIVECAPTION)); | |
376 | ||
377 | #elif defined(__WXMAC__) | |
378 | // Using a miniframe with float and tool styles keeps the parent | |
379 | // frame activated and highlighted as such... | |
380 | m_mask = new wxMiniFrame(parent, wxID_ANY, wxEmptyString, | |
381 | m_currentRect.GetPosition(), m_currentRect.GetSize(), | |
382 | wxFRAME_FLOAT_ON_PARENT | |
383 | | wxFRAME_TOOL_WINDOW ); | |
384 | ||
385 | // Can't set the bg colour of a Frame in wxMac | |
386 | wxPanel* p = new wxPanel(m_mask); | |
387 | ||
388 | // The default wxSYS_COLOUR_ACTIVECAPTION colour is a light silver | |
389 | // color that is really hard to see, especially transparent. | |
390 | // Until a better system color is decided upon we'll just use | |
391 | // blue. | |
392 | p->SetBackgroundColour(*wxBLUE); | |
393 | ||
394 | // So that even if the cursor run into the mask, the events are still correctly processed. | |
4bae10bd FM |
395 | p->Connect( wxEVT_LEFT_DOWN, wxMouseEventHandler( CtrlMaskOut::OnLeftButtonDown ), NULL, this); |
396 | p->Connect( wxEVT_LEFT_UP, wxMouseEventHandler( CtrlMaskOut::OnLeftButtonUp ), NULL, this); | |
397 | p->Connect( wxEVT_MOTION, wxMouseEventHandler( CtrlMaskOut::OnMouseMoving ), NULL, this); | |
0d5eda9c FM |
398 | #endif |
399 | ||
400 | // If the platform doesn't support SetTransparent() | |
401 | // we will use a dummy mask(which does and shows nothing) | |
402 | // /this is the current trick, shall make it visible later | |
403 | if (!m_mask->CanSetTransparent()) | |
404 | { | |
405 | m_mask->Destroy(); | |
406 | ||
407 | m_mask = new wxPseudoTransparentFrame(parent, | |
408 | wxID_ANY, | |
409 | wxEmptyString, | |
410 | wxDefaultPosition, | |
411 | wxSize(1,1), | |
412 | wxFRAME_TOOL_WINDOW | | |
413 | wxFRAME_FLOAT_ON_PARENT | | |
414 | wxFRAME_NO_TASKBAR | |
415 | ); | |
416 | ||
417 | } | |
418 | ||
419 | m_mask->SetTransparent(100); // Full value is 255 | |
420 | m_mask->Show(true); | |
421 | ||
422 | // So that even if the cursor run into the mask, the events are still correctly processed. | |
4bae10bd FM |
423 | m_mask->Connect( wxEVT_LEFT_DOWN, wxMouseEventHandler( CtrlMaskOut::OnLeftButtonDown ), NULL, this); |
424 | m_mask->Connect( wxEVT_LEFT_UP, wxMouseEventHandler( CtrlMaskOut::OnLeftButtonUp ), NULL, this); | |
425 | m_mask->Connect( wxEVT_MOTION, wxMouseEventHandler( CtrlMaskOut::OnMouseMoving ), NULL, this); | |
0d5eda9c FM |
426 | } |
427 | ||
4bae10bd | 428 | void CtrlMaskOut::DestroyMask() |
0d5eda9c | 429 | { |
4bae10bd FM |
430 | m_mask->Disconnect( wxEVT_LEFT_DOWN, wxMouseEventHandler( CtrlMaskOut::OnLeftButtonDown ), NULL, this); |
431 | m_mask->Disconnect( wxEVT_LEFT_UP, wxMouseEventHandler( CtrlMaskOut::OnLeftButtonUp ), NULL, this); | |
432 | m_mask->Disconnect( wxEVT_MOTION, wxMouseEventHandler( CtrlMaskOut::OnMouseMoving ), NULL, this); | |
0d5eda9c FM |
433 | wxWindow * parent = m_mask->GetParent(); |
434 | // m_mask->Destroy(); | |
435 | delete m_mask; | |
436 | ||
437 | parent->Update(); | |
438 | m_mask = NULL; | |
439 | } | |
440 |