| 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 | // Include private headers |
| 41 | #include "wx/applet/applet.h" |
| 42 | #include "wx/applet/window.h" |
| 43 | #include "wx/applet/loadpage.h" |
| 44 | |
| 45 | // Preprocessor Stuff |
| 46 | #include "wx/applet/prepinclude.h" |
| 47 | #include "wx/applet/prepecho.h" |
| 48 | #include "wx/applet/prepifelse.h" |
| 49 | |
| 50 | /*---------------------------- Global variables ---------------------------*/ |
| 51 | |
| 52 | wxHashTable wxHtmlAppletWindow::m_Cookies; |
| 53 | |
| 54 | /*------------------------- Implementation --------------------------------*/ |
| 55 | |
| 56 | // Empty event handler. We include this event handler simply so that |
| 57 | // sub-classes of wxApplet can reference wxApplet in the event tables |
| 58 | // that they create as necessary. |
| 59 | BEGIN_EVENT_TABLE(wxHtmlAppletWindow, wxHtmlWindow) |
| 60 | EVT_LOAD_PAGE(wxHtmlAppletWindow::OnLoadPage) |
| 61 | EVT_PAGE_LOADED(wxHtmlAppletWindow::OnPageLoaded) |
| 62 | END_EVENT_TABLE() |
| 63 | |
| 64 | // Implement the class functions for wxHtmlAppletWindow |
| 65 | IMPLEMENT_CLASS(wxHtmlAppletWindow, wxHtmlWindow); |
| 66 | |
| 67 | // Define the wxAppletList implementation |
| 68 | #include "wx/listimpl.cpp" |
| 69 | WX_DEFINE_LIST(wxAppletList); |
| 70 | |
| 71 | /**************************************************************************** |
| 72 | REMARKS: |
| 73 | Constructor for the applet window class. |
| 74 | ****************************************************************************/ |
| 75 | wxHtmlAppletWindow::wxHtmlAppletWindow( |
| 76 | wxWindow *parent, |
| 77 | wxWindowID id, |
| 78 | wxToolBarBase *navBar, |
| 79 | int navBackId, |
| 80 | int navForwardId, |
| 81 | const wxPoint& pos, |
| 82 | const wxSize& size, |
| 83 | long style, |
| 84 | const wxString& name, |
| 85 | const wxString& docroot ) |
| 86 | : wxHtmlWindow(parent,id,pos,size,style,name) |
| 87 | { |
| 88 | // Init our locks |
| 89 | UnLock(); |
| 90 | |
| 91 | // setup client navbars |
| 92 | if (navBar) { |
| 93 | m_NavBar = navBar; |
| 94 | m_NavBackId = navBackId; |
| 95 | m_NavForwardId = navForwardId; |
| 96 | } |
| 97 | else { |
| 98 | m_NavBar = NULL; |
| 99 | } |
| 100 | |
| 101 | // Set up docroot |
| 102 | m_DocRoot = docroot; |
| 103 | |
| 104 | // Add HTML preprocessors |
| 105 | // deleting preprocessors is done by the code within the window |
| 106 | |
| 107 | incPreprocessor = new wxIncludePrep(); // #include preprocessor |
| 108 | incPreprocessor->ChangeDirectory(m_DocRoot); |
| 109 | |
| 110 | wxEchoPrep * echoPreprocessor = new wxEchoPrep(); // #echo preprocessor |
| 111 | wxIfElsePrep * ifPreprocessor = new wxIfElsePrep(); |
| 112 | |
| 113 | this->AddProcessor(incPreprocessor); |
| 114 | this->AddProcessor(echoPreprocessor); |
| 115 | this->AddProcessor(ifPreprocessor); |
| 116 | } |
| 117 | |
| 118 | /**************************************************************************** |
| 119 | REMARKS: |
| 120 | Destructor for the applet window class. |
| 121 | ****************************************************************************/ |
| 122 | wxHtmlAppletWindow::~wxHtmlAppletWindow() |
| 123 | { |
| 124 | } |
| 125 | |
| 126 | /**************************************************************************** |
| 127 | PARAMETERS: |
| 128 | className - Name of the applet class to create an object for |
| 129 | size - Initial size of the applet to be created |
| 130 | |
| 131 | RETURNS: |
| 132 | Pointer to the wxApplet created, or NULL if unable to create the applet. |
| 133 | |
| 134 | REMARKS: |
| 135 | This function is used to create new wxApplet objects dynamically based on the |
| 136 | class name as a string. This allows instances of wxApplet classes to be |
| 137 | created dynamically based on string values embedded in the custom tags of an |
| 138 | HTML page. |
| 139 | ****************************************************************************/ |
| 140 | wxApplet *wxHtmlAppletWindow::CreateApplet( |
| 141 | const wxString& classId, |
| 142 | const wxString& iName, |
| 143 | const wxHtmlTag& params, |
| 144 | const wxSize& size) |
| 145 | { |
| 146 | // Dynamically create the class instance at runtime |
| 147 | wxClassInfo *info = wxClassInfo::FindClass(classId.c_str()); |
| 148 | if (!info) |
| 149 | return NULL; |
| 150 | wxObject *obj = info->CreateObject(); |
| 151 | if (!obj) |
| 152 | return NULL; |
| 153 | wxApplet *applet = wxDynamicCast(obj,wxApplet); |
| 154 | if (!applet) |
| 155 | return NULL; |
| 156 | if (!applet->Create(this,params,size)) { |
| 157 | delete applet; |
| 158 | return NULL; |
| 159 | } |
| 160 | m_AppletList.Append(iName,applet); |
| 161 | return applet; |
| 162 | } |
| 163 | |
| 164 | /**************************************************************************** |
| 165 | PARAMETERS: |
| 166 | appletName - Name of the applet class to find |
| 167 | |
| 168 | RETURNS: |
| 169 | Pointer to the wxApplet found, or NULL if not found. |
| 170 | |
| 171 | REMARKS: |
| 172 | Find an instance of an applet based on it's name |
| 173 | ****************************************************************************/ |
| 174 | wxApplet *wxHtmlAppletWindow::FindApplet( |
| 175 | const wxString& appletName) |
| 176 | { |
| 177 | wxAppletList::Node *node = m_AppletList.Find(appletName); |
| 178 | if (!node) |
| 179 | return NULL; |
| 180 | return node->GetData(); |
| 181 | } |
| 182 | |
| 183 | /**************************************************************************** |
| 184 | PARAMETERS: |
| 185 | applet - Pointer to the applet object to remove from the list |
| 186 | |
| 187 | RETURNS: |
| 188 | True if the applet was found and removed, false if not. The applet itself |
| 189 | is *not* destroyed! |
| 190 | |
| 191 | REMARKS: |
| 192 | Remove an applet from the manager. Called during applet destruction |
| 193 | ****************************************************************************/ |
| 194 | bool wxHtmlAppletWindow::RemoveApplet( |
| 195 | const wxApplet *applet) |
| 196 | { |
| 197 | for (wxAppletList::Node *node = m_AppletList.GetFirst(); node; node = node->GetNext()) { |
| 198 | if (node->GetData() == applet) { |
| 199 | m_AppletList.DeleteNode(node); |
| 200 | return true; |
| 201 | } |
| 202 | } |
| 203 | return false; |
| 204 | } |
| 205 | |
| 206 | /**************************************************************************** |
| 207 | PARAMETERS: |
| 208 | URL - New URL for the page to load |
| 209 | |
| 210 | RETURNS: |
| 211 | True if page loaded successfully, false if not |
| 212 | |
| 213 | REMARKS: |
| 214 | Remove an applet from the manager. Called during applet destruction |
| 215 | ****************************************************************************/ |
| 216 | bool wxHtmlAppletWindow::LoadPage( |
| 217 | const wxString& link) |
| 218 | { |
| 219 | wxString href(link); |
| 220 | |
| 221 | // Check for abs path. If it is not then tack on the path |
| 222 | // supplied at creation. |
| 223 | if (!wxIsAbsolutePath(href)) |
| 224 | href = m_DocRoot + href; |
| 225 | |
| 226 | // TODO: This needs to be made platform inde if possible. |
| 227 | if (link.GetChar(0) == '?'){ |
| 228 | wxString cmd = link.BeforeFirst('='); |
| 229 | wxString cmdValue = link.AfterFirst('='); |
| 230 | if(!(cmd.CmpNoCase("?EXTERNAL"))){ |
| 231 | return wxSpawnBrowser(this, cmdValue.c_str()); |
| 232 | } |
| 233 | if (!(cmd.CmpNoCase("?EXECUTE"))){ |
| 234 | wxProcess *child = new AppletProcess(this); |
| 235 | wxExecute(cmdValue, false, child); |
| 236 | return true; |
| 237 | } |
| 238 | if (!(cmd.CmpNoCase("?VIRTUAL"))){ |
| 239 | VirtualData& temp = *((VirtualData*)FindCookie(cmdValue)); |
| 240 | if (&temp) { |
| 241 | href = temp.GetHref(); |
| 242 | } |
| 243 | else { |
| 244 | #ifdef CHECKED |
| 245 | wxLogError(_T("VIRTUAL LINK ERROR: '%s' does not exist."), cmdValue.c_str()); |
| 246 | #endif |
| 247 | return true; |
| 248 | } |
| 249 | } |
| 250 | } |
| 251 | |
| 252 | // Inform all the applets that the new page is being loaded |
| 253 | for (wxAppletList::Node *node = m_AppletList.GetFirst(); node; node = node->GetNext()) |
| 254 | (node->GetData())->OnLinkClicked(wxHtmlLinkInfo(href)); |
| 255 | Show(false); |
| 256 | bool stat = wxHtmlWindow::LoadPage(href); |
| 257 | Show(true); |
| 258 | |
| 259 | // Enable/Dis the navbar tools |
| 260 | if (m_NavBar) { |
| 261 | m_NavBar->EnableTool(m_NavForwardId,HistoryCanForward()); |
| 262 | m_NavBar->EnableTool(m_NavBackId,HistoryCanBack()); |
| 263 | } |
| 264 | return stat; |
| 265 | } |
| 266 | |
| 267 | /**************************************************************************** |
| 268 | PARAMETERS: |
| 269 | URL - String URL that we are navigating to |
| 270 | |
| 271 | REMARKS: |
| 272 | Called when the user navigates to a new URL from the current page. We simply |
| 273 | call the LoadPage function above to load the new page and display it. |
| 274 | ****************************************************************************/ |
| 275 | void wxHtmlAppletWindow::OnLinkClicked( |
| 276 | const wxHtmlLinkInfo& link) |
| 277 | { |
| 278 | LoadPage(link.GetHref()); |
| 279 | } |
| 280 | |
| 281 | /**************************************************************************** |
| 282 | REMARKS: |
| 283 | Called when the user navigates forward within the HTML history stack. |
| 284 | We call all the applets in turn allowing them to handle the navigation |
| 285 | command prior to being destructed when the current page is destroyed. |
| 286 | ****************************************************************************/ |
| 287 | bool wxHtmlAppletWindow::HistoryForward() |
| 288 | { |
| 289 | if (!HistoryCanForward()) |
| 290 | return false; |
| 291 | |
| 292 | for (wxAppletList::Node *node = m_AppletList.GetFirst(); node; node = node->GetNext()) |
| 293 | (node->GetData())->OnHistoryForward(); |
| 294 | |
| 295 | return wxHtmlWindow::HistoryForward(); |
| 296 | } |
| 297 | |
| 298 | /**************************************************************************** |
| 299 | REMARKS: |
| 300 | Called when the user navigates backwards within the HTML history stack. |
| 301 | We call all the applets in turn allowing them to handle the navigation |
| 302 | command prior to being destructed when the current page is destroyed. |
| 303 | ****************************************************************************/ |
| 304 | bool wxHtmlAppletWindow::HistoryBack() |
| 305 | { |
| 306 | if (!HistoryCanBack()) |
| 307 | return false; |
| 308 | |
| 309 | for (wxAppletList::Node *node = m_AppletList.GetFirst(); node; node = node->GetNext()) |
| 310 | (node->GetData())->OnHistoryBack(); |
| 311 | |
| 312 | return wxHtmlWindow::HistoryBack(); |
| 313 | } |
| 314 | |
| 315 | /**************************************************************************** |
| 316 | PARAMETERS: |
| 317 | msg - wxEvent message to be sent to all wxApplets |
| 318 | |
| 319 | REMARKS: |
| 320 | This function is called by the wxApplet's when they need to send a message |
| 321 | to all other applets on the current page. This is the primary form of |
| 322 | communication between applets on the page if they need to inform each |
| 323 | other of internal information. |
| 324 | |
| 325 | Note that the event handling terminates as soon as the first wxApplet |
| 326 | handles the event. If the event should be handled by all wxApplet's, |
| 327 | the event handlers for the applets should not reset the wxEvent::Skip() |
| 328 | value (ie: by default it is true). |
| 329 | ****************************************************************************/ |
| 330 | void wxHtmlAppletWindow::SendMessage( |
| 331 | wxEvent& msg) |
| 332 | { |
| 333 | // Preset the skip flag |
| 334 | msg.Skip(); |
| 335 | |
| 336 | // Process all applets in turn and send them the message |
| 337 | for (wxAppletList::Node *node = m_AppletList.GetFirst(); node; node = node->GetNext()) { |
| 338 | (node->GetData())->OnMessage(msg); |
| 339 | if (!msg.GetSkipped()){ |
| 340 | wxMessageBox("BREAK"); |
| 341 | break; |
| 342 | } |
| 343 | } |
| 344 | } |
| 345 | |
| 346 | /**************************************************************************** |
| 347 | PARAMETERS: |
| 348 | name - Uniq wxString used as hash key |
| 349 | cookie - wxObject data returned when name is found. |
| 350 | |
| 351 | RETURNS: |
| 352 | True if new cookie was added, false if cookie with same name already exists. |
| 353 | |
| 354 | REMARKS: |
| 355 | This function is called by the wxApplet's when they need register a cookie |
| 356 | of data in the applet window's cookie table. Cookies are arbitrary data |
| 357 | objects that are references by unique name's by the wxApplet. These |
| 358 | values can be used to store and retrieve data that needs to remain |
| 359 | persisent across invocations of the wxApplet. Ie: The first time an |
| 360 | applet is created it would use the cookie to store data to maintain |
| 361 | it's present state so that if you navigated back to the same page |
| 362 | is would be able to re-load the prior state as though the applet |
| 363 | was never actually destructed. |
| 364 | |
| 365 | Note: If a cookie with the same name already exists, this function returns |
| 366 | false. Hence if you wish to replace a cookie you should first call |
| 367 | UnRegisterCookie to ensure the cookie is deleted and then call this |
| 368 | function. |
| 369 | ****************************************************************************/ |
| 370 | bool wxHtmlAppletWindow::RegisterCookie( |
| 371 | const wxString& name, |
| 372 | wxObject *cookie) |
| 373 | { |
| 374 | // Fail if the named cookie already exists! |
| 375 | if (m_Cookies.Get(name)) |
| 376 | return false; |
| 377 | m_Cookies.Put(name,cookie); |
| 378 | return true; |
| 379 | } |
| 380 | |
| 381 | /**************************************************************************** |
| 382 | PARAMETERS: |
| 383 | name - wxString uniq haskey used to remove item from hash |
| 384 | |
| 385 | RETURNS: |
| 386 | True if found and deleted, false if not found in table. |
| 387 | |
| 388 | REMARKS: |
| 389 | This function is called by the wxApplet's when they need de-register a |
| 390 | cookie of data in the applet window's cookie table. The data in the |
| 391 | cookie itself is also deleted before it is removed from the table. |
| 392 | ****************************************************************************/ |
| 393 | bool wxHtmlAppletWindow::UnRegisterCookie( |
| 394 | const wxString& name) |
| 395 | { |
| 396 | wxObject *data = m_Cookies.Delete(name); |
| 397 | if (data) { |
| 398 | delete data; |
| 399 | return true; |
| 400 | } |
| 401 | return false; |
| 402 | } |
| 403 | |
| 404 | /**************************************************************************** |
| 405 | PARAMETERS: |
| 406 | msg - wxEvent message to be sent to all wxApplets |
| 407 | |
| 408 | RETURNS: |
| 409 | Pointer to the cookie data if found, NULL if not found. |
| 410 | |
| 411 | REMARKS: |
| 412 | This function is called by the wxApplet's when they need to find a cookie |
| 413 | of data given it's public name. If the cookie is not found, NULL is |
| 414 | returned. |
| 415 | ****************************************************************************/ |
| 416 | wxObject *wxHtmlAppletWindow::FindCookie( |
| 417 | const wxString& name) |
| 418 | { |
| 419 | return m_Cookies.Get(name); |
| 420 | } |
| 421 | |
| 422 | /**************************************************************************** |
| 423 | PARAMETERS: |
| 424 | event - Event to handle |
| 425 | |
| 426 | REMARKS: |
| 427 | This function handles delayed LoadPage events posted from applets that |
| 428 | need to change the page for the current window to a new window. |
| 429 | ****************************************************************************/ |
| 430 | void wxHtmlAppletWindow::OnLoadPage( |
| 431 | wxLoadPageEvent &event) |
| 432 | { |
| 433 | // Test the mutex lock. |
| 434 | if (!(IsLocked())){ |
| 435 | Lock(); |
| 436 | if (event.GetHtmlWindow() == this){ |
| 437 | if (LoadPage(event.GetHRef())){ |
| 438 | // wxPageLoadedEvent evt; |
| 439 | // NOTE: This is reserved for later use as we might need to send |
| 440 | // page loaded events to applets. |
| 441 | } |
| 442 | } |
| 443 | UnLock(); |
| 444 | } |
| 445 | } |
| 446 | |
| 447 | /**************************************************************************** |
| 448 | PARAMETERS: |
| 449 | event - Event to handle |
| 450 | |
| 451 | REMARKS: |
| 452 | This function handles delayed LoadPage events posted from applets that |
| 453 | need to change the page for the current window to a new window. |
| 454 | ****************************************************************************/ |
| 455 | void wxHtmlAppletWindow::OnPageLoaded( |
| 456 | wxPageLoadedEvent &) |
| 457 | { |
| 458 | Enable(true); |
| 459 | } |
| 460 | |
| 461 | /**************************************************************************** |
| 462 | PARAMETERS: |
| 463 | none |
| 464 | |
| 465 | REMARKS: |
| 466 | This function tries to lock the mutex. If it can't, returns |
| 467 | immediately with false. |
| 468 | ***************************************************************************/ |
| 469 | bool wxHtmlAppletWindow::TryLock() |
| 470 | { |
| 471 | if (!m_mutexLock) { |
| 472 | m_mutexLock = true; |
| 473 | return true; |
| 474 | } |
| 475 | return false; |
| 476 | } |
| 477 | |
| 478 | /**************************************************************************** |
| 479 | PARAMETERS: |
| 480 | name - name of the last applet that changed the data in this object |
| 481 | group - name of the group the allplet belongs to. |
| 482 | href - webpage to go to. |
| 483 | |
| 484 | REMARKS: |
| 485 | VirtualData is used to store information on the virtual links. |
| 486 | ****************************************************************************/ |
| 487 | VirtualData::VirtualData( |
| 488 | wxString& name, |
| 489 | wxString& group, |
| 490 | wxString& href ) |
| 491 | { |
| 492 | m_name = name; |
| 493 | m_group = group; |
| 494 | m_href = href; |
| 495 | } |
| 496 | |
| 497 | /**************************************************************************** |
| 498 | PARAMETERS: |
| 499 | REMARKS: |
| 500 | ****************************************************************************/ |
| 501 | void AppletProcess::OnTerminate( |
| 502 | int, |
| 503 | int ) |
| 504 | { |
| 505 | // we're not needed any more |
| 506 | delete this; |
| 507 | } |
| 508 | |
| 509 | #include "wx/html/m_templ.h" |
| 510 | |
| 511 | /**************************************************************************** |
| 512 | REMARKS: |
| 513 | Implementation for the <embed> HTML tag handler. This handler takes care |
| 514 | of automatically constructing the wxApplet objects of the appropriate |
| 515 | class based on the <embed> tag information. |
| 516 | ****************************************************************************/ |
| 517 | TAG_HANDLER_BEGIN(wxApplet, "WXAPPLET") |
| 518 | |
| 519 | TAG_HANDLER_PROC(tag) |
| 520 | { |
| 521 | wxWindow *wnd; |
| 522 | wxHtmlAppletWindow *appletWindow; |
| 523 | wxApplet *applet; |
| 524 | wxString classId; |
| 525 | wxString name; |
| 526 | int width, height; |
| 527 | |
| 528 | wnd = m_WParser->GetWindow(); |
| 529 | |
| 530 | if ((appletWindow = wxDynamicCast(wnd,wxHtmlAppletWindow)) != NULL){ |
| 531 | tag.ScanParam("WIDTH", "%i", &width); |
| 532 | tag.ScanParam("HEIGHT", "%i", &height); |
| 533 | if (tag.HasParam("CLASSID")){ |
| 534 | classId = tag.GetParam("CLASSID"); |
| 535 | if ( classId.IsNull() || classId.Len() == 0 ){ |
| 536 | wxMessageBox("wxApplet tag error: CLASSID is NULL or empty.","Error",wxICON_ERROR); |
| 537 | return false; |
| 538 | } |
| 539 | if (tag.HasParam("NAME")) |
| 540 | name = tag.GetParam("NAME"); |
| 541 | |
| 542 | // If the name is NULL or len is zero then we assume that the html guy |
| 543 | // didn't include the name param which is optional. |
| 544 | if ( name.IsNull() || name.Len() == 0 ) |
| 545 | name = classId; |
| 546 | |
| 547 | // We got all the params and can now create the applet |
| 548 | if ((applet = appletWindow->CreateApplet(classId, name, tag , wxSize(width, height))) != NULL){ |
| 549 | applet->Show(true); |
| 550 | m_WParser->OpenContainer()->InsertCell(new wxHtmlWidgetCell(applet,0)); |
| 551 | } |
| 552 | else |
| 553 | wxMessageBox("wxApplet error: Could not create:" + classId + "," + name); |
| 554 | } |
| 555 | else{ |
| 556 | wxMessageBox("wxApplet tag error: Can not find CLASSID param.","Error",wxICON_ERROR); |
| 557 | return false; |
| 558 | } |
| 559 | //Add more param parsing here. If or when spec changes. |
| 560 | //For now we'll ignore any other params those HTML guys |
| 561 | //might put in our tag. |
| 562 | } |
| 563 | |
| 564 | return false; |
| 565 | } |
| 566 | |
| 567 | TAG_HANDLER_END(wxApplet) |
| 568 | |
| 569 | TAGS_MODULE_BEGIN(wxApplet) |
| 570 | TAGS_MODULE_ADD(wxApplet) |
| 571 | TAGS_MODULE_END(wxApplet) |
| 572 | |
| 573 | // This is our little forcelink hack. |
| 574 | FORCE_LINK(loadpage) |
| 575 | |