1 /************************************************************** 2 * 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * 20 *************************************************************/ 21 22 23 24 // MARKER(update_precomp.py): autogen include statement, do not remove 25 #include "precompiled_automation.hxx" 26 27 // do not use Application Idle but AutoTimer instead 28 #define TIMERIDLE 29 30 #define NO_JPEG 31 32 #ifndef NO_JPEG 33 #include <svtools/jpeg.hxx> 34 #endif 35 #include <vcl/timer.hxx> 36 #include <vcl/wrkwin.hxx> 37 #include <osl/diagnose.h> 38 #include <osl/mutex.hxx> 39 40 #ifndef _DIALOG_HXX //autogen 41 #include <vcl/dialog.hxx> 42 #endif 43 #include <tools/stream.hxx> 44 #include <tools/config.hxx> 45 46 #include <vos/socket.hxx> 47 48 #if 1 49 #include <svtools/ttprops.hxx> 50 #include <basic/ttstrhlp.hxx> 51 #include <svl/stritem.hxx> 52 #include <svtools/stringtransfer.hxx> 53 #include <vcl/sound.hxx> 54 #include "testtool.hrc" 55 #include <vcl/bitmap.hxx> 56 // Hat keinen Includeschutz 57 #include <svtools/svtdata.hxx> 58 //#ifndef _DTRANS_HXX //autogen 59 //#include <so2/dtrans.hxx> 60 //#endif 61 #endif // 1 62 #include <rtl/textenc.h> 63 #include <rtl/uri.h> 64 #include <rtl/uri.hxx> 65 #include "statemnt.hxx" 66 #include "scmdstrm.hxx" 67 #include "rcontrol.hxx" 68 #include "server.hxx" 69 #include "testtool.hxx" 70 #include "automation/automation.hxx" 71 #include "recorder.hxx" 72 73 #include "basic/svtmsg.hrc" 74 75 #ifdef DBG_UTIL 76 void TestToolDebugPrint( const sal_Char *pString ) 77 { 78 if ( !DbgFilterMessage( pString ) ) 79 StatementList::DirectLog( S_AssertError, UniString( pString, RTL_TEXTENCODING_UTF8 ) ); 80 } 81 void SAL_CALL osl_TestToolDebugPrint( const sal_Char *pString ) 82 { 83 TestToolDebugPrint( pString ); 84 } 85 #endif 86 87 88 sal_uLong RemoteControlCommunicationManager::nPortIs = TT_PORT_NOT_INITIALIZED; 89 sal_uInt16 RemoteControlCommunicationManager::nComm = 0; 90 sal_Bool RemoteControlCommunicationManager::bQuiet = sal_False; 91 92 #if OSL_DEBUG_LEVEL > 1 93 RemoteControlCommunicationManager::RemoteControlCommunicationManager( EditWindow * pDbgWin ) 94 #else 95 RemoteControlCommunicationManager::RemoteControlCommunicationManager() 96 #endif 97 : CommunicationManagerServerViaSocket( GetPort(), 1, sal_True ) 98 #if OSL_DEBUG_LEVEL > 1 99 , m_pDbgWin( pDbgWin ) 100 #endif 101 , pTimer( NULL ) 102 { 103 bIsPortValid = ( GetPort() != 0 ); 104 if ( bQuiet ) 105 { 106 SetInfoType( CM_NO_TEXT ); 107 } 108 else 109 { 110 SetInfoType( CM_SHORT_TEXT | CM_ALL ); 111 ByteString aByteString; 112 InfoMsg( InfoString( aByteString, CM_ALL ) ); // Anzeigen, da� wir da sind 113 } 114 } 115 116 RemoteControlCommunicationManager::~RemoteControlCommunicationManager() 117 { 118 if ( pTimer ) 119 delete pTimer; 120 DoQuickShutdown(); 121 } 122 123 void RemoteControlCommunicationManager::ConnectionOpened( CommunicationLink* pCL ) 124 { 125 StatementFlow::pCommLink = pCL; 126 CommunicationManagerServerViaSocket::ConnectionOpened( pCL ); 127 } 128 129 130 void RemoteControlCommunicationManager::ConnectionClosed( CommunicationLink* pCL ) 131 { 132 StatementFlow::pCommLink = NULL; 133 CommunicationManagerServerViaSocket::ConnectionClosed( pCL ); 134 } 135 136 137 IMPL_LINK( RemoteControlCommunicationManager, SetWinCaption, Timer*, EMPTYARG ) 138 { 139 if ( pTimer ) 140 { 141 delete pTimer; 142 pTimer = NULL; 143 } 144 145 if ( StatementList::GetFirstDocFrame() ) 146 { 147 if ( !aOriginalWinCaption.Len() ) 148 aOriginalWinCaption = StatementList::GetFirstDocFrame()->GetText(); 149 StatementList::GetFirstDocFrame()->SetText(String(aOriginalWinCaption).AppendAscii(" TT").Append(aAdditionalWinCaption).AppendAscii("[").Append(UniString::CreateFromInt32(nPortToListen)).AppendAscii("]")); 150 } 151 else 152 { // Dann Probieren wir es eben in 1 Sekunde nochmal 153 pTimer = new Timer(); // Wird im Link gel�scht 154 pTimer->SetTimeout( 1000 ); 155 pTimer->SetTimeoutHdl( LINK( this, RemoteControlCommunicationManager, SetWinCaption ) ); 156 pTimer->Start(); 157 } 158 return 0; 159 } 160 161 void RemoteControlCommunicationManager::InfoMsg( InfoString aMsg ) 162 { 163 if ( !bIsPortValid ) 164 return; 165 aAdditionalWinCaption = UniString( aMsg, RTL_TEXTENCODING_ASCII_US ); 166 SetWinCaption(); 167 #if OSL_DEBUG_LEVEL > 1 168 m_pDbgWin->AddText( UniString( (ByteString)aMsg, RTL_TEXTENCODING_ASCII_US ) ); 169 m_pDbgWin->AddText( "\n" ); 170 #endif 171 } 172 173 sal_uLong RemoteControlCommunicationManager::GetPort() 174 { 175 if ( TT_PORT_NOT_INITIALIZED == nPortIs ) 176 { // Read Config 177 178 sal_uInt16 i; 179 // are we to be automated at all? 180 sal_Bool bAutomate = sal_False; 181 for ( i = 0 ; i < Application::GetCommandLineParamCount() ; i++ ) 182 { 183 if ( Application::GetCommandLineParam( i ).EqualsIgnoreCaseAscii("/enableautomation") 184 || Application::GetCommandLineParam( i ).EqualsIgnoreCaseAscii("-enableautomation")) 185 { 186 bAutomate = sal_True; 187 break; 188 } 189 } 190 // Get port from command line 191 if (bAutomate) 192 { 193 for ( i = 0 ; i < Application::GetCommandLineParamCount() ; i++ ) 194 { 195 if ( Application::GetCommandLineParam( i ).Copy(0,16).EqualsIgnoreCaseAscii("/automationport=") 196 || Application::GetCommandLineParam( i ).Copy(0,16).EqualsIgnoreCaseAscii("-automationport=")) 197 { 198 nPortIs = Application::GetCommandLineParam( i ).Copy(16).ToInt32(); 199 return nPortIs; 200 } 201 } 202 } 203 // 204 // if started within Portal determin location of testtool.ini/rc by analysing the commandline 205 // /userid:demo1[/export/home/user/demo1] 206 // -userid:demo1[/export/home/user/demo1] 207 String aIniFileDir; 208 for ( i = 0 ; i < Application::GetCommandLineParamCount() ; i++ ) 209 { 210 if ( Application::GetCommandLineParam( i ).Copy(0,8).EqualsIgnoreCaseAscii("/userid:") 211 || Application::GetCommandLineParam( i ).Copy(0,8).EqualsIgnoreCaseAscii("-userid:") ) 212 { 213 rtl::OUString aEncHome 214 = Application::GetCommandLineParam(i).GetBuffer(); 215 216 rtl::OUString aDecHome = rtl::Uri::decode(aEncHome, 217 rtl_UriDecodeWithCharset, 218 RTL_TEXTENCODING_UTF8); 219 220 aIniFileDir = aDecHome; 221 aIniFileDir.Erase( 0, aIniFileDir.Search('[')+1 ); 222 aIniFileDir.Erase( aIniFileDir.Search(']') ); 223 } 224 } 225 226 if ( ! aIniFileDir.Len() ) 227 aIniFileDir = Config::GetDefDirectory(); 228 229 Config aConf(Config::GetConfigName( aIniFileDir, CUniString("testtool") )); 230 aConf.SetGroup("Communication"); 231 232 ByteString aNoTesttoolKey( ByteString("Exclude_").Append( ByteString( Application::GetAppFileName(), RTL_TEXTENCODING_UTF8 ) ) ); 233 // -notesttool 234 for ( i = 0 ; i < Application::GetCommandLineParamCount() ; i++ ) 235 { 236 if ( Application::GetCommandLineParam( i ).CompareIgnoreCaseToAscii("-notesttool") == COMPARE_EQUAL ) 237 aConf.WriteKey( aNoTesttoolKey, "something" ); 238 } 239 240 nPortIs = aConf.ReadKey("TTPort","0").ToInt32(); 241 242 // noch pr�fen ob dieses Office getestet werden soll. 243 if ( !bAutomate || aConf.ReadKey( aNoTesttoolKey, "" ) != "" ) 244 nPortIs = 0; 245 246 nComm = (sal_uInt16)aConf.ReadKey("Comm","0").ToInt32(); 247 if ( nComm ) 248 aConf.DeleteKey("Comm"); 249 250 bQuiet = ( aConf.ReadKey("Quiet","no").CompareIgnoreCaseToAscii("yes") == COMPARE_EQUAL ); 251 } 252 return nPortIs; 253 } 254 255 #if OSL_DEBUG_LEVEL > 1 256 #define MIN_IDLE 10000 // Ruhe vor dem Sturm min 10 Sekunden 257 #else 258 #define MIN_IDLE 60000 // Ruhe vor dem Sturm min 1 Minuten 259 #endif 260 261 class ExtraIdle : public AutoTimer 262 { 263 virtual void Timeout(); 264 265 sal_uInt16 nStep; 266 ImplRemoteControl *pRemoteControl; 267 public: 268 ExtraIdle( ImplRemoteControl *pRC ); 269 }; 270 271 272 ExtraIdle::ExtraIdle( ImplRemoteControl *pRC ) 273 : nStep( 0 ) 274 , pRemoteControl (pRC ) 275 { 276 SetTimeout( 120000 ); // 2 Minuten 277 #if OSL_DEBUG_LEVEL > 1 278 SetTimeout( 40000 ); // 40 Sekunden 279 #endif 280 Start(); 281 } 282 283 void ExtraIdle::Timeout() 284 { 285 if ( !StatementList::pTTProperties ) 286 StatementList::pTTProperties = new TTProperties(); 287 288 if ( !StatementList::pTTProperties->GetSlots() ) 289 { 290 delete this; 291 return; 292 } 293 294 // M�ssen wir selbst idlen? 295 #if OSL_DEBUG_LEVEL > 1 296 sal_uLong nLastInputInterval = Application::GetLastInputInterval(); 297 sal_Bool bIsInModalMode = Application::IsInModalMode(); 298 if ( bIsInModalMode || nLastInputInterval < MIN_IDLE ) 299 #else 300 if ( Application::IsInModalMode() || Application::GetLastInputInterval() < MIN_IDLE ) 301 #endif 302 { 303 if ( nStep ) // Schon angefangen? dann abbrechen, sonst sp�ter nochmal 304 { 305 if ( nStep < 15 ) 306 { 307 Sound::Beep(); 308 Sound::Beep(); 309 } 310 #if OSL_DEBUG_LEVEL < 2 311 delete this; 312 #endif 313 } 314 #if OSL_DEBUG_LEVEL > 1 315 if ( nStep < 15 ) 316 { 317 Sound::Beep(); 318 Sound::Beep(); 319 } 320 #endif 321 return; 322 } 323 324 if ( StatementList::pFirst ) // Verarbeitung neu aufsetzen 325 { 326 GetpApp()->PostUserEvent( LINK( pRemoteControl, ImplRemoteControl, CommandHdl ) ); 327 return; 328 } 329 330 331 switch ( nStep++ ) // Probieren ob wir noch was machen k�nnen 332 { 333 case 0: 334 { 335 SfxPoolItem *pItem = new SfxStringItem((sal_uInt16)StatementList::pTTProperties->nSidNewDocDirect, CUniString("swriter/web") ); 336 new StatementSlot( StatementList::pTTProperties->nSidNewDocDirect, pItem ); 337 SetTimeout(30000); 338 return; 339 } 340 case 1: 341 { 342 new StatementSlot( StatementList::pTTProperties->nSidSourceView ); 343 #if OSL_DEBUG_LEVEL > 1 344 SetTimeout(7000); 345 #else 346 SetTimeout(1500); 347 #endif 348 return; 349 } 350 case 2: 351 { 352 new StatementSlot( StatementList::pTTProperties->nSidSelectAll ); 353 return; 354 } 355 case 3: 356 { 357 358 #if OSL_DEBUG_LEVEL > 1 359 //#define TT_NO_DECRYPT 360 #define TT_CODE 361 #else 362 #define TT_CODE 363 #endif 364 365 #ifdef TT_NO_DECRYPT 366 String aStr = 367 "" 368 ; 369 370 #else 371 ByteString aStr = 372 "\n" 373 "VRQJ`ob\n" 374 "YEZO\n" 375 "ob\n" 376 "UmRo`\n" 377 "5J~O2o5+90~5,6xW$+5:c9o0UXRm`Y UQ~JP~X]`Y\\|%Y`Yo]~O||2[pP0Y1J,|V),,7:,+|JS+U*[/O|K\n" 378 "|KaLYNV~]]2W/]*Y9|`*Y,P=[5P|U\n" 379 "]}mqbw`zZU\\L\n" 380 "LZdYWo9\n" 381 "/J\n" 382 "U~[QoZ\n" 383 "Rqd~V\n" 384 ",)1~00\n" 385 "\n" 386 ")0~*2=\n" 387 "++2\\5&K|~5n9r~9/*9<*~051*Q|0~0rY|~./97~Q*7,Z9<|KY0:=K*<=w~qY`IbOKzLwN,`7b,V~]E`]b\\ORE~\n" 388 "\n" 389 "Vq~bR`W;a+Y\\J=LKJa+W*I/PbR~JLUX[|b~`Z2P/R*[9a~W=9~/9p8=a*P=J0OZ~7L`JbL=P<WbaLQbPO]JYKbD\n" 390 "aY`J5J:b~7=2~+9)9W1,50b9X3P0`YbYVJ`Jb \\`Z]`Vb\n" 391 "VRQJ`b" 392 ; 393 #endif 394 395 #ifdef TT_CODE 396 for ( sal_uInt16 i = 0 ; i < aStr.Len() ; i++ ) 397 { 398 if ( aStr.GetChar(i) < 32 || aStr.GetChar(i) > 126 ) 399 { 400 // do nothing 401 } 402 else 403 { 404 aStr.SetChar( i, aStr.GetChar(i) - 32 ); 405 aStr.SetChar( i, 126 - aStr.GetChar(i) ); 406 } 407 408 if ( i > (aStr.Len() / 2) && (i&1) ) 409 { 410 sal_Char c = aStr.GetChar(i); 411 aStr.SetChar( i, aStr.GetChar(aStr.Len()-i-1) ); 412 aStr.SetChar( aStr.Len()-i-1, c ); 413 } 414 } 415 #endif 416 417 ::svt::OStringTransfer::CopyString( UniString( aStr, RTL_TEXTENCODING_ASCII_US ), StatementList::GetFirstDocFrame() ); 418 419 new StatementSlot( StatementList::pTTProperties->nSidPaste ); 420 return; 421 } 422 case 4: 423 { 424 new StatementSlot( StatementList::pTTProperties->nSidSourceView ); 425 return; 426 } 427 case 5: 428 { 429 new StatementSlot( StatementList::pTTProperties->nSidSelectAll ); 430 new StatementSlot( StatementList::pTTProperties->nSidCopy ); 431 new StatementSlot( StatementList::pTTProperties->nSidPaste ); 432 return; 433 } 434 case 6: 435 { 436 ByteString aTr("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+-"); 437 ByteString aData = 438 "P-S-0U04Fihixh00l0004b0b300-PS0g30428333y243q334j44426a6a65576c8k97aJecf7feccedg2inj3ghlshde5krk+lno" 439 "PpqpBfjsgusp1unp-po-PS0gm044x465e6b6L6boygeg-ooo-ooo-ooo-ooo-ooo-ooo-ooo-ooo-ooo-ooo-ooo-ooo-ooo-ooo" 440 "-ooo-ooo-oo-1M04020Y30J0o080B040R040M-N0M700l010l000k000000000006000N011I112r222M-N0gJ40D000U001R011" 441 "0110500vr0001014p148mcg1R4koV18s95cwkAE2V8gImM5kgQY9WcosCw22I556p669I99aoaadrddd6eeeNghhIhhiriik6lll" 442 "NlmmImoprppp6qqqNsttItturuuw6xxxNxyyHyAA6BBBNBCCHCEE6FFFNFGGHGII6JJJNJKKHKMM6NNNNNOOHOQQ6RRRNRSSCSUU" 443 "NUVVIVVWpWWYIYYZrZZZ6+++M-N0Q700R000l000l000g00000006000N011I112r222M-N0kJ40C0003110d1110110r00t6000" 444 "Q041l18cF14gtk1ous48Acw295gAlEIMv28cxkY5FosQE2595dU9sY56q669N9aaCaddNdeeIeghrhhh6iiiNkllIllmrmmo6ppp" 445 "NpqqIqstrttt6uuuIwwxrxxx6yyyIAABrBBB6CCCIEEFrFFF6GGGIIIJrJJJ6KKKIMMNrNNN6OOOIQQRrRRR6SSSIUUVrVVV6WWW" 446 "IYYZrZZZ6+++U-S0d3009004Q040Of0TPU5QGjFCdPoji85WiqEopkCag321kP8dW4yO4KRlNi9iwzeTKup+Yk0lrdcicCEeLtVQ" 447 "z1IFeROmSJBa7VYMYY-0EWGkJWH6LpAVdrUepM7ScEpkTBkenX3YGuoFVU0IGk+dSzPpv0N6U07eTPFgid-YtvOD2ws5C96qDgIL" 448 "vhsoWmBPAozc+KgPjiVuW0TJnrt6PqF63p2VJEJ6A+l33JqESWh0G4yn1JkcaaEBnw17xmaf0q4BGkVy40Jj+FAyioG3KEukCtP1" 449 "OAdOe4ASVCPuUrQDFsqBoRWN6jqxOBfH-30WbgyZy+HtyI6xNVvt3M0lnfscjA8rBUeoRXifTPCceY6t46AR9ooG2jVzdmo+PQ6R" 450 "cAEDd7VE3GvUyDJzn2e0yyzypEdnCzUZorT029pk4LHJYsRQmR5smaW9EuCbt2A2s2Nd9ZKAkcJSWoTGPV5p6d1PZCiYt6kVETBB" 451 "K7zNWhRK7kMBCag7zELQ2e6HWHM+BwO4nJA-30uF2a2WgcgndWuk6gPbha0D5WFPq902KmjNwyg5xkVQvgd9W9SCfiFd95Ndh9yj" 452 "Odd7k38da3xWqtwcHPOEb7AvIPqAdRbz3XNNEYFu7bS9Iz-0UVQJc-gtgPCQ7cledmoGTULsGpjeu0TzkJi2tusMDnR4cisDw2rz" 453 "Vhs36hPC0oSH7V-UMAjVIC3dRFwNoc20a0+Culnm3q9QQJsgt00IeEoRXCh3jUg3eO8yGBOpFwYap5OrpoAfMeR6Q8L0sUIgI7B3" 454 "Oy9q5WMBAxg5PYnBSxZlywhwDlb45Il6Y+F-NaH62MEoByaq02d2aaEz5Bwx45DqfEC4ACqd4FYjI9IbAgqH7uFopm+JQRSHrSNd" 455 "ct0dwNo+FAUaD926b3wtUoRIPJ-MTLLiQcC92bTBue9RkDqqYRcXxn06S9Jm6Qhpk9IjH8JLyIinJj3EAF7bTH9jkf170OvzuO2j" 456 "I2jenHhQvnKoDSHSmWenEhfEHkVgekpfIOhkBhqLVaEvb83EyfD2Awrbk5+lwyvOne6yBA36rdrmna4xFOsvqGxRcgcJy-lXnjCn" 457 "eeWhGvqAbmSf7LcDwqykK9jqADpRqkXSq7MB7ZOHSgJhNitiw3i6y9LYjRNlq4Lc-00zCNL3CThC65Ajjlw8550bAbqa0d0Jz3BT" 458 "kH6EDgQhRUhjtyK9y9CjraNEw9ERUq6MmYa989nsRqsPxo+zi2IbOfxy9q3lFL-QSWn5qwp7nTFUwVe-XaDxnGfWOIYXXfIkILs-" 459 "lWPSm51tjj967w11u-YylxUO++EfuLsmr1c3jLdgcDYmK9roIAmz1t1vAalje3oyXDp335xkQ24rS1JhdokOn5cWpizqiE5bsbg4" 460 "4gWkfJ2IEVnSoWmj8eNeAFuoT0wzWWm9UgmDKRH2INGJy6OHTwn7zawsiPo796yQd6OsPORlTrUR-bEMLPj8nZdMwyX-Jb8npd2-" 461 "zV9JMRdNarUy1aF0tiihB0o+kQh5iy9r9BMqhPjf+WckJ9WWqmSQTEqAl+zwgw-+vH5WomSNVjbDLchO9Ae-ggdQGPcb+7Dq0X-d" 462 "XsFHj76-a0eUqKlN6cgHMKgKSmv8xcMVnCIPAnqR0SsThTWe8GSgo3pTGWTgBrtb1X2OfHMHsi8D3gkpPwKvoxoEuSJcTmD2kiAS" 463 "Pk3wl5C5NZDe9OrZMdDg6VQpDybXJ7EWLCdwsPoTGqhcGOGvrJ2WgFuuem+wP1ZGhkpee9rU7CTad9q9DxVgNzGWk+lGid6rKswa" 464 "1+Uc57RmFASpo3qbaGvuMReTLCWXsocM6lvXmSZHAhhaaV7EHH9sJglnrUlniII4I0gVZHFLys8VKKb2yKbAYHeSY3VlmgRywmqd" 465 "UXugq90wSsh0poya0qEAF9CjjadQumckue1unyK1sdcUwyxQOcARlHjLWYd3lS2ozCTQ48zZXesU66bAUfTdoXoOInm7MpPgwiDp" 466 "XDqJrEMEChxb747KzIHfxSdi++EwdRNK7RHEmgVhqiZkW1WqBEnjst6Oz08ztIPVknfPjq8NDB4h9g1sD+l1xQNzHNg+Jb1Vmii6" 467 "1dP-57LPdOhlWSTKYaCmzwAhGqyOlPrY9zXZodpZuoL2kjTBLBxaeGcM+NONZcN7GqIqFcNlhVgMXKHsd-WEBBR957ZZn7hk-mbb" 468 "FGxWLzaiHE6t48mXupNDlxi6d1w-yaPlmczA0gTsEhqRrsEbj48ProNvyivlaY06bdYSvGN7IOBc1ezBJiFd5OTz+RbzIsqJpCsJ" 469 "BOTSLjAdwXCzq-XExGbygb3X2oURVXxTB4q0e6euBRnXkIJuTM7SfQfQkdEEjN7J56t3oxP6B0cA4lgSDhURzsDzrkk0ECxfwaU3" 470 "ovagJuvzx07aksPdxkQ8aqEy618F-4wjCr3hZq8gq3gu7RJ4ovXa86R7ZskSYJC01o2OpfvJh0WqpYiIuE0zBqpI3kTJQZ0Or5ku" 471 "9RzhbzbV1AU0BzJ5vPTOBRIOIAiJiBiOdI8fR3dcWle3xCder+W6QELyr6NaldJipQCeAMwRr5mpzZESGAhuU3BDdkCh5ENgMUE-" 472 "sWotoCfnOwT7tJlXLHODk8K7Z4zYCG9Dh2fQazDE0JqBDruomfatotGADn25BCDpk6GI6SSftpUd71Qr1JBrgOr33aWswl983Uk7" 473 "cq9Em7vGtACekHlvOOVJfbdh76nNHzuQ1Z1oBvuU9l-dAg+-QWWFQ18D8U+zmYn1jypyarIXSrcIb67wLDTFXWm8F9XPmFWRBD3d" 474 "WukVJwhGNV5ZHVE1wCudY07ZIEAd1kgzgPcRSxFhRhFpXsnESjJhUNCA3DlrARwzz+llg0xpVHrJiddYT36P453qxpOmIE9e6-qJ" 475 "h4ipfTTt8f2Kq4mdWniErPtI+wrN-edvCQFtPdrL+tpV6EpPRXgmHnjRhV0eWWzqxdRZacX98CME3pvwDYWkO8TOUlcNQSKTU1iF" 476 "FC9WIBA8PulsCFVNH1qJwZxYYcaX6CGNnR7vHiIBDsTE51J4b4fYucNYFG9V5mCUdrJT57tHk9eghSOfgeHZDxuvQt8619pwKma7" 477 "3Nl00EFklZOk+APRmKviL+iyiG1sWfA3E0xUPznlQgvsHJRzD9u0TzHsB6tIMKLmOxvVHG9knpHerjAXNqIp7jwZzvYXIyW8kw8g" 478 "3ycECFaB2Y2U0l00NE7l2Aca2y5uhk+QJygN0857SQMVSEXjy+Q84nQjkTh1GAtFACtdHRhwQ6FhQMLjFu6zyxuFycbQA7qNSsiy" 479 "90wlAaUBBtFhxMV0TPd8DbVScjJoMSAYMh6GhAHnKOZsbdqvwtHKdZWZ9HQbdmVOt0xnnK5Ju9KfwhuHMZIoPt73BqspII6qBobB" 480 "5kfcwm183j4fwapcs50EoGgz2UZGuK88agfskePeYt9DOQD3qxxfuJ5lZUFHa8aqFJIT6MG2Kwtwuu0zBqTz8x5DYM7PDh29F9FU" 481 "1ge-wqqIMqmXlpbO65sila1be1yRGABAbw2njF5txZEAaqEyEo9FUPqnKQ4y1NQqSXkCpsqpO06UUCyBBzaDjawwoHkKOT1-zqpz" 482 "FU7JNudONE3fuYk83U9thALoAIeG6FKizOLgU4AcDcszCmGZgylUI-Edd9mAKL9nJe+YdiYxl7uX4mATdO30KcuDrRoTxBbiHbuA" 483 "qlorQn1D0opRuIhzVLm8+z8QRFlNA0683M1QYE+Lhka+kaIDvE8RHQHel4bOsMFp6lmV6D3cNhQvpG1sECm02a5tgF52reEBaYEw" 484 "OhD+RQiFedTm3OQg5iq2c04kidOoDgaPNGs1VitbrhIvAuzStaWksap3jp9UrAN1O-0nAECIfSP0QHVkGWtduz6XSmJ7MsLPmPJ3" 485 "hRjY7DtZXWjvtHcj9ooAXcPsI+3YgG951n7urnyB1kbQV+ZdlAbI11Y3orBMB+le8goi66fWyEX9FHpFEL32jNqSghzvyEC1227-" 486 "p5t8vx19mYHbOghy5K7voWUAXsjX2gwzicmKiNJR9OrHppAbVEVzVjOuYWmwCpGWFW1DlaoOc03PWkgqvVeezQY8IiM9Rptnniwf" 487 "Xa1XnMPo6ES0MHE5nwC8tT65VVw3C2peCu720i6oVvevcoMGeP3PVgvBkudifs0GNH7AaOGVFhrbE68B8sq6AH8BFvXhZfzdhb1f" 488 "Y1p-GVyr3qECy393zFEq0wHg2Vls4OiVD-J0d7JFKsuhUPgdykTCWhbqkdvwUUyg7qXPvdeC09AUAszRcVsk5iihIr1+N-0ATkGU" 489 "i6GPwTlzw-dALNmjbVjHOSAsWaihe303RxAmD4akSPWkjgtot17BTZfaSgaNH+ESoUGJ3GgPJqD8UBsAShIF-X0wwyFpDkTwESHg" 490 "jNwUF9EpszCwj1myzqZG9hIp76G1ymz7BuZF0T5pdA1GMG8AGuRbXEtJMkHsDJoztG06Jqm-khFPydXg-VB1k+l9AMwzzvtCDacK" 491 "k22WU1fByYcDpmW0Y9YF-zeZDDcQJVF8tT8cNNjt9GdIF3103ZFP8oulWCfnXETCKz3YQFsm3qOUu6GJ-lb2foo1WJqGpcCbyPmy" 492 "Ib95rQLJnk56YC1KmN5zMJ831cVsERyvdPOSW8kg-2uk8m3J4zgAWAhvvBOofIjFb5yNf0shVv-JJ9f49ZFcQ+LKDFKX3iNV1E-G" 493 "MxeEwbi-uGP8BGO4vGgV0IFbgswumfhk14OF3q+1qwRFpq4hr1s6zQEAgoVAW3QE4tsQpYW3JkcqDcnSOjbePZeFrFMor-o3UG2F" 494 "jmw8667eXk3UiM9vq5EpyrbQxexsJ3tKy7w6lGsumfMWIlcSglkLUzicysuPgqT5Wuzn8MkGvTYve2UyunErUnD-+Qwr0rDo1tOG" 495 "bbtcNNeFInx5rDK3DHahjTON3d3oTpePxioVK3sRLDh185yKMzTQv812ADCFcwvFHbetPF41f7kot00O2OMUkw4OPvuTRkhdAhgd" 496 "il2SM9bunNaNHqh9Ov8Qv3SKEl1O-BwzjYF0VWjkxycswQFqQotUPw+Q-6FrCPFWvaF2CP2F319stMfD-8bHsd87KZfQ9ChereG4" 497 "Z8XP8dNMipn-evkOVVFqfgN16dO8Ya9nqGFIpIW1Ljv7wOAzdZFsm5C1EuQoKzwyXDO0BDjceBsyTt40H0upG8D1N1ZP66OPIeQy" 498 "oXQwI63e+NnuYA0687-d6N6rDscj+VHn2R0RUXQFZ2+EANqcqvan4y0Erpl01fAfmLaI8pmOgsRUDvuF5e9YnWNhxtSzS4fsjj1J" 499 "1EIGpcw0WfiaOul1s19ZIECoLBx-#S"; 500 501 502 //#if OSL_DEBUG_LEVEL > 1 503 // SvFileStream aStream( "d:\\gh_writeback.jpg" , STREAM_STD_READWRITE | STREAM_TRUNC ); 504 //#else 505 SvMemoryStream aStream; 506 //#endif 507 xub_StrLen c; 508 xub_StrLen cRest = 0; 509 510 xub_StrLen nIndex; 511 for ( nIndex = 0 ; nIndex < aData.Len() ; nIndex++ ) 512 { 513 if ( ( nIndex & 3 ) == 0 ) 514 { 515 cRest = aData.GetChar( nIndex ); 516 cRest = aTr.Search( (sal_Char)cRest ); 517 } 518 else 519 { 520 c = aData.GetChar( nIndex ); 521 c = aTr.Search( (sal_Char)c ); 522 523 c <<= 2; 524 c |= ( ( cRest & 0x30 ) >> 4 ); 525 cRest <<= 2; 526 527 aStream << sal_Char(c); 528 } 529 } 530 531 aStream.Seek(0); 532 #ifndef NO_JPEG 533 Graphic aGraphic; 534 if ( ImportJPEG( aStream, aGraphic, NULL ) ) 535 { 536 Bitmap *pBmp = new Bitmap( aGraphic.GetBitmap() ); 537 StatementList::pTTProperties->Img( pBmp ); 538 delete pBmp; 539 } 540 else 541 #endif 542 { 543 ::svt::OStringTransfer::CopyString( CUniString("\nSorry! no bitmap"), StatementList::GetFirstDocFrame() ); 544 } 545 546 /*********************************************************************** 547 // sal_uInt16 nBC = pBmp->GetBitCount(); 548 // pBmp->Scale( 0.02, 0.02 ); 549 // nBC = pBmp->GetBitCount(); 550 // SvMemoryStream aStream; 551 SvFileStream aStream( "d:\gh_small50.jpg", STREAM_STD_READ ); 552 553 aStream.Seek( 0 ); 554 xub_StrLen c; 555 String aOut; 556 String aDreierGruppe; 557 xub_StrLen cRest=0; 558 aStream >> c; 559 while ( !aStream.IsEof() ) 560 { 561 cRest <<= 2; // Im ersten Durchgang egal, da immer 0 562 cRest |= ( c & 0x03 ); 563 c >>= 2; 564 aDreierGruppe += aTr.GetChar( c ); 565 566 if ( aDreierGruppe.Len() == 3 ) 567 { 568 aOut += aTr.GetChar( cRest ); 569 aOut += aDreierGruppe; 570 cRest = 0; 571 aDreierGruppe = ""; 572 } 573 aStream >> c; 574 } 575 if ( aDreierGruppe.Len() ) 576 { 577 aOut += cRest; 578 aOut += aDreierGruppe; 579 } 580 ::svt::OStringTransfer::CopyString( aOut ); 581 **********************************************************************************/ 582 583 new StatementSlot( StatementList::pTTProperties->nSidPaste ); 584 return; 585 } 586 case 7: 587 { 588 new StatementSlot( 20384 ); // FN_TOOL_ANKER_CHAR aus SW? 589 return; 590 } 591 } 592 593 // Wir sind am Ende 594 595 #if OSL_DEBUG_LEVEL < 2 596 delete this; 597 #endif 598 } 599 600 IMPL_LINK( ImplRemoteControl, IdleHdl, Application*, EMPTYARG ) 601 { 602 if( StatementList::pFirst ) 603 { 604 #if OSL_DEBUG_LEVEL > 1 605 m_pDbgWin->AddText( "* " ); 606 #endif 607 GetpApp()->PostUserEvent( LINK( this, ImplRemoteControl, CommandHdl ) ); 608 } 609 return 0; 610 } 611 612 613 614 IMPL_LINK( ImplRemoteControl, CommandHdl, Application*, EMPTYARG ) 615 { 616 #if OSL_DEBUG_LEVEL > 1 617 m_pDbgWin->AddText( "Entering CommandHdl\n" ); 618 #endif 619 620 if ( StatementList::MaybeResetSafeReschedule() ) 621 { 622 StatementList::bExecuting = sal_False; // Wird nacher im SafeReschedule wieder zur�ckgesetzt 623 #if OSL_DEBUG_LEVEL > 1 624 m_pDbgWin->AddText( "SafeReschedule has been reset\n" ); 625 #endif 626 } 627 628 if ( ( StatementList::bReadingCommands && !StatementList::bDying ) || 629 ( StatementList::bExecuting ) || 630 ( StatementList::IsInReschedule() ) ) 631 { 632 #if OSL_DEBUG_LEVEL > 1 633 if ( StatementList::bReadingCommands ) 634 m_pDbgWin->AddText( "Reading Commands " ); 635 if ( StatementList::bExecuting ) 636 m_pDbgWin->AddText( "In Execute " ); 637 if ( StatementList::IsInReschedule() ) 638 { 639 m_pDbgWin->AddText( "In Reschedule FocusWindow: 0x" ); 640 m_pDbgWin->AddText( 641 String::CreateFromInt64( 642 sal::static_int_cast< sal_Int64 >( 643 reinterpret_cast< sal_IntPtr >(GetpApp()->GetFocusWindow())), 644 16 )); 645 m_pDbgWin->AddText( " " ); 646 } 647 m_pDbgWin->AddText( "Leaving CommandHdl\n" ); 648 #endif 649 return 0; // Garnicht erst irgendwelchen bl�dsinn machen 650 } 651 652 while( StatementList::pFirst && ( !StatementList::bReadingCommands || StatementList::bDying ) ) 653 // Schleift hier bis Befehl nicht zur�ckkommt, 654 // Wird dann rekursiv �ber IdleHdl und PostUserEvent aufgerufen. 655 { 656 m_bInsideExecutionLoop = sal_True; 657 #ifdef TIMERIDLE 658 m_aIdleTimer.Stop(); 659 m_aIdleTimer.Start(); 660 #endif 661 StatementList *pC = StatementList::pFirst; 662 663 // MessBox MB( pMainWin, WB_DEF_OK|WB_OK, "Pause ...", "... und Weiter" ); 664 // MB.Execute(); 665 666 if ( !StatementList::bCatchGPF ) 667 { 668 if (!pC->CheckWindowWait() || !pC->Execute()) 669 { 670 #if OSL_DEBUG_LEVEL > 1 671 m_pDbgWin->AddText( "Leaving CommandHdl\n" ); 672 #endif 673 return 0; // So dass die App nochmal �ne chance bekommt 674 } 675 } 676 else 677 { 678 try 679 { 680 if (!pC->CheckWindowWait() || !pC->Execute()) 681 { 682 #if OSL_DEBUG_LEVEL > 1 683 m_pDbgWin->AddText( "Leaving CommandHdl\n" ); 684 #endif 685 return 0; // So dass die App nochmal �ne chance bekommt 686 } 687 } 688 catch( ... ) 689 { 690 if ( !StatementFlow::bUseIPC ) 691 throw; // aus der Hilfe heraus nicht leise abbrechen 692 693 try 694 { 695 ModelessDialog *pDlg = new ModelessDialog(NULL); 696 pDlg->SetOutputSizePixel(Size(150,0)); 697 pDlg->SetText( String ( TTProperties::GetSvtResId( TT_GPF ) ) ); 698 pDlg->Show(); 699 DBG_ERROR("GPF"); 700 pC->ReportError( GEN_RES_STR0( S_GPF_ABORT ) ); 701 StatementList::bDying = sal_True; 702 while ( StatementList::pFirst ) // Kommandos werden �bersprungen 703 StatementList::NormalReschedule(); 704 delete pDlg; 705 } 706 catch ( ... ) 707 { 708 Application::Quit(); 709 } 710 Application::Quit(); 711 } 712 } 713 714 /* #i46293# remove reschedules 715 for (int xx = 1;xx < 20;xx++) 716 StatementList::NormalReschedule(); 717 */ 718 m_bInsideExecutionLoop = sal_False; 719 } 720 721 StatementList::aWindowWaitUId = rtl::OString(); // Warten r�cksetzen, da handler sowieso verlassen wird 722 723 /* if( StatementList::pFirst && !StatementList::bReadingCommands ) 724 // Abfrage n�tig, da andere CommandHdl aktiv sein k�nnen oder 725 // neue Commands gelesen werden k�nnen 726 { 727 delete StatementList::pFirst; // L�scht die gesamte Liste !! 728 StatementList::pFirst = NULL; 729 StatementList::pCurrent = NULL; // Nur zur Sicherheit, sollte hier sowieso NULL sein 730 }*/ 731 732 #if OSL_DEBUG_LEVEL > 1 733 m_pDbgWin->AddText( "Leaving CommandHdl\n" ); 734 #endif 735 return 0; 736 } 737 738 IMPL_LINK( ImplRemoteControl, QueCommandsEvent, CommunicationLink*, pCL ) 739 { 740 SvStream *pTemp = pCL->GetServiceData(); 741 QueCommands( SI_IPCCommandBlock, pTemp ); 742 delete pTemp; 743 return 0; 744 } 745 746 sal_Bool ImplRemoteControl::QueCommands( sal_uLong nServiceId, SvStream *pIn ) 747 { 748 // return sal_True; 749 sal_uInt16 nId; 750 751 if( !m_bIdleInserted ) 752 { 753 #ifdef TIMERIDLE 754 m_aIdleTimer.SetTimeoutHdl( LINK( this, ImplRemoteControl, IdleHdl ) ); 755 m_aIdleTimer.SetTimeout( 500 ); 756 m_aIdleTimer.Start(); 757 #else 758 GetpApp()->InsertIdleHdl( LINK( this, ImplRemoteControl, IdleHdl ), 1 ); 759 #endif 760 m_bIdleInserted = sal_True; 761 } 762 763 764 StatementList::bReadingCommands = sal_True; 765 766 #if OSL_DEBUG_LEVEL > 1 767 if (!m_pDbgWin->bQuiet) 768 m_pDbgWin->Show(); 769 m_pDbgWin->AddText( "Reading " ); 770 m_pDbgWin->AddText( String::CreateFromInt64( nServiceId ) ); 771 m_pDbgWin->AddText( " :\n" ); 772 #endif 773 774 if( nServiceId != SI_IPCCommandBlock && nServiceId != SI_DirectCommandBlock ) 775 { 776 DBG_ERROR1( "Ung�ltiger Request :%i", (int)nServiceId ); 777 return sal_False; 778 } 779 780 SCmdStream *pCmdStream = new SCmdStream(pIn); 781 782 pCmdStream->Read( nId ); 783 while( !pIn->IsEof() ) 784 { 785 switch( nId ) 786 { 787 case SICommand: 788 { 789 new StatementCommand( pCmdStream ); // Wird im Konstruktor an Liste angeh�ngt 790 break; 791 } 792 case SIControl: 793 case SIStringControl: 794 { 795 new StatementControl( pCmdStream, nId ); // Wird im Konstruktor an Liste angeh�ngt 796 break; 797 } 798 case SISlot: 799 { 800 new StatementSlot( pCmdStream ); // Wird im Konstruktor an Liste angeh�ngt 801 break; 802 } 803 case SIUnoSlot: 804 { 805 new StatementUnoSlot( pCmdStream ); // Wird im Konstruktor an Liste angeh�ngt 806 break; 807 } 808 case SIFlow: 809 { 810 new StatementFlow( nServiceId, pCmdStream, this ); // Wird im Konstruktor an Liste angeh�ngt 811 break; 812 } 813 default: 814 DBG_ERROR1( "Unbekannter Request Nr:%i", nId ); 815 break; 816 } 817 if( !pIn->IsEof() ) 818 pCmdStream->Read( nId ); 819 else { 820 DBG_ERROR( "truncated input stream" ); 821 } 822 } 823 824 StatementList::bReadingCommands = sal_False; 825 826 delete pCmdStream; 827 #if OSL_DEBUG_LEVEL > 1 828 m_pDbgWin->AddText( "Done Reading " ); 829 m_pDbgWin->AddText( String::CreateFromInt64( nServiceId ) ); 830 m_pDbgWin->AddText( " :\n" ); 831 #endif 832 if ( !m_bInsideExecutionLoop ) 833 { 834 #ifdef DEBUG 835 m_pDbgWin->AddText( "Posting Event for CommandHdl.\n" ); 836 #endif 837 838 GetpApp()->PostUserEvent( LINK( this, ImplRemoteControl, CommandHdl ) ); 839 } 840 #ifdef DEBUG 841 else 842 m_bInsideExecutionLoop = sal_True; 843 #endif 844 return sal_True; 845 } // sal_Bool ImplRemoteControl::QueCommands( sal_uLong nServiceId, SvStream *pIn ) 846 847 848 SvStream* ImplRemoteControl::GetReturnStream() 849 { 850 SvStream* pTemp = pRetStream; 851 pRetStream = NULL; 852 return pTemp; 853 } 854 855 ImplRemoteControl::ImplRemoteControl() 856 : m_bIdleInserted( sal_False ) 857 , m_bInsideExecutionLoop( sal_False ) 858 #if OSL_DEBUG_LEVEL > 1 859 , m_pDbgWin(NULL) 860 #endif 861 , pRetStream(NULL) 862 { 863 #if OSL_DEBUG_LEVEL > 1 864 if ( RemoteControlCommunicationManager::GetPort() != TT_NO_PORT_DEFINED || RemoteControlCommunicationManager::nComm ) 865 { 866 m_pDbgWin = new EditWindow( NULL, CUniString("Debug Window"), WB_VSCROLL ); 867 m_pDbgWin->bQuiet = sal_True; 868 m_pDbgWin->Hide(); 869 m_pDbgWin->bQuiet = sal_False; 870 m_pDbgWin->Show(); 871 872 StatementList::m_pDbgWin = m_pDbgWin; 873 } 874 #endif 875 if ( RemoteControlCommunicationManager::GetPort() == TT_NO_PORT_DEFINED ) 876 pServiceMgr = NULL; 877 else 878 { 879 #if OSL_DEBUG_LEVEL > 1 880 pServiceMgr = new RemoteControlCommunicationManager( m_pDbgWin ); 881 #else 882 pServiceMgr = new RemoteControlCommunicationManager(); 883 #endif 884 pServiceMgr->SetDataReceivedHdl( LINK( this, ImplRemoteControl, QueCommandsEvent ) ); 885 pServiceMgr->StartCommunication(); 886 887 #ifdef DBG_UTIL 888 DbgSetPrintTestTool( TestToolDebugPrint ); 889 // first change it, so we get the original Pointer 890 StatementCommand::pOriginal_osl_DebugMessageFunc = osl_setDebugMessageFunc( osl_TestToolDebugPrint ); 891 if ( DbgGetErrorOut() != DBG_OUT_TESTTOOL ) 892 osl_setDebugMessageFunc( StatementCommand::pOriginal_osl_DebugMessageFunc ); 893 #endif 894 } 895 if ( RemoteControlCommunicationManager::nComm ) 896 new ExtraIdle( this ); // Setzt die Bearbeitung wieder auf 897 } 898 899 ImplRemoteControl::~ImplRemoteControl() 900 { 901 if ( MacroRecorder::HasMacroRecorder() ) 902 MacroRecorder::GetMacroRecorder()->SetActionRecord( sal_False ); // Will delete MacroRecorder if necessary 903 904 905 StatementList::bDying = sal_True; 906 #if OSL_DEBUG_LEVEL > 1 907 if ( m_pDbgWin ) 908 m_pDbgWin->bQuiet = sal_True; // Keine Ausgabe mehr im Debugwindow 909 #endif 910 911 #ifdef DBG_UTIL 912 // Zur�cksetzen, so da� nachfolgende Assertions nicht verloren gehen 913 DbgSetPrintTestTool( NULL ); 914 osl_setDebugMessageFunc( StatementCommand::pOriginal_osl_DebugMessageFunc ); 915 #endif 916 917 if ( StatementList::pFirst ) 918 { // Es sind noch Kommandos da, also auch eine M�glichkeit zur�ckzusenden. 919 StatementList::pFirst->ReportError( GEN_RES_STR0( S_APP_SHUTDOWN ) ); 920 while ( StatementList::pFirst ) // Kommandos werden �bersprungen 921 StatementList::NormalReschedule(); // Fehler zur�ckgeschickt 922 } 923 924 if ( pServiceMgr ) 925 pServiceMgr->StopCommunication(); 926 927 if ( GetTTSettings()->pDisplayHidWin ) 928 { 929 delete (Window*)(GetTTSettings()->pDisplayHidWin); 930 GetTTSettings()->pDisplayHidWin = NULL; 931 } 932 if ( GetTTSettings()->pTranslateWin ) 933 { 934 delete (Window*)(GetTTSettings()->pTranslateWin); 935 GetTTSettings()->pTranslateWin = NULL; 936 } 937 #if OSL_DEBUG_LEVEL > 1 938 delete m_pDbgWin; 939 #endif 940 if( m_bIdleInserted ) 941 { 942 #ifdef TIMERIDLE 943 m_aIdleTimer.Stop(); 944 #else 945 GetpApp()->RemoveIdleHdl( LINK( this, ImplRemoteControl, IdleHdl ) ); 946 #endif 947 m_bIdleInserted = sal_False; 948 } 949 delete pServiceMgr; 950 } 951 952 RemoteControl::RemoteControl() 953 { 954 pImpl = new ImplRemoteControl; 955 } 956 957 RemoteControl::~RemoteControl() 958 { 959 delete pImpl; 960 } 961 962 static ::osl::Mutex aMutex; 963 static RemoteControl* pRemoteControl = 0; 964 extern "C" void CreateRemoteControl() 965 { 966 if ( !pRemoteControl ) 967 { 968 ::osl::MutexGuard aGuard( aMutex ); 969 if ( !pRemoteControl ) 970 pRemoteControl = new RemoteControl(); 971 } 972 } 973 974 extern "C" void DestroyRemoteControl() 975 { 976 ::osl::MutexGuard aGuard( aMutex ); 977 delete pRemoteControl; 978 pRemoteControl = 0; 979 } 980 981 extern "C" void CreateEventLogger() 982 { 983 MacroRecorder::GetMacroRecorder()->SetActionLog(); 984 } 985 986 extern "C" void DestroyEventLogger() 987 { 988 MacroRecorder::GetMacroRecorder()->SetActionLog( sal_False ); // Will delete MacroRecorder if necessary 989 } 990 991