1 /*
2 
3   Copyright (C) 2003 - 2013  Razvan Cojocaru <rzvncj@gmail.com>
4   Mac OS specific patches contributed by Chanler White
5   <cawhite@nwrails.com>
6   "Save link as" patch contributed by Joerg Wunsch
7   <joerg_wunsch@users.sourceforge.net>
8 
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; either version 2 of the License, or
12   (at your option) any later version.
13 
14   This program is distributed in the hope that it will be useful,
15   but WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   GNU General Public License for more details.
18 
19   You should have received a copy of the GNU General Public License
20   along with this program; if not, write to the Free Software
21   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22   MA 02110-1301, USA.
23 
24 */
25 
26 
27 #include <chmhtmlwindow.h>
28 #include <chmhtmlnotebook.h>
29 #include <hhcparser.h>
30 #include <chminputstream.h>
31 #include <chmframe.h>
32 #include <wx/wx.h>
33 #include <wx/log.h>
34 #include <wx/clipbrd.h>
35 #include <wx/filename.h>
36 #include <wx/uri.h>
37 #include <wx/wfstream.h>
38 #include <memory>
39 
40 
41 
CHMHtmlWindow(wxWindow * parent,wxTreeCtrl * tc,CHMFrame * frame)42 CHMHtmlWindow::CHMHtmlWindow(wxWindow *parent, wxTreeCtrl *tc, CHMFrame *frame)
43 	: wxHtmlWindow(parent, -1, wxDefaultPosition, wxSize(200,200)),
44 	  _tcl(tc), _syncTree(true), _found(false), _menu(NULL),
45 	  _frame(frame), _fdlg(NULL)
46 {
47 	_menu = new wxMenu;
48 	_menu->Append(ID_PopupForward, _("For&ward"));
49 	_menu->Append(ID_PopupBack, _("&Back"));
50 	_menu->Append(ID_CopyLink, _("Copy &link location"));
51 	_menu->Append(ID_SaveLinkAs, _("&Save link as.."));
52 	_menu->Append(ID_OpenInNewTab, _("&Open in a new tab"));
53 
54 	_menu->AppendSeparator();
55 	_menu->Append(ID_CopySel, _("&Copy selection"));
56 	_menu->AppendSeparator();
57 	_menu->Append(ID_PopupFind, _("&Find in page.."));
58 	_menu->AppendSeparator();
59 	_menu->Append(ID_PopupFullScreen, _("&Toggle fullscreen mode"));
60 }
61 
62 
~CHMHtmlWindow()63 CHMHtmlWindow::~CHMHtmlWindow()
64 {
65 	delete _menu;
66 	delete _fdlg;
67 }
68 
69 
LoadPage(const wxString & location)70 bool CHMHtmlWindow::LoadPage(const wxString& location)
71 {
72 	wxLogNull log;
73 	wxString tmp = location;
74 	if(!tmp.Left(19).CmpNoCase(wxT("javascript:fullsize")))
75 		tmp = tmp.AfterFirst(wxT('\'')).BeforeLast(wxT('\''));
76 
77 	if(_syncTree &&
78 	   // We should be looking for a valid page, not / (home).
79 	   !location.AfterLast(wxT('/')).IsEmpty() &&
80 	   _tcl->GetCount() > 1) {
81 
82 		wxFileName fwfn(tmp.AfterLast(wxT(':')).BeforeFirst(wxT('#')),
83 				wxPATH_UNIX);
84 		wxString cwd = GetParser()->GetFS()->
85 			GetPath().AfterLast(wxT(':'));
86 		fwfn.Normalize(wxPATH_NORM_DOTS | wxPATH_NORM_ABSOLUTE, cwd,
87 				wxPATH_UNIX);
88 
89 		// Sync will call SelectItem() on the tree item
90 		// if it finds one, and that in turn will call
91 		// LoadPage() with _syncTree set to false.
92 		Sync(_tcl->GetRootItem(), fwfn.GetFullPath(wxPATH_UNIX));
93 
94 		if(_found)
95 			_found = false;
96 	}
97 	return wxHtmlWindow::LoadPage(tmp);
98 }
99 
100 
Sync(wxTreeItemId root,const wxString & page)101 void CHMHtmlWindow::Sync(wxTreeItemId root, const wxString& page)
102 {
103 	if(_found)
104 		return;
105 
106 	URLTreeItem *data = reinterpret_cast<URLTreeItem *>(
107 		_tcl->GetItemData(root));
108 
109 	wxString url;
110 
111 	if(data)
112 		url = (data->_url).BeforeFirst(wxT('#'));
113 
114 	if(data && !url.CmpNoCase(page)) {
115 		_found = true;
116 		_tcl->SelectItem(root);
117 		return;
118 	}
119 
120 	wxTreeItemIdValue cookie;
121 	wxTreeItemId child = _tcl->GetFirstChild(root, cookie);
122 
123 	for(size_t i = 0; i < _tcl->GetChildrenCount(root, FALSE); ++i) {
124 		Sync(child, page);
125 		child = _tcl->GetNextChild(root, cookie);
126 	}
127 }
128 
129 
GetPrefix(const wxString & location) const130 wxString CHMHtmlWindow::GetPrefix(const wxString& location) const
131 {
132 	return location.AfterLast(wxT(':')).AfterFirst(
133 		           wxT('/')).BeforeLast(wxT('/'));
134 }
135 
136 
OnLinkClicked(const wxHtmlLinkInfo & link)137 void CHMHtmlWindow::OnLinkClicked(const wxHtmlLinkInfo& link)
138 {
139 	wxString url = link.GetHref();
140 
141 	LoadPage(url);
142 
143 	if(!url.Left(7).CmpNoCase(wxT("MS-ITS:")))
144 		_frame->UpdateCHMInfo();
145 }
146 
147 
FindFirst(wxHtmlCell * parent,const wxString & word,bool wholeWords,bool caseSensitive)148 wxHtmlCell* CHMHtmlWindow::FindFirst(wxHtmlCell *parent, const wxString& word,
149 				     bool wholeWords, bool caseSensitive)
150 {
151 	wxString tmp = word;
152 
153 	if(!parent)
154 		return NULL;
155 
156 	if(!caseSensitive)
157 		tmp.MakeLower();
158 
159 	// If this cell is not a container, the for body will never happen
160 	// (GetFirstChild() will return NULL).
161 	for(wxHtmlCell *cell = parent->GetFirstChild(); cell;
162 	    cell = cell->GetNext()) {
163 
164 		wxHtmlCell *result;
165 		if((result = FindFirst(cell, word, wholeWords, caseSensitive)))
166 			return result;
167 	}
168 
169 	wxHtmlSelection ws;
170 	ws.Set(parent, parent);
171 	wxString text = parent->ConvertToText(&ws);
172 
173 	if(text.IsEmpty())
174 		return NULL;
175 
176 	if(!caseSensitive)
177 		text.MakeLower();
178 
179 	text.Trim(TRUE);
180 	text.Trim(FALSE);
181 
182 	bool found = false;
183 
184 	if(wholeWords && text == tmp) {
185 		found = true;
186 	} else if(!wholeWords && text.Find(tmp.c_str()) != -1) {
187 		found = true;
188 	}
189 
190 	if(found) {
191 		// What is all this wxWidgets protected member crap?
192 		delete m_selection;
193 		m_selection = new wxHtmlSelection();
194 
195 		// !! Must see if this works now. !!
196 		m_selection->Set(parent, parent);
197 
198 		int y;
199 		wxHtmlCell *cell = parent;
200 
201 		for (y = 0; cell != NULL; cell = cell->GetParent())
202 			y += cell->GetPosY();
203 		Scroll(-1, y / wxHTML_SCROLL_STEP);
204 		Refresh();
205 
206 		return parent;
207 	}
208 
209 	return NULL;
210 }
211 
212 
FindNext(wxHtmlCell * start,const wxString & word,bool wholeWords,bool caseSensitive)213 wxHtmlCell* CHMHtmlWindow::FindNext(wxHtmlCell *start, const wxString& word,
214 				    bool wholeWords, bool caseSensitive)
215 {
216 	wxHtmlCell *cell;
217 
218 	if(!start)
219 		return NULL;
220 
221 	for(cell = start; cell; cell = cell->GetNext()) {
222 		wxHtmlCell *result;
223 		if((result = FindFirst(cell, word, wholeWords, caseSensitive)))
224 			return result;
225 	}
226 
227 	cell = start->GetParent();
228 
229 	while(cell && !cell->GetNext())
230 		cell = cell->GetParent();
231 
232 	if(!cell)
233 		return NULL;
234 
235 	return FindNext(cell->GetNext(), word, wholeWords, caseSensitive);
236 }
237 
238 
ClearSelection()239 void CHMHtmlWindow::ClearSelection()
240 {
241 	delete m_selection;
242 	m_selection = NULL;
243 	Refresh();
244 }
245 
OnCopy(wxCommandEvent & WXUNUSED (event))246 void CHMHtmlWindow::OnCopy(wxCommandEvent& WXUNUSED(event))
247 {
248 	CopySelection();
249 }
250 
251 
OnFind(wxCommandEvent & WXUNUSED (event))252 void CHMHtmlWindow::OnFind(wxCommandEvent& WXUNUSED(event))
253 {
254 	if(!_fdlg) {
255 		wxWindow* p = GetParent();
256 		while(p->GetParent())
257 			p = p->GetParent();
258 
259 		_fdlg = new CHMFindDialog(p, this);
260 	}
261 
262 	_fdlg->CentreOnParent();
263 	_fdlg->ShowModal();
264 	_fdlg->SetFocusToTextBox();
265 	_fdlg->Reset();
266 }
267 
268 
OnForward(wxCommandEvent & WXUNUSED (event))269 void CHMHtmlWindow::OnForward(wxCommandEvent& WXUNUSED(event))
270 {
271 	HistoryForward();
272 }
273 
274 
OnBack(wxCommandEvent & WXUNUSED (event))275 void CHMHtmlWindow::OnBack(wxCommandEvent& WXUNUSED(event))
276 {
277 	HistoryBack();
278 }
279 
280 
OnSize(wxSizeEvent & event)281 void CHMHtmlWindow::OnSize(wxSizeEvent& event)
282 {
283 	int x, y;
284 	GetViewStart(&x, &y);
285 
286 	wxHtmlWindow::OnSize(event);
287 
288 	Scroll(x, y);
289 	event.Skip(false);
290 }
291 
292 
OnCopyLink(wxCommandEvent & WXUNUSED (event))293 void CHMHtmlWindow::OnCopyLink(wxCommandEvent& WXUNUSED(event))
294 {
295 	if(wxTheClipboard->Open()) {
296 		wxTheClipboard->SetData(
297 			new wxTextDataObject(_link));
298 		wxTheClipboard->Close();
299 	}
300 }
301 
OnSaveLinkAs(wxCommandEvent & WXUNUSED (event))302 void CHMHtmlWindow::OnSaveLinkAs(wxCommandEvent& WXUNUSED(event))
303 {
304 	std::auto_ptr<wxFSFile> f(m_FS->OpenFile(_link));
305 
306 	if (f.get() == NULL) {
307 		::wxMessageBox(_("OpenFile(") + _link + _(") failed"),
308 			       _("Error"), wxOK, this);
309 		return;
310 	}
311 
312 	wxFileName wfn(_link);
313 	wxString suggestedName = wfn.GetFullName();
314 
315 	if(suggestedName.IsEmpty())
316 		suggestedName = wxT("index.html");
317 
318 	wxString filename = ::wxFileSelector(_("Save as"),
319 					     wxT(""),
320 					     suggestedName,
321 					     wxT(""),
322 					     wxT("*.*"),
323 					     wxFD_SAVE | wxFD_OVERWRITE_PROMPT,
324 					     this);
325 	if (!filename.empty()) {
326 		wxInputStream *s = f->GetStream();
327 		wxFileOutputStream out(filename);
328 		if (!out.IsOk()) {
329 			::wxMessageBox(_("Error creating file ") + filename,
330 				       _("Error"), wxOK, this);
331 		} else {
332 			char buffer[4096];
333 			while (!s->Eof()) {
334 				s->Read(buffer, sizeof(buffer));
335 				size_t nbytes = s->LastRead();
336 				out.Write((void *)buffer, nbytes);
337 			}
338 			::wxMessageBox(_("Saved file ") + filename,
339 				       _("Success"), wxOK, this);
340 		}
341 	}
342 }
343 
344 
OnRightClick(wxMouseEvent & event)345 void CHMHtmlWindow::OnRightClick(wxMouseEvent& event)
346 {
347 	if(IsSelectionEnabled())
348 		_menu->Enable(ID_CopySel, m_selection != NULL);
349 
350 	_menu->Enable(ID_PopupForward, HistoryCanForward());
351 	_menu->Enable(ID_PopupBack, HistoryCanBack());
352 	_menu->Enable(ID_CopyLink, false);
353 	_menu->Enable(ID_SaveLinkAs, false);
354 	_menu->Enable(ID_OpenInNewTab, false);
355 
356         int x, y;
357         CalcUnscrolledPosition(event.m_x, event.m_y, &x, &y);
358 
359 	wxHtmlCell *cell = GetInternalRepresentation()->
360 		FindCellByPos(x, y);
361 
362 	wxHtmlLinkInfo* linfo = NULL;
363 
364 	if(cell)
365 		linfo = cell->GetLink();
366 
367 	if(linfo) {
368 		_link = linfo->GetHref();
369 		_menu->Enable(ID_CopyLink, true);
370 		_menu->Enable(ID_SaveLinkAs, true);
371 		_menu->Enable(ID_OpenInNewTab, true);
372 	}
373 
374 	PopupMenu(_menu, event.GetPosition());
375 }
376 
377 
OnOpenInNewTab(wxCommandEvent & WXUNUSED (event))378 void CHMHtmlWindow::OnOpenInNewTab(wxCommandEvent& WXUNUSED(event))
379 {
380 	wxString link = _link;
381 
382 	if(link.StartsWith(wxT("#"))) // anchor
383 		link = GetOpenedPage() + _link;
384 
385 	_frame->AddHtmlView(GetParser()->GetFS()->GetPath(), link);
386 }
387 
388 
OnToggleFullScreen(wxCommandEvent & WXUNUSED (event))389 void CHMHtmlWindow::OnToggleFullScreen(wxCommandEvent& WXUNUSED(event))
390 {
391 	_frame->ToggleFullScreen();
392 }
393 
394 
OnChar(wxKeyEvent & event)395 void CHMHtmlWindow::OnChar(wxKeyEvent& event)
396 {
397 	int x = 0, y = 0;
398 	int xUnit = 0, yUnit = 0;
399 
400 	switch(event.GetKeyCode()) {
401 	case WXK_SPACE:
402 		event.m_keyCode = WXK_PAGEDOWN;
403 		break;
404 	case WXK_BACK:
405 		event.m_keyCode = WXK_PAGEUP;
406 		break;
407 	case WXK_ESCAPE:
408 		_frame->ToggleFullScreen(true);
409 		break;
410 	case 'g':
411 	case WXK_HOME:
412 		Scroll(0, 0);
413 		break;
414 	case WXK_END:
415 	case 'G':
416 		GetVirtualSize(&x, &y);
417 		GetScrollPixelsPerUnit(&xUnit, &yUnit);
418 		Scroll(0, y / yUnit);
419 		break;
420 	case 'j':
421 		event.m_keyCode = WXK_DOWN;
422 		break;
423 	case 'k':
424 		event.m_keyCode = WXK_UP;
425 		break;
426 	case 'h':
427 		event.m_keyCode = WXK_LEFT;
428 		break;
429 	case 'l':
430 		event.m_keyCode = WXK_RIGHT;
431 		break;
432 	default:
433 		break;
434 	}
435 
436 	event.Skip();
437 }
438 
439 
OnSetTitle(const wxString & title)440 void CHMHtmlWindow::OnSetTitle(const wxString& title)
441 {
442 	// Direct access to the notebook
443 	// TODO: design a new event type
444 	CHMHtmlNotebook* parent = dynamic_cast<CHMHtmlNotebook*>(GetParent());
445 
446 	if(parent)
447 		parent->OnChildrenTitleChanged(title);
448 
449 	wxHtmlWindow::OnSetTitle(title);
450 }
451 
452 
453 BEGIN_EVENT_TABLE(CHMHtmlWindow, wxHtmlWindow)
454 	EVT_MENU(ID_CopySel, CHMHtmlWindow::OnCopy)
455 	EVT_MENU(ID_PopupFind, CHMHtmlWindow::OnFind)
456 	EVT_MENU(ID_PopupForward, CHMHtmlWindow::OnForward)
457 	EVT_MENU(ID_PopupBack, CHMHtmlWindow::OnBack)
458 	EVT_MENU(ID_CopyLink, CHMHtmlWindow::OnCopyLink)
459 	EVT_MENU(ID_SaveLinkAs, CHMHtmlWindow::OnSaveLinkAs)
460 	EVT_MENU(ID_OpenInNewTab, CHMHtmlWindow::OnOpenInNewTab)
461 	EVT_MENU(ID_PopupFullScreen, CHMHtmlWindow::OnToggleFullScreen)
462 	EVT_CHAR(CHMHtmlWindow::OnChar)
463 	EVT_RIGHT_DOWN(CHMHtmlWindow::OnRightClick)
464 	EVT_SIZE(CHMHtmlWindow::OnSize)
465 END_EVENT_TABLE()
466 
467 
468 /*
469   Local Variables:
470   mode: c++
471   c-basic-offset: 8
472   tab-width: 8
473   c-indent-comments-syntactically-p: t
474   c-tab-always-indent: t
475   indent-tabs-mode: t
476   End:
477 */
478 
479 // vim:shiftwidth=8:autoindent:tabstop=8:noexpandtab:softtabstop=8
480 
481 
482