]> git.saurik.com Git - wxWidgets.git/blob - contrib/src/applet/appletwindow.cpp
fixed yet another bug in wxActivateEvent handling
[wxWidgets.git] / contrib / src / applet / appletwindow.cpp
1 /****************************************************************************
2 *
3 * wxWindows HTML Applet Package
4 *
5 * Copyright (C) 1991-2001 SciTech Software, Inc.
6 * All rights reserved.
7 *
8 * ======================================================================
9 * |REMOVAL OR MODIFICATION OF THIS HEADER IS STRICTLY PROHIBITED BY LAW|
10 * | |
11 * |This copyrighted computer code is a proprietary trade secret of |
12 * |SciTech Software, Inc., located at 505 Wall Street, Chico, CA 95928 |
13 * |USA (www.scitechsoft.com). ANY UNAUTHORIZED POSSESSION, USE, |
14 * |VIEWING, COPYING, MODIFICATION OR DISSEMINATION OF THIS CODE IS |
15 * |STRICTLY PROHIBITED BY LAW. Unless you have current, express |
16 * |written authorization from SciTech to possess or use this code, you |
17 * |may be subject to civil and/or criminal penalties. |
18 * | |
19 * |If you received this code in error or you would like to report |
20 * |improper use, please immediately contact SciTech Software, Inc. at |
21 * |530-894-8400. |
22 * | |
23 * |REMOVAL OR MODIFICATION OF THIS HEADER IS STRICTLY PROHIBITED BY LAW|
24 * ======================================================================
25 *
26 * Language: ANSI C++
27 * Environment: Any
28 *
29 * Description: Main wxHtmlAppletWindow class implementation
30 *
31 ****************************************************************************/
32
33 // For compilers that support precompilation
34 #include "wx/wxprec.h"
35 #include "wx/utils.h"
36 #include "wx/process.h"
37 #include "wx/spawnbrowser.h"
38 #include "wx/html/forcelnk.h"
39
40 // crt
41 #ifdef __WXMSW__
42 #include <process.h> // spawnl()
43 #endif
44
45 // Include private headers
46 #include "wx/applet/applet.h"
47 #include "wx/applet/window.h"
48 #include "wx/applet/loadpage.h"
49 #include "wx/applet/plugin.h"
50
51 // Preprocessor Stuff
52 #include "wx/applet/prepinclude.h"
53 #include "wx/applet/prepecho.h"
54 #include "wx/applet/prepifelse.h"
55
56 /*---------------------------- Global variables ---------------------------*/
57
58 wxHashTable wxHtmlAppletWindow::m_Cookies;
59
60 /*------------------------- Implementation --------------------------------*/
61
62 // Empty event handler. We include this event handler simply so that
63 // sub-classes of wxApplet can reference wxApplet in the event tables
64 // that they create as necessary.
65 BEGIN_EVENT_TABLE(wxHtmlAppletWindow, wxHtmlWindow)
66 EVT_LOAD_PAGE(wxHtmlAppletWindow::OnLoadPage)
67 EVT_PAGE_LOADED(wxHtmlAppletWindow::OnPageLoaded)
68 END_EVENT_TABLE()
69
70 // Implement the class functions for wxHtmlAppletWindow
71 IMPLEMENT_CLASS(wxHtmlAppletWindow, wxHtmlWindow);
72
73 // Define the wxAppletList implementation
74 #include "wx/listimpl.cpp"
75 WX_DEFINE_LIST(wxAppletList);
76
77 /****************************************************************************
78 REMARKS:
79 Constructor for the applet window class.
80 ****************************************************************************/
81 wxHtmlAppletWindow::wxHtmlAppletWindow(
82 wxWindow *parent,
83 wxWindowID id,
84 wxToolBarBase *navBar,
85 int navBackId,
86 int navForwardId,
87 const wxPoint& pos,
88 const wxSize& size,
89 long style,
90 const wxString& name,
91 const wxString& docroot )
92 : wxHtmlWindow(parent,id,pos,size,style,name)
93 {
94 // Init our locks
95 UnLock();
96
97 // setup client navbars
98 if (navBar) {
99 m_NavBar = navBar;
100 m_NavBackId = navBackId;
101 m_NavForwardId = navForwardId;
102 }
103 else {
104 m_NavBar = NULL;
105 }
106
107 // Set up docroot
108 m_DocRoot = docroot;
109
110 // Set the key_type for applets
111 m_AppletList = wxAppletList(wxKEY_STRING);
112
113 // Add HTML preprocessors
114 // deleting preprocessors is done by the code within the window
115
116 incPreprocessor = new wxIncludePrep(); // #include preprocessor
117 incPreprocessor->ChangeDirectory(m_DocRoot);
118
119 wxEchoPrep * echoPreprocessor = new wxEchoPrep(); // #echo preprocessor
120 wxIfElsePrep * ifPreprocessor = new wxIfElsePrep();
121
122 this->AddProcessor(incPreprocessor);
123 this->AddProcessor(echoPreprocessor);
124 this->AddProcessor(ifPreprocessor);
125 }
126
127 /****************************************************************************
128 REMARKS:
129 Destructor for the applet window class.
130 ****************************************************************************/
131 wxHtmlAppletWindow::~wxHtmlAppletWindow()
132 {
133 }
134
135 /****************************************************************************
136 PARAMETERS:
137 className - Name of the applet class to create an object for
138 size - Initial size of the applet to be created
139
140 RETURNS:
141 Pointer to the wxApplet created, or NULL if unable to create the applet.
142
143 REMARKS:
144 This function is used to create new wxApplet objects dynamically based on the
145 class name as a string. This allows instances of wxApplet classes to be
146 created dynamically based on string values embedded in the custom tags of an
147 HTML page.
148 ****************************************************************************/
149 wxApplet *wxHtmlAppletWindow::CreateApplet(
150 const wxString& classId,
151 const wxString& iName,
152 const wxHtmlTag& params,
153 const wxSize& size)
154 {
155 // Dynamically create the class instance at runtime
156 wxClassInfo *info = wxClassInfo::FindClass(classId.c_str());
157 if (!info)
158 return NULL;
159 wxObject *obj = info->CreateObject();
160 if (!obj)
161 return NULL;
162 wxApplet *applet = wxDynamicCast(obj,wxApplet);
163 if (!applet)
164 return NULL;
165 if (!applet->Create(this,params,size)) {
166 delete applet;
167 return NULL;
168 }
169 else {
170 // do some fixups on the size if its screwed up
171 wxSize nsize = applet->GetBestSize();
172 if (nsize.x < size.x) nsize.x = size.x;
173 if (nsize.y < size.y) nsize.y = size.y;
174 applet->SetSize(nsize);
175 }
176
177 m_AppletList.Append(iName,(wxObject*)applet);
178 return applet;
179 }
180
181 /****************************************************************************
182 PARAMETERS:
183 classId - Name of the Plugin class to create an object for
184
185 RETURNS:
186 Pointer to the wxplugIn created, or NULL if unable to create the PlugIn.
187
188 REMARKS:
189 This function is used to create new wxPlugIn objects dynamically based on the
190 class name as a string. This allows instances of wxPlugIn classes to be
191 created dynamically based on string values embedded in the custom tags of an
192 HTML page.
193 ****************************************************************************/
194 bool wxHtmlAppletWindow::CreatePlugIn(
195 const wxString& classId )
196 {
197 // Dynamically create the class instance at runtime
198 wxClassInfo *info = wxClassInfo::FindClass(classId.c_str());
199 if (!info)
200 return false;
201 wxObject *obj = info->CreateObject();
202 if (!obj)
203 return false;
204 wxPlugIn *plugIn = wxDynamicCast(obj,wxPlugIn);
205 if (!plugIn)
206 return false;
207 if (!plugIn->Create(this)) {
208 delete plugIn;
209 return false;
210 }
211 return true;
212 }
213
214 /****************************************************************************
215 PARAMETERS:
216 appletName - Name of the applet class to find
217
218 RETURNS:
219 Pointer to the wxApplet found, or NULL if not found.
220
221 REMARKS:
222 Find an instance of an applet based on it's name
223 ****************************************************************************/
224 wxApplet *wxHtmlAppletWindow::FindApplet(
225 const wxString& appletName)
226 {
227 wxAppletList::Node *node = m_AppletList.Find(appletName);
228 if (!node)
229 return NULL;
230 return node->GetData();
231 }
232
233 /****************************************************************************
234 PARAMETERS:
235 applet - Pointer to the applet object to remove from the list
236
237 RETURNS:
238 True if the applet was found and removed, false if not. The applet itself
239 is *not* destroyed!
240
241 REMARKS:
242 Remove an applet from the manager. Called during applet destruction
243 ****************************************************************************/
244 bool wxHtmlAppletWindow::RemoveApplet(
245 const wxApplet *applet)
246 {
247 for (wxAppletList::Node *node = m_AppletList.GetFirst(); node; node = node->GetNext()) {
248 if (node->GetData() == applet) {
249 m_AppletList.DeleteNode(node);
250 return true;
251 }
252 }
253 return false;
254 }
255
256 /****************************************************************************
257 PARAMETERS:
258 URL - New URL for the page to load
259
260 RETURNS:
261 True if page loaded successfully, false if not
262
263 REMARKS:
264 Remove an applet from the manager. Called during applet destruction
265 ****************************************************************************/
266 #include "scitech"
267 bool wxHtmlAppletWindow::LoadPage(
268 const wxString& link)
269 {
270 wxString href(link);
271
272 // TODO: technically we allow no relative paths
273
274 // Check to see if it is a real url, if not it is a file
275 if (link.Mid(0, 5).CmpNoCase("http:") != 0) {
276
277 // Check for abs path. If it is not then tack on the path
278 // supplied at creation.
279 // TODO: Abs paths are only used in testing (remove this)
280 if (link.GetChar(1) != ':')
281 href = m_DocRoot + href;
282 }
283
284 if (link.GetChar(0) == '?'){
285 wxString cmd = link.BeforeFirst('=');
286 wxString cmdValue = link.AfterFirst('=');
287
288 // Launches the default Internet browser for the system.
289 if(!(cmd.CmpNoCase("?EXTERNAL"))){
290 return wxSpawnBrowser(this, cmdValue.c_str());
291 }
292
293 // Launches an external program on the system.
294 if (!(cmd.CmpNoCase("?EXECUTE"))){
295 int code = spawnl( P_NOWAIT, cmdValue , NULL );
296 return (!code);
297 }
298
299 // Looks for a href in a variable stored as a cookie. The href can be
300 // changed on the fly.
301 if (!(cmd.CmpNoCase("?VIRTUAL"))){
302 VirtualData& temp = *((VirtualData*)FindCookie(cmdValue));
303 if (&temp) {
304 href = temp.GetHref();
305 }
306 else {
307 #ifdef CHECKED
308 wxLogError(_T("VIRTUAL LINK ERROR: '%s' does not exist."), cmdValue.c_str());
309 #endif
310 return true;
311 }
312 }
313
314 // This launches a qlet - It is like an applet but is more generic in that it
315 // can be of any wxWin type so it then has the freedom to do more stuff.
316 if (!(cmd.CmpNoCase("?WXAPPLET"))){
317 if (!cmdValue.IsNull()){
318 if (!CreatePlugIn(cmdValue)){
319 #ifdef CHECKED
320 wxLogError(_T("Launch Applet ERROR: '%s' does not exist."), cmdValue.c_str());
321 #endif
322 }
323 }
324 return true;
325 }
326 }
327
328 // Inform all the applets that the new page is being loaded
329 for (wxAppletList::Node *node = m_AppletList.GetFirst(); node; node = node->GetNext())
330 (node->GetData())->OnLinkClicked(wxHtmlLinkInfo(href));
331 Show(false);
332 bool stat = wxHtmlWindow::LoadPage(href);
333 Show(true);
334
335 // Enable/Dis the navbar tools
336 if (m_NavBar) {
337 m_NavBar->EnableTool(m_NavForwardId,HistoryCanForward());
338 m_NavBar->EnableTool(m_NavBackId,HistoryCanBack());
339 }
340 return stat;
341 }
342
343 /****************************************************************************
344 PARAMETERS:
345 URL - String URL that we are navigating to
346
347 REMARKS:
348 Called when the user navigates to a new URL from the current page. We simply
349 call the LoadPage function above to load the new page and display it.
350 ****************************************************************************/
351 void wxHtmlAppletWindow::OnLinkClicked(
352 const wxHtmlLinkInfo& link)
353 {
354 LoadPage(link.GetHref());
355 }
356
357 /****************************************************************************
358 REMARKS:
359 Called when the user navigates forward within the HTML history stack.
360 We call all the applets in turn allowing them to handle the navigation
361 command prior to being destructed when the current page is destroyed.
362 ****************************************************************************/
363 bool wxHtmlAppletWindow::HistoryForward()
364 {
365 if (!HistoryCanForward())
366 return false;
367
368 for (wxAppletList::Node *node = m_AppletList.GetFirst(); node; node = node->GetNext())
369 (node->GetData())->OnHistoryForward();
370
371 return wxHtmlWindow::HistoryForward();
372 }
373
374 /****************************************************************************
375 REMARKS:
376 Called when the user navigates backwards within the HTML history stack.
377 We call all the applets in turn allowing them to handle the navigation
378 command prior to being destructed when the current page is destroyed.
379 ****************************************************************************/
380 bool wxHtmlAppletWindow::HistoryBack()
381 {
382 if (!HistoryCanBack())
383 return false;
384
385 for (wxAppletList::Node *node = m_AppletList.GetFirst(); node; node = node->GetNext())
386 (node->GetData())->OnHistoryBack();
387
388 return wxHtmlWindow::HistoryBack();
389 }
390
391 /****************************************************************************
392 PARAMETERS:
393 msg - wxEvent message to be sent to all wxApplets
394
395 REMARKS:
396 This function is called by the wxApplet's when they need to send a message
397 to all other applets on the current page. This is the primary form of
398 communication between applets on the page if they need to inform each
399 other of internal information.
400
401 Note that the event handling terminates as soon as the first wxApplet
402 handles the event. If the event should be handled by all wxApplet's,
403 the event handlers for the applets should not reset the wxEvent::Skip()
404 value (ie: by default it is true).
405 ****************************************************************************/
406 void wxHtmlAppletWindow::SendMessage(
407 wxEvent& msg)
408 {
409 // Preset the skip flag
410 msg.Skip();
411
412 // Process all applets in turn and send them the message
413 for (wxAppletList::Node *node = m_AppletList.GetFirst(); node; node = node->GetNext()) {
414 (node->GetData())->OnMessage(msg);
415 if (!msg.GetSkipped()){
416 wxMessageBox("BREAK");
417 break;
418 }
419 }
420 }
421
422 /****************************************************************************
423 PARAMETERS:
424 name - Uniq wxString used as hash key
425 cookie - wxObject data returned when name is found.
426
427 RETURNS:
428 True if new cookie was added, false if cookie with same name already exists.
429
430 REMARKS:
431 This function is called by the wxApplet's when they need register a cookie
432 of data in the applet window's cookie table. Cookies are arbitrary data
433 objects that are references by unique name's by the wxApplet. These
434 values can be used to store and retrieve data that needs to remain
435 persisent across invocations of the wxApplet. Ie: The first time an
436 applet is created it would use the cookie to store data to maintain
437 it's present state so that if you navigated back to the same page
438 is would be able to re-load the prior state as though the applet
439 was never actually destructed.
440
441 Note: If a cookie with the same name already exists, this function returns
442 false. Hence if you wish to replace a cookie you should first call
443 UnRegisterCookie to ensure the cookie is deleted and then call this
444 function.
445 ****************************************************************************/
446 bool wxHtmlAppletWindow::RegisterCookie(
447 const wxString& name,
448 wxObject *cookie)
449 {
450 // Fail if the named cookie already exists!
451 if (m_Cookies.Get(name))
452 return false;
453 m_Cookies.Put(name,cookie);
454 return true;
455 }
456
457 /****************************************************************************
458 PARAMETERS:
459 name - wxString uniq haskey used to remove item from hash
460
461 RETURNS:
462 True if found and deleted, false if not found in table.
463
464 REMARKS:
465 This function is called by the wxApplet's when they need de-register a
466 cookie of data in the applet window's cookie table. The data in the
467 cookie itself is also deleted before it is removed from the table.
468 ****************************************************************************/
469 bool wxHtmlAppletWindow::UnRegisterCookie(
470 const wxString& name)
471 {
472 wxObject *data = m_Cookies.Delete(name);
473 if (data) {
474 delete data;
475 return true;
476 }
477 return false;
478 }
479
480 /****************************************************************************
481 PARAMETERS:
482 msg - wxEvent message to be sent to all wxApplets
483
484 RETURNS:
485 Pointer to the cookie data if found, NULL if not found.
486
487 REMARKS:
488 This function is called by the wxApplet's when they need to find a cookie
489 of data given it's public name. If the cookie is not found, NULL is
490 returned.
491 ****************************************************************************/
492 wxObject *wxHtmlAppletWindow::FindCookie(
493 const wxString& name)
494 {
495 return m_Cookies.Get(name);
496 }
497
498 /****************************************************************************
499 PARAMETERS:
500 event - Event to handle
501
502 REMARKS:
503 This function handles delayed LoadPage events posted from applets that
504 need to change the page for the current window to a new window.
505 ****************************************************************************/
506 void wxHtmlAppletWindow::OnLoadPage(
507 wxLoadPageEvent &event)
508 {
509 // Test the mutex lock.
510 if (!(IsLocked())){
511 Lock();
512 if (event.GetHtmlWindow() == this){
513 if (LoadPage(event.GetHRef())){
514 // wxPageLoadedEvent evt;
515 // NOTE: This is reserved for later use as we might need to send
516 // page loaded events to applets.
517 }
518 }
519 UnLock();
520 }
521 }
522
523 /****************************************************************************
524 PARAMETERS:
525 event - Event to handle
526
527 REMARKS:
528 This function handles delayed LoadPage events posted from applets that
529 need to change the page for the current window to a new window.
530 ****************************************************************************/
531 void wxHtmlAppletWindow::OnPageLoaded(
532 wxPageLoadedEvent &)
533 {
534 Enable(true);
535 }
536
537 /****************************************************************************
538 PARAMETERS:
539 none
540
541 REMARKS:
542 This function tries to lock the mutex. If it can't, returns
543 immediately with false.
544 ***************************************************************************/
545 bool wxHtmlAppletWindow::TryLock()
546 {
547 if (!m_mutexLock) {
548 m_mutexLock = true;
549 return true;
550 }
551 return false;
552 }
553
554 /****************************************************************************
555 PARAMETERS:
556 name - name of the last applet that changed the data in this object
557 group - name of the group the allplet belongs to.
558 href - webpage to go to.
559
560 REMARKS:
561 VirtualData is used to store information on the virtual links.
562 ****************************************************************************/
563 VirtualData::VirtualData(
564 wxString& name,
565 wxString& group,
566 wxString& href )
567 {
568 m_name = name;
569 m_group = group;
570 m_href = href;
571 }
572
573 /****************************************************************************
574 PARAMETERS:
575 REMARKS:
576 ****************************************************************************/
577 void AppletProcess::OnTerminate(
578 int,
579 int )
580 {
581 // we're not needed any more
582 delete this;
583 }
584
585 #include "wx/html/m_templ.h"
586
587 /****************************************************************************
588 REMARKS:
589 Implementation for the <embed> HTML tag handler. This handler takes care
590 of automatically constructing the wxApplet objects of the appropriate
591 class based on the <embed> tag information.
592 ****************************************************************************/
593 TAG_HANDLER_BEGIN(wxApplet, "WXAPPLET")
594
595 TAG_HANDLER_PROC(tag)
596 {
597 wxWindow *wnd;
598 wxHtmlAppletWindow *appletWindow;
599 wxApplet *applet;
600 wxString classId;
601 wxString name;
602 int width, height;
603
604 wnd = m_WParser->GetWindow();
605
606 if ((appletWindow = wxDynamicCast(wnd,wxHtmlAppletWindow)) != NULL){
607 wxSize size = wxDefaultSize;
608 int al;
609 if (tag.HasParam("WIDTH")) {
610 tag.GetParamAsInt("WIDTH", &width);
611 size.SetWidth(width);
612 }
613
614 if (tag.HasParam("HEIGHT")) {
615 tag.GetParamAsInt("HEIGHT", &height);
616 size.SetHeight(height);
617 }
618
619 al = wxHTML_ALIGN_BOTTOM;
620 if (tag.HasParam(wxT("ALIGN"))) {
621 wxString alstr = tag.GetParam(wxT("ALIGN"));
622 alstr.MakeUpper(); // for the case alignment was in ".."
623 if (alstr == wxT("TEXTTOP") || alstr == wxT("TOP"))
624 al = wxHTML_ALIGN_TOP;
625 else if ((alstr == wxT("CENTER")) || (alstr == wxT("ABSCENTER")))
626 al = wxHTML_ALIGN_CENTER;
627 }
628
629 if (tag.HasParam("CLASSID")){
630 classId = tag.GetParam("CLASSID");
631 if ( classId.IsNull() || classId.Len() == 0 ){
632 wxMessageBox("wxApplet tag error: CLASSID is NULL or empty.","Error",wxICON_ERROR);
633 return false;
634 }
635 if (tag.HasParam("NAME"))
636 name = tag.GetParam("NAME");
637
638 // If the name is NULL or len is zero then we assume that the html guy
639 // didn't include the name param which is optional.
640 if ( name.IsNull() || name.Len() == 0 )
641 name = classId;
642
643 // We got all the params and can now create the applet
644 if ((applet = appletWindow->CreateApplet(classId, name, tag , size)) != NULL){
645 applet->Show(true);
646 m_WParser->OpenContainer()->InsertCell(new wxHtmlAppletCell(applet,al));
647 }
648 else
649 wxMessageBox("wxApplet error: Could not create:" + classId + "," + name);
650 }
651 else{
652 wxMessageBox("wxApplet tag error: Can not find CLASSID param.","Error",wxICON_ERROR);
653 return false;
654 }
655 //Add more param parsing here. If or when spec changes.
656 //For now we'll ignore any other params those HTML guys
657 //might put in our tag.
658 }
659
660 return false;
661 }
662
663 TAG_HANDLER_END(wxApplet)
664
665 TAGS_MODULE_BEGIN(wxApplet)
666 TAGS_MODULE_ADD(wxApplet)
667 TAGS_MODULE_END(wxApplet)
668
669 /*********************************************************************************
670 wxHtmlAppletCell
671 *********************************************************************************/
672 wxHtmlAppletCell::wxHtmlAppletCell(wxWindow *wnd, int align) : wxHtmlCell()
673 {
674 int sx, sy;
675 m_Wnd = wnd;
676 m_Wnd->GetSize(&sx, &sy);
677 m_Width = sx, m_Height = sy;
678
679 switch (align) {
680 case wxHTML_ALIGN_TOP :
681 m_Descent = m_Height;
682 break;
683 case wxHTML_ALIGN_CENTER :
684 m_Descent = m_Height / 2;
685 break;
686 case wxHTML_ALIGN_BOTTOM :
687 default :
688 m_Descent = 0;
689 break;
690 }
691
692 SetCanLiveOnPagebreak(FALSE);
693 }
694
695
696 void wxHtmlAppletCell::Draw(wxDC& WXUNUSED(dc), int WXUNUSED(x), int WXUNUSED(y), int WXUNUSED(view_y1), int WXUNUSED(view_y2))
697 {
698 int absx = 0, absy = 0, stx, sty;
699 wxHtmlCell *c = this;
700
701 while (c)
702 {
703 absx += c->GetPosX();
704 absy += c->GetPosY();
705 c = c->GetParent();
706 }
707
708 ((wxScrolledWindow*)(m_Wnd->GetParent()))->GetViewStart(&stx, &sty);
709 m_Wnd->Move(absx - wxHTML_SCROLL_STEP * stx, absy - wxHTML_SCROLL_STEP * sty);
710 }
711
712
713
714 void wxHtmlAppletCell::DrawInvisible(wxDC& WXUNUSED(dc), int WXUNUSED(x), int WXUNUSED(y))
715 {
716 int absx = 0, absy = 0, stx, sty;
717 wxHtmlCell *c = this;
718
719 while (c)
720 {
721 absx += c->GetPosX();
722 absy += c->GetPosY();
723 c = c->GetParent();
724 }
725
726 ((wxScrolledWindow*)(m_Wnd->GetParent()))->GetViewStart(&stx, &sty);
727 m_Wnd->Move(absx - wxHTML_SCROLL_STEP * stx, absy - wxHTML_SCROLL_STEP * sty);
728 }
729
730
731
732 void wxHtmlAppletCell::Layout(int w)
733 {
734 int sx, sy;
735 m_Wnd->GetSize(&sx, &sy);
736 m_Width = sx, m_Height = sy;
737
738 wxHtmlCell::Layout(w);
739 }
740
741
742
743
744 // This is our little forcelink hack.
745 FORCE_LINK(loadpage)
746