rkward-0.6.4/0000775000175000017500000000000012633754452012400 5ustar thomasthomasrkward-0.6.4/NOTES0000644000175000017500000000253412455741220013204 0ustar thomasthomasNotes about changes in RKWard that existing users need to adapt to: *** From 0.4.x to 0.5.0 *** - Versions of RKWard 0.5.0 or higher are designed for KDE 4, and cannot be used with KDE 3. For KDE 3 use the 0.4.x versions. - The build system was changed to CMake. Read INSTALL for details on how to build RKWard. - Due to clashes with kate default shortcuts, the shortcuts for Run->Run Line, Run Selection, and Run All were changed to Shift+F7, Shift+F8, Shift+F9, respectively. - The tabs in the main workplace view no longer have a close button that is shown when hovering the mouse over the respective icon. Rather, there is a single button to close the current tab, shown at the right of the tab bar. *** From 0.4.7a to 0.4.8 *** - With the addition of the new filesystem browser, the shortcuts to show/hide the main tool windows have changed. The filesystem browser window can be toggled using Alt+2, the shortcuts for command log, pending jobs, console, and help search are now Alt+3 through 6, respectively, instead of Alt+2 through 5. - All commands run through the console are now added to the command history of the console, by default. This setting can be changed back to the old behavior (only commands *entered* in the console are added to the history) under Settings->Configure RKWard->Console. *** Changes prior to 0.4.7a not listed in this document. *** rkward-0.6.4/INSTALL0000664000175000017500000000707512633754363013443 0ustar thomasthomasRequirements =================== RKWard requires: - KDE-libraries and headers (>= 4.0) (http://www.kde.org) - Qt-libraries and headers (>= 4.3) (http://www.trolltech.com) - R and headers (http://www.r-project.org) - CMake (http://cmake.org) Compilation =================== IMPORTANT: You will need to have the R shared library installed. This is typically located in a place like /usr/lib/R/lib/libR.so. Unfortunately, some distributions do not yet ship their R binary with the shared library. In those (rare) cases, you'll have to compile and install R on your own with the option --enable-R-shlib. The recommended way to build from source is: > mkdir build; cd build > cmake path_to_rkward_sources [options] # see below > make > sudo make install CMake options (configuring) =================== The cmake command offers a number of parameters to control the build process. Some commonly needed are: -DCMAKE_INSTALL_PREFIX set the installation base directory. Generally -DCMAKE_INSTALL_PREFIX=`kde4-config --prefix` should be correct. -DCMAKE_BUILD_TYPE type of build. Useful settings include -DCMAKE_BUILD_TYPE=Release and -DCMAKE_BUILD_TYPE=debugfull In some cases you may want to set the following options: -DR_EXECUTABLE path to the R executable. You may want to set this, if you have multiple versions of R installed, or R is not in your path. Example: -DR_EXECUTABLE=/usr/bin/R -DR_LIBDIR directory where R packages should be installed. Generally, this option is only of interest to packagers. -DR_HOME R home directory. Almost certainly this option is not needed. -DR_INCLUDEDIR path to R include files. Almost certainly this option is not needed. -DNO_R_XML When packaging RKWard for binary distribution, it may be necessary to exclude the R syntax highlighting defintion "r.xml" from installation, as it may already be provided other packages. In this case, you can use: -DNO_R_XML=1 -DUSE_BINARY_PACKAGES Currently only interpreted on Mac OS: If RKWard should default to installing binary R packages (if available) use -DUSE_BINARY_PACKAGE=1. Otherwise, RKWard will default to building R packages from source. Further generic options are listed on http://www.cmake.org/Wiki/CMake_Useful_Variables . Installation ================== As a last step, you _have to_ run 'make install'. Otherwise RKWard will be missing important menu-options, may behave strangely, or may not run at all. As root run: > make install Make sure you have set the correct path in cmake (the -DCMAKE_INSTALL_PREFIX option). Running ================== For now it's recommended to run RKWard from the command-line for debug-output. For the few available command-line options see > rkward --help If an error occurs directly after starting, this will typically indicate a problem in your installation. In this case, please make sure, you have followed all above instructions diligently. Automated tests ================== A series of automated functionality tests can be run unsing 'make plugintests' *after* completing the installation. Unfortunately, these tests are rather prone to false alarms, and thus end users are not currently encouraged to run the tests. However, esp. developers, and packagers patching the sources should run 'make plugintests' after doing their changes. For failed tests, please take some time trying to understand, exactly how the test failed. Often it's just a harmless difference in rounding or formatting of output. If you see a genuine test failure, unrelated to your patches, please report this to rkward-devel AT kde DOT org, including details on your installed versions of KDE, R, and RKWard. rkward-0.6.4/doc/0000775000175000017500000000000012633754452013145 5ustar thomasthomasrkward-0.6.4/doc/rkward/0000755000175000017500000000000012471622105014422 5ustar thomasthomasrkward-0.6.4/doc/rkward/index.docbook0000644000175000017500000001311012471622105017067 0ustar thomasthomas RKWard'> ]> The RKWard Handbook Thomas Friedrichsmeier
rkward-devel@kde.org
2006 2007 Thomas Friedrichsmeier & the RKWard Team &FDLNotice; 04/02/2007 0.4.6 &rkward; is a GUI frontend to the powerful R language for statistical computing. KDE R rkward science
Introduction &kapp; is meant to be(come) an easy to use graphical frontend to the powerful R language for statistical computing. Since R is a full featured programming language, &kapp; will never be able to offer a complete GUI for everything in R. However, &kapp; strives to make common task simple, to provide an easy entry point for newcomers, while at the same time, transparent access to the full power of the underlying R language is provided for advanced users. Using &kapp; Most documentation is available from inside RKWard itself by selecting Help->Help on RKWard from the Menu, NOT in this document! A useful guide to getting started with &kapp; is User Documentation Section in the &kapp; wiki. Please go there for an introduction to &kapp;, and contact us, if you can help with creating user documentation. Developer's Guide to &kapp; &kapp; can be extended without the need to recompile using plugins. Extensive documentation on this should be included with &kapp; in docbook format, and is available in HTML format here. (TODO: figure out, how to include this in the docs). License Documentation: &underFDL; &underGPL; &documentation.index;
rkward-0.6.4/doc/rkward/CMakeLists.txt0000644000175000017500000000024412455741220017164 0ustar thomasthomasKDE4_CREATE_HANDBOOK (index.docbook INSTALL_DESTINATION ${HTML_INSTALL_DIR}/en) KDE4_CREATE_MANPAGE (man-rkward.1.docbook 1 INSTALL_DESTINATION ${MAN_INSTALL_DIR}) rkward-0.6.4/doc/rkward/man-rkward.1.docbook0000644000175000017500000001451312471622105020172 0ustar thomasthomas RKWard'> R'> ]> KDE User's Manual Thomas Friedrichsmeier RKWard man page.
rkward-devel@kde.org
2014-11-16 K Desktop Environment
rkward 1 rkward A &kde; frontend to R rkward Rcode level flags debugger_commanddebugger_args debugger_command path_to_executable KDE Generic Options Qt Generic Options files_to_open Description &rkward; is the a &kde;-based GUI and IDE for the &R; scripting language for statistical computing. For more information, please refer to the &rkward; website, and the documentation provided inside &rkward;. Options Rcode The given &R; code is evaluated after &rkward; has started, and after any specified workspace is loaded. Mostly useful for automated testing. level Verbosity of debug output. 0-5, where 0 is no output, 5 is all output including function trace information. Default it 2. flags Allows to configure, which sections of code to debug. Flags are given as a binary number. Refer to the source files for documentation, as this really is an internal option. command [arguments [--]] Run &rkward; through the specified debugger command. All arguments following this will be passed to the debugger command. To end debugger arguments (and add arguments to pass to &rkward;), use "--". NOTE: Only the frontend process will be debugged, using this option. Note that there are a number of pitfalls that may complicate setting up the debugger session as desired. Consider starting &rkward; with option \-\-debug-lebel 3, which will print the effective command line used to start the frontend (but not all relevant environment variables). As one hint, you will generally need to pass a separator argument with the debugger arguments, e.g. rkward --debugger gdb --args. Under Windows, the debugger command will not be connected to stdin. For interactive debugging, consider using a graphical debugger. command Run the &rkward; backend through the specified debugger command. To add command line options to the debugger command, enclose them in single quotes ('') together with the command. NOTE: Debugger arguments will be split by spaces. If this is not appropriate, you will have to write your own wrapper script for invoking the debugger. Also, make sure to redirect all debugger output and/or input as appropriate. See the examples. command In the case of several R installations, specify the installation to use, e.g. /usr/bin/R. Note that the rkward R library must have been installed to this installation of R, or startup will fail. If an instance of RKWard is already running, bring that to the front, and open files_to_open. Note that all other command line options will be ignored in case an instance is reused. files_to_open You can specify any number of file names or urls for RKWard to open. Usually this will be either workspace files, workplace files, R script files, or rkward://-urls (e.g. for starting with a plugin dialog). Specifying more than one workspace file will lead to the workspaces being merge together, and is not recommended. See Also R 1 Examples # Run the rkward backend through valgrind rkward --backend-debugger 'valgrind --log-file=valgrind.log'. # Debug the frontend through gdb rkward --debugger 'gdb --args' Authors &rkward; was written by Thomas Friedrichsmeier and the &rkward; team. See the &rkward; website.
rkward-0.6.4/doc/CMakeLists.txt0000644000175000017500000000007312455741220015672 0ustar thomasthomasADD_SUBDIRECTORY (rkward) ADD_SUBDIRECTORY (rkwardplugins) rkward-0.6.4/doc/rkwardplugins/0000755000175000017500000000000012633754363016040 5ustar thomasthomasrkward-0.6.4/doc/rkwardplugins/t_test_plugin_example.png0000644000175000017500000016256112455741220023142 0ustar thomasthomasPNG  IHDRT`sBITO pHYs:duhtEXtFensterklasserkward7U>tEXtTitelTwo Variable t-Test`x] IDATxwXGg!MtQAPa7k&Q{lbOcK,i^[cA+ѯyxvggeH{))b@ @ t=$4Tvo8ٳ@fDk>&0&H |4a2N`nðOuwsg0@M c};WwD٦]%Wzo2]ʫ{vO2=?ÄBAQqq``7΁gO*ATtA am!6]ǼI~O3] d{Fqtl$$/.ED%Yq!ıQ'ѣGMl/ X,mذa i{S~+%Eu999eggS*Z[[K*XYY 3ڿSoTx.;s]t;gNvvݩkS뚚nݺ51>nj ꟣Tk@p?.=k΅ҟFF|$ÛLli"Jqyc& _~݌JD͙3oנc=sv۷ߏEHA˙Ycƌ{7ҥvϑ";JpԦ6>Zlc&Bqqqׯ_o֑욚[aayJRJex|O>J$b1** ([8pdaw---}_a Em쒐qOۧ$' {环oK._~ޏ']|E^rJ@/:uڸa79rÆ =tת]aM^|@#>~%i•!ĒIk]Q@Ҩ`!JLF6XuX0h ͦ͛mf͞)+?P(iO :6 B.\}G_Q.\8zĉ5VHmMCBKvlnF$ s0|7_|0)&7Ol[vYwg!\nIIIV<Լr߾{}W>BD$JLjD" Afax[7oaܺuk)._ufx~ֶy%HQtI.\t_|)J1_/G`%ZeJA!99I~1o޼;w(Qu=xL۶^xPku:GGE]rQEX`_#ɺ Wj Ē&JS%D~p]+Ql%$ڟ~:٬tרFiIgW}F 64AJiMC%Z 'TVi̱mm|:A 9{V$kE'On_R iXaÆw\, 4Q1[ibnuʫDžLz~Knnn<,+Wooo++W*KP(t: H(;ŤhEE%NPw\2ݷo>}gϞxS$+oX2z̘ :!g^Y^F(@3UD%U\w@Oɣ(;6uu5oJʊҚJm!E޾)9EK +GWӨ蛒|kP@[9;_kh:LL![P^+}sޞE '@Ȩ+,, mrs&ݻR&\zz?}tǎӷo~^ΝеkW@uu=z󓥥Ç]~t \]]oݎU%rOsrr(Jljff?9rUa۷o9{NޒĄȕNY@ΧND$)K7nlfFׂ #""~gj$22 1̖>>DyHF@DD#!ı!\PrKVV,8RuBPŋT eҟZ[vhnKWeWO8>s2RiqCDr_ uvr yi4:M[@exzmp;KKKYFcX<O"P( :A&oo_\*fet:AEekuɟ*dxk*`0: RA^ZK*Aeil6YH$U5FFXM. }QJrql.DLQ*U)DRզwT^)--}bmmP *** uvpphN !MC($7"IĊn_ƸBPT"U;ʄB=Ųe34piD Ӄ <=YBBp#^ƈB 0 Jb(JPL&NRl H|rmz$f#P#Bc ?[Ah4>MG}ކK|YGL+ke %g^e5|5]ql%l^WX㓇NJUKo;BbF>@BKd 7Sw}SqR @b<0}!8FwX-zr*n[]sdSwq<)ZȩB*>8EQDot{ %0#Jgi@]#B v;BBF$FwK 02.XsU HvA^E0H$K$VVVc "N1 cۢ0 J;UUF;F]}, |\7EB *%o@6\4b_{4Z6ԌPALt+unQna5nYX[_j}b1suskQE *ѱA:.7w͖  ~KW 46QrM;R*E6݀&wlæ$<zjDBŀ7cY[[f,HTQQdS?PYUQ"Q`&HZn{;;;*|?UUA=Co[֊b 77ښ V{wP~,4 ڮ_/bqqqN+A\7f͜),Ν6 J!pP7.))/G=<<0 B v y261֫;H -v<И懦BԹC>x ϯJU[-Uzz'ǎGrss f^СO9Q6^,w7 |a`89;# 믿৷ntFBᶭ[g+grg|}sʒџvqϟUUU ڥs.?3F[m1cKgΘeGLrOt_E5diZޑ^0-ct{0UK/J $y$|He Yo~<^݃5lڎNvz,NgP(mۊD"KKK*h c:yTt:+NWTT|6iԩ֮}޽Uuܜ]s87_?dȐ]v?م(UI}z?EArƄK.-^`mm #Ǵ|;o#cccOÀ aBU7%VV ׎YY~hW!KB˴4'';G>fu5V ~Y]`;i i\ooozyyCBBJKKq_G9棻PM@pp]`ׯ0[#*[,ڵmɓ~ӧO۷k|EQB`(JP%c@Ayu-[ݿw߮_cvm7 HJ(n C#WC4m4P.v5/1lIc QMD`dE(8qcف 6LS9;;!bgg׷oo_tܹs<==+Wdffzzz\RAȪUm6~8ハq͚6mjjjΞ= \pN>2F3k׮[tIPPwww VO7~eii]w]V2K//cǏttt|Qn9={oo'S'Aunee|rxD P)"G(CKCb4>u&嗐Á9Q\T*[nmm5E3Br8ef%I@>nmDa{x<iL=<4˗[ ( IDAT:sf~hf+8j?u? { p!kBrMKLInD% ƢU+:';7y4K --->۷B۷k2>iLN:M&|ZϸX aBԄLh@sj[PJ[e]\<<y.qkuuub؈f]bꮒQN㻋)_B`Bl<6J -G JFϥ *wUA !-BF5a: @=6 Z=.-%#DT*M6EEEto]nd^90  TZھ{_LhE@  -GB_DR4DҪU+.kmmaS]uj xٵ/B*_KA* a|Oy|ӴEq}o#!PQ}BB6 A0EE Z}!JMBzQe5BH `.(*HZfđA`N@ &҇Y q[!) A)"H"Xؘ! \5q0 iqk}]FAgҒ@pF)^,|wT*A#Xf tsaA5qp4 B^!z%|>Ȑ!|>_U5hs\P?i>Z6ehUP9ᣪT*AA@1L*nݺJ]qgd̛7gϠ^K-U4^BT63SN 2DG1QiY$GGG0PRxxM"H[hzPZZP(׈%rn9PT/0eXrD\z_c~&n1rdbRƎ}p8* ~][7ixw2u-&IygϞq=zOǎuqv9{ Q Qhn}zA EQ>U o_Ϸg0mڴ pٳgm󲳳ҫ* wѿ_NRcx0|Ĉ$H2|pҰCv=v(~:}>|}K QH ?n\.OZVVOw۷'oܸẍ#>yҸ@ B~ҥ=>F;۷XbŰCbbv#ꢣG NHL?`Çҫc/v[n%Ńvڵm^J .nǎ}||K癳8&_zyC US:t~Fmܰ2rգܱ딑u)B 1Dw>~hر;t^pYVVfޏF=7Wn,))ٳ{eK7))ʕK.XYY+WyڵҩS'kJ?t셋| ȋ/n߶RZ[[vS2000sAjcsQa'~+f_}5hPYXP@ !AfF}yauuu2*xiFqqqqY*11tejJRggg E]'G'%&=zpԨQxMr~jHYZZJ(.*mvsw/(Ǐ|`iǎ3{Ѡ%@GChڶm֮Y;tVm7}W>>\V95rT[#c9Ӧ͜13+3@--..>udhh_~11;SS <z "b <wVkc?3h̬ "Wvw5sfϠ˖-œ͙;w|>_Օ+#mll>4KyskV-T_M,%?0/kOOO6+2f'*522Ru~5xժAWZbsڵnݴ/FB~TNx5{Mxoݎ}_|_G?'$;A(n-SyD 6(JPU6 suuu3gN||u;w{1 ,?AlCHB|\B2HֻwMM 9 G>g\^.\8]n)mb:|>:YFϞAgϞsww/((0aÇ;vn322B00kzz|}>*Kܳ?zHq\ hei+wNS\*H$HݧD, Ѐ &}x77@@@W/{xx5:5Eѻwomm믿:wwRVA4yRb&oii/?hdtʕwQz~H3CdYL&S*fffR͛7oڷoJ=u`vA 0Xܹs"#W߹W[[$<&''@>}d߾{ƯfffDFFcm۶OW^m,ׯ{w@ 2hPF'>tPIIqZٳڟW^H@~Ĥ &A AoDNX}DGo֭СN<ϟ?oڴ<^ڵ  (..Yv5~u]X|ժkM޹sŋx }!nǎ9 eeeKٳm۶ۃkUw?Wݻw߼y̙۷ox7oJݾUUU[n=ua !}B7wwH$uE|cXjd n_I{>?!4 (,(p}eegXYBQ15 *;;ۻ|Aa "ыC ~^QvcTm9,-?Nvk}2^("QbY46mkk@~{7bv @ QO00QvZ^m K4Óma΋0A 1]Tu`R(sss''gFmiid2)U+`ݾM[^^W[ 0 >_% ,6۷S'[[ۦ`-k fX, ]C篂}fmc~YҢ~߹ >Ԑ@跖 |kWXTd2h1e|~)]PPK11 <'2oHaa[at F߮%Q@ڔ?-,,5 4',,,PUVXDHA9%<*n!he1@Z"MYN6? L&Sb)( q<ͮ<p,AyÎ Y(7%4OS hR]Q^l$C*KLȲWA{ la=A"@  ڗ&1#~CsC1DUAR}H3z2ZTZ\\)Qaa)ĦhO< Aq%ݮ"_oư &)aR'kjq%V(j85"Z$ W?ERwm,QHby:ARU/׼@ԋ!!Ge1u !E^P~OI?`,RGp4@ (?)o?/}4O?;Z 3i/ͺ_bX$ښ5NPQ9JA0H,J$6 Si4F 09h@Ҋĉ\]] B<9rƍڵ۱crWW{z |Û'%Lw.Uo鿕yۧk'9Oݽ<‚F#2Q ɤh\u$db1KDb@]v*✟_4T7uԟڵfϞU]]|iaݻw;~~:qℐ`NÇ۳gϨQ#;w9rG\.wΜAA=ϟcJ1sʕ7n^zuikf)`„]O^VVWyRC90q℠AAAK,“IaX6r<:T*jAmhl:@ Al6C:篪N,TCRXhggWUUdȏ(Xÿ KRKg<>[OacG\vͯSG޿_! ޙXEϬ3\Աch<>G哊 ~:=<..ܹsBpӦMx2>& *f^cefͶdeՔ   @r'Hr6JQ 5BeES++*Y,[.H5} 4j'=zرc|K? Ɨ"--- Ƅ ڵkd2ǍlK.Ke-{mظQ"oܸy׮]YYYd|3gLIIcsZ>AܺusĈx$vww7o^\\c'gO_[ֲKIQG~>F T_QcP ]cwb1<= K4(,Ɣ"~4+$5{o޺%ټy} ͙LT3$v̬̬gO6yaT*u˖-{d_uptDQTÓ"*(-Fl/׃p߼+ZZmc{U{nd" :ĔRUX(n߾#;'ype!Nwǿ[aWXB#WFtcG t۽+&55U # :22Z‡D(jeeueK'+ѺW{1*WO A^jϞ8sHH/.}wÊH圯Ln߮iS322drB{"#۴i*C7FEzFǖq ᣼ocGo0\9|/ ?\|n޽K.eboߎO/(t@ Fa\**d5kϟ;O6lrt4TSSjoY#̖± (p>{ᅴ ˜;ߥsɓ{{ 'pܹsf @ԽRBPrb8#=w>*'f@ ?y{)c|;fv!D'_]d&+\Tx` %%sLY+@Z7778t{ָ떙9z':;s󦧧W^^!s_}e]]iӇ /-+Cڵz=@(zyzyݑ[?&;'pxZ3+Ici`>@wǠѭ@+s{899 dM#hN9s<)x BQ4ŒM0433Ųs.X(sV^uot1f%%% ܃*2@ & 6|¢WxifE'O@8*48"+AW.^.MII3ftqq1cFrRkׯ-^[n`^]v%$$ɒ-_[`e Më)i*+Hugl`W^pdffMhڨe(U"J{"7zigXǏ슉ݧޯO{~***ڳgOaaa׭[3<5>.N$j4hPtt'OxY>/N<-/0B'LCu`@va‚3fEF jX^J ߽{O{xw\rţG߿{0`}F0 Jٱ(@R'''u2o?6wY/dffeff=xPSB LujL^wB 1+/ if<&**O⢢K].Zлcŋ߰AlEmf͞M'e͝G^z^m۶UkgLLRbR@@@@Pwk0decc-HڴqVJ\bcc{W={z ĉRRRD"=,,ȑo޼9|Ph. ڸq۷obqqq1)2oNNNppҥK_Lcwޥ=8N |׷o332z4O *?-NOk桨#+uW-REլg8Sz! Dvvv rAZ:1>^aJ}s.uU"sss=<<Μ9 }M'lٲϞ=5p֭bbvr8ϟ?8q~Z弓&MG ޺uK||9 IDAT|mmɓ' >Ꮲs$b1@ J4:]+(w^m SUHPTƌ[l`Ѻ,bX,:]jg:wIҢb.<44BpB0L/?JJ 9)k3p($--3v]}i4y˧^T2(2_jJFuRCBB$WYׅ^˧=3=8X,* 3׮R鰣!9*QT[[[.[: 5o޼flmm'˵>tA:qaH%[L Ϯ*#Džh5<2@a@ 6ӫ^JreU:VN◭`doݹcGh"InJ@ `M-dYJu@HͰ.mTr~u]T[i.-tF&hAPlll۴qegؖL&S<&)}}2,ab^=MB{ }Au@Ч@P)ˉjo&A 7FA4 A)j @H Ba,KBx%-t޷nA| UjŐlCCOѫ((BA0:!kA"i awܖ޹OJByg_ Y쁠97Lj`^}$SV>!Z @ bB ?BaK__^]?>}xa䒅VW&HXfXnV04e`UAmaFAFϥf >*;#cY[vlS2RqĞ,3,A3+gg}kSV>r'zh #1wSczK@ +qjE})9o_Ϝf}ұC{Ay߿?Bҥˋ/4ݱico-1{_L])rƔ/dӐ@ i @0c(p珘R7T9YZIHHK)+w_UQ6{)Wndz-,:w˼~[|Nnm@ 1`˟`OdǍ,M=뇅dAAA nݾjkz|P)߽m>ߘѻݻ[d .r` @O0LӢ Q)9={6pww"r33?8z$ 툡ߞ"%Jϵm %Y$))V@ CO1 733 (TUTy¤h^gU|9!}C?]dyA]=бfΦfˮ cO3~|LFhN@ ]+Q ///<< Jy<^ii麨gͦ5-@ i >.g._PHt#@ ,msb{|ͺ8u}غ@ i -`ic'a, k|]GE1 Cardxƶ@ ,|гA1vn3+: SVQJǏ;{p `R/F_i`H`ZS;{z|Zb`ٻw@ AIynRxܤɲS+++KKKKKKYL$R1L4O]gEq4\"&dbnQ~@? `=C.$0S/ت9O!%5ïrhfPJRq&4EvfƲs kʥʶ)`HSUV#FiUuK{4id>BBv2n/^&Mҥd~0@V1@EWcLocY,T*UDq9B \_{qi6b_R%sKԤ/&oS(ۦM5-N?QYY9)S| غeP 8}欭mϠH{G ?ܰa3gQ;cv{N2=#P@Z0)g|᪆`***T8 !kfL䖗{O6߇Cþ=]x #1wSU)ֺ2,.VaADzˁ Dr;|b؇Sݦ!: p&8 BJb|lc]S]ع%}/6lف';xϪS5 rR}~9Љ\8uS)8[6=qJFǴJio׹T^sKɋ׎oIޱico-1{_Lb|uzWAذzAok:gצ[&LL[wn]HRGT&]LHÇ_xa߾ J2UZIs&=Cr.i+kk:ON\bI,1)>OpnjzX.WV, qڶ37g; 5~UdEV 3>F ?ׯ]Kf.2Sgrvq5H~x/eڌl ]D{ܵ?ݻV,ULɣlUbܝ{-,?)秧kH={.Y;999D,@ u"ɦ> Bќð?]k޽4(dJRaf&H4HbD"FJ89+@d*mwPNY*eU 6u[JQ{GG-4-.\pBDF\nQ+'ڵk=<<,/_bݺ:A^4寍dHXes@P'T%&itH^JFlZXՐаG}Sr^X \E^U3o?3eé)ǏOI7c*++iolѣ >\] 'O=y…#FcxvӧϠ@ )/,T*ŝ?.}ʢ呛֭fُ*râ93]dyA]חnPf!y)-Zimc3},]0Gx'޾yhyFɫr'C4B*!.c 5sͶiÒ5ٙa=׭\{~削ӧĉ^~5u4c@ HB|\NN7X'4p^>~X϶`ۦ.],ojnĨ1?O@EvfH$z|aAuQM"fv!<2IsB,Z={6Fn-I1*uԌQ3c>a Q @$eR$>3mA!ń@ a0~޾ ^o_(*'Ak}@ǑO R/cI@ r f[YI_JEQ ׶0_[`2?y˜\d2g8@l) b]muTE+Z\uhպlvZDZUn ̻{88ca&3I>yixR{_ߒS~3h*B> CR4hii٤eФT*HԜ'!Ԋ0/,-}ҒM>^4FpK&;:soj5F(TR0 Mn xjEu͢eڶy<~>V{XSWt3>߿ mu |܊2OHeB}?zwQrZn߾Ch^.BD>i+zti,b'KԐԚGb²Qot{Gl*Ҕ2*/O9a~eҚO1Oxe[WZZRfkj͙5BmߖQz#{Q\\gT?ؚ@{I޸s+K '89h-J/w>x_@ ([68~c.d_p)kUTXx^.~"MN.nn.nn`*_zeYWXy{SUA+INeN.H<8;7rrY{?veek-96 ||KVRI`PZS߮^VU62jDCriJ,K;;;S:n߾{]7)߆[7%rp<׷!t-˻iJumYdE|G͙/W.[nĄe^*1vX6[svUa# KBm5Q,NX9$A8xďBχ5[i;3&?)LMI>zIleU^V~IXXX|w% w֯ZP}c.[Zad2;=S"L&/q!бдL&3%򬬂>}lB6.dΝ7wY $ ;ՋQlH;1?SI=?@kׅ)uY`kg~CNOg?.<׏Nlݸ~ќOo2tdUv"d'egoNZ660lԛ[6$!)Iw;1 YdI_Y{XiRi*:~ .!tdǎNfd^L=T*ssC@+}!L-3r>,//;^QU0e%';dsgϚXz~R&V!#vm,oh>X@~e~~eT! 6യk"un B5Œ sGhOҚQ6P*Zp^Vfzxْ?P(b{ۓ$}fs?|uŒ+*ʃCvfboJ1{ފE%а_~}Hof̞d_i:;Nx^<3qthU޶]!$-LD{VXz엟t)k3<TC;Ӹ)?5i^J?ݿwgeHDVeiTb+u[z>Hfh|' J$?ji)NJ:ZTEÇ;JFSdFG;88MJ  㵱_#"ʚǻqFkK $ʄuL\nt( nR!7VCǴm9~Jlc%*z ^8 G|T:1DV R,!BVm\#!iշ}CҪC4@#86ϜoԞJXEOz):>A:C1rvsyaB&>k1L2ۥK@S-BA=2l[νwC;Q?ޝ;شD0w7MZ-W(|}}jZxw0Pk(7V+zzMJB!;njn5ZNS $ M6e_ExzyJ-Pj}GG͡:yud7s3OOd_~Ldb?#ITɓۏ9_4 y}"F|~eE׀$:y]O/wzc/(x4aʴOg4OpM)R ,,,,,\ܹZx9{o䰇~s-e3(nj=ZƝ[Y]X< TVXZ+++o?*Vvv^^VVV͖)BȌ ~5{w RTTDŨׂn޼^uacvkRdSu X )ɧR/J 1aYJLľ];$e#ٵm߸m{O.bKKJ&X|Ɋ FiJN;`wZr޼o+:aB6 q7]TߘٟnھO:9Ee+mU?&"IR(tusUUL$ E=V3j!w&(}ɴIw< IDAT Xb++Մ)ƿhgoNp|oG]8gsǰ+C~ ~0tĕKٽzc kWdUU ޲!q d\0LTr.b~0\L/  &}vN<4VO5փ!t>0!]~LGX,P$[ݵ! M妕2T*={ 􉎍޸}6A=ѢvvvMU6eb=-5ڸ|.JjJ+a?wH$(}mai kݪ  V=fbm){wn?O6 GywWZ@;cSۦK])M|8iܙq 1~RSTP@խ8w L|!fx~}￟ضmuk5^zkEEUV/YlMT6ZH PHGn3kW++*vob1?O5\~&ƒes[sڬi7!)). N.Q"EQE<ܲaZEo9ܾUk <ǧaޚrRzd!o !4t*pvq=7+,ӌ3Ϝ9̽^^ӧǞ>}}$0%4Z~[|۷:w`*̜fLXQQ$7֔Ofdۇ=lyY^>My-?_ "vCc~Fw{cpSEo9?4%n?1o E,wxUSNJWhIdq籁;~A2:_=-(2Gs/\]]n[z䤰0JKReHL7wn\1qKƏWPBƩժts!ptrJ>s&(8XsZ[;[xxYag**#=_dUeMQ?[2uΜT*6-6((0//woỀ/a_llHl|tPp !Bt7RfΟ?/!!a„!!!LAtk=x|A]꠻9h`Z*ZV(%ͷB!ϯIצMaaw ,2Y=}t"Q6mfQJϏ[ BIz{{WVTN%B,Z]QQz=G{ϥeV 揟c͎w0H!54[5O)]֧o׎I~>ֈk)J*`B8Zmkg'JLEDv Ivv€@WW\Cܼ̅B!IlwyZǶj]ۭ~ZWsJ*)_+m‹';>gFJ_eee>:'իqMF׈kl~`d([ŧJKy ={xW7g/)%vlҲ,kF64wxGB!}[O;771Ǖ< Ŏ7sqO1f7rG?q 9do^5l7{@T:sf2ViG|.1 ^ճS78Qo)vآӫ1m ٲaWVTƴ𬬨͉+!1Y4r_ad{-'=MZgT*uh}gWgN,/מ^oUu}!jaȦyd$= >׎NaB.zJĄN.~>Xf矾pyD.ƥvS_{c[?~{p庍4|۫.IyY9׷}vplpf$/])>rI7gRW.3~@!G};;[pV:t=oޓ  ~֭E+Vksp.LQaWRzg(Y#yػqYΗDm޸fKSTu}!jH`{[:~n[ﻎVDVV^L;| p>eImy|0~)l``p g=b¾Pڻs[I4vf|'{oCvq 3jISC-L&{q 2y,*wLBW3&5?Ssvuk& 8k7m{g̸ΌĆ$iQHDtXXZrd WuݴUsn^󌟂ud񬪪#\.rBԥxvR$޸]޳ו˗rs_q&?ϻᑆ5oN]9Ykw}8iJGg8jC_WwįjiW )#%EDnFNuPDcd$n#ߵm Ɇy9{wnyVVffNq qso{1RyhbQl<rwp\=v`{Jr>&>u2,_lcbk"VOݴn͸@.8\,J_ xb"xH'؏{ݧ̩=7{7YCy8m-NѤuZ~g4?#CjޚRz~g"iLX$EBaK?b~1dTXWCM`@OoշPtk/;;+clȌr>ꐼ籁bpX4?&&5sgN.3&O|wEs?e|2k'Sw1׼.]bcC;t2=qNhWy<^\IH^%n=/#?3Ԟ^oϜ<#KC_Jq塯IH^A!#"lXD8gߙ E, E[SZOĠyxZ>uԨQ#;u8bp]#F ܹӰa?6l֬Fԛ oR)#"§Oe(S݈ХK#߼y yݻ+/Q#"}鬲O$>{n$PVH5ebY/ MQ*TJ%MQo䤰PBy/PRe{9Iaq+;= oТ{zݹqEkT!.mqZ u8B8Z5WBppPzz_.888?࠿daaqcǎÏ0gΜYf:88ѝKP7 ̙3|V6b;wo?~X.~q7n0abppH$ĵ"8!!Bˏ?ef\7#Ql]%:¯n?.uCwyJ8;;[>3+@wqd"M>p3c?@ͽ-AB@boׯ fmc#G^~=tfW_yzy&7sG=;LTf1 zƎ;q䖭[وb++ՔS{]&omv'L5rүGBMC7\"IV+T*???ZR㓞^^n#3OS`B)caa=͛?`}Nb(X,V*k\]\ ;ta~:q_25eW:]vU,yzzjʥWF2{KT7S݈F >O~U??m۶Y=DwTԗ_8qΝ;z]L,6Ո̋bihXRQUORDb&D&5 D+XLNYm+1"ȹ2DR]ӿ>ŋ͛PرcG|Yff?5ϛ7w޽zzyyY[[k}dsY3DҡC>[z'UTT!F|N1/kҥo֭+Ӝ='>>_ҥKB*cobB-Uz4՚g4?nq'5UAMN Q*?">ښiG~O U)eW !Tg*ڥ|Z4OO/R?߽{g%_Lِഴ/ 2 ;{3|8_t>T*UFzZ1GwUU+ =rX""z9eY3 y2-&ZEBMi+^ګ7 BI]+lٲ7n8::kf)K/([m޼,Jm=>~w2D=#.YV GoݣskcgNmwSXfsgUqSsVB=fw{j*Ng0zZZySh ;uVS=AWW!L,>}~Z`=Q'+ViC-9ǶeT7w@4DvMxqlB;]!sc/DZǰoOc7o JΝ_Q_rCBB233؈J9sdd2 ݾ}ۙоl <$?cǎc?LO(*x{{<[AA᧟ξtr@'eLh=($W0e 㚀~IRT,rtr:t^}lؐҒoc5ۦ$tFsMlCNlk7Y "+54wX:a{I1]Kk/ B2C6n֛ Ydw]Šwr۶SNxⒷz/_*--efɒA6}5|b!?Fg;7 hFuaXiq1`w{u ỏ ?NW{t{5n\j}#;wk?tHM {yP^? M0{X\4C-d|=w  1F7[EEc>.d8'+:ƭGii"{Ekzq`tNNDP)5E?|} %7l߼aC*'bJexKi f/X;i\qIVVu_Wxk+=On~~ZԞht*!T01,ֹa Zv}rY/O6$%̙;v93g4ܩ;w~??Ȉqoa.^'͍g>K/:lD•N;9:Ü?:VV]tNJoEiLb~׺[DIь`\*.|M}:c6_)44D7vڶk1qjaƍ㏧vuue4z$ҥ$I6d;uԕKV3}|M9)õ:o2콤]6l d!;q䱣c.[wN06/0dknqI)CGlX^|u%Ο/=['dUU&Ž[{G5MKJN)1aՆ__=nRf<)HUTPp] }WH:u7Fϡ%} %k(=;VVO>ȉdc6bkNoU@HGx-i~jv!dZIٺuY3\V?׿?%#332"nilHLLS}\9*…۵k׽{7a:wpђ~}s-Xg_~X IRR$BHQ j5 _s²gt̼YE;oƤ~y| C:^}:8سЉh֕YkwF32O{7vq!VPQQ6DF3^K =!(f<{ϳXw@q\ IDATk/\bI^_ֹ̚O37f};NUS̋?7RfCy)?DT}yuO5>##M^:eW_Md}Ok6BfDdqT$PjҬ|BM yFMN Q(?JGt}VЦXޭVyRX S-Z>GU)t_ 9v>V~~qU)-.6wB-? ZSZm\|k%rsrJˢJ6 7<3zc"6vm۵\ۗHOCkZ BJ >MVBMUCSe@=x<}6:tdI:k i=*ᅱmhXS}-W TLem}>ƕV)uI7!((j.#-j =k,~~ҲҦ*e cgk%2tmmS`'ىy5MekiD%BV:Ǣ(iJE4I$I߯!dIBM.2wqP$ F&CgAFAQL&㑤]6VV|@RUTVS4- $bH$ge)$ ;jzZ<Z.PJeB! ldrlllv!dnAOGV~k}!>jϏYUZiah*EUUO+))V.,Bn$>2')R]\Ӵ˻\od2/,ɴcbG"P Ot >j bb"ɀ"04Mq,+2ؖ)L& 7,f(,,4Ri=4ڥ72|z!dN TTT)2nYkJ$''pRZXP hyaǎkB3!s(=M_4EYV"@QQњ4EUR+J)5#(..\e7! H&L1qD6/]O~7v:j(6_,,,?a|xD7GsKb{9#nFVV#BȌH \BFWar9 Æi=*r;ȅ+**:?g ^;O> @ªgg~:䜰*AoR+VOM3!aV+V>}f\`BB€N:kn#GnƷ:যӬ:jV4( `kcgm۶5B!M{'::SpÇ=z}RSSǏ߮]>(55ҡCo6lؐ婕9~xkkΝps΍3gٳFD!3ao"̋ K<)nE@۶,\HF0DFF^|9''KI9FD<^3ovMcƌZx|@a6SBfDAABv-v14u_=povP3Ɩ b)?Hԯ_zimm'66,-ЫW]/{77oejҧOߍ7 ݻFD!3`bBȜx<]YYYD Sʊr)e - **lmL_OKK{W`Q7o:UvW|||NNk ͍}Ѣы/9s֮x /,69sNһwԼyBfN~!vuuM9{vBVVQjw o!)ϥxX[I:w\m]\\|>^z&Yb`T B5oA>s6 P)gddTVTx{{MCZ!GVxBDmAA׮JQjomeU,siBtIl"$iem)::9Wd*5BX,b>o$:Bi35]zc!SC|>Ɔi6$IS&vA!T+c+Be \Bz]L!BY6!Bx/9܆B 5^UB01>ˋiobm!B=ŸQB!Z1a)B͈(Uj5MQ$'I!j&yft&yzB5 EQ2;icem-T*UEEETJQH$& B#9k5Z0t0<!MVWTTx{{[ -U R^1A[oL}kkkvBu5^9Ғ@R(JWy@@iiZ6waB-.TaW@M%%%>>j\bkzS((J[RR­fL&:UL֐5J"0]dT* ݻw8.\Zoib˃ M>k̪*nIFDq)I2\լR23 Gwf,'BQU97||2pР¢"T*ݻw>7xR,J'<}||uHTT˽{>lKPljZm۵ LO .-)i* #2235BNs?sU.ΕGՉT4E1$#yyϥ8; viJX2--!22rر-xJ& C:6N9BȜlll||| 8s`PdgWTxyyy{MKZ!W^kW۳go3aǎjC I~~׮^K=JUO$Oc/BiؙpTPKAQ\.NN@RUTTHrP(& BH?d>_e?2<`V+|}} \^U.ch 2;VVVBψ:FO`Fc?.9겲``ˊ Ei(B!+/H .++U.,BVlGEM%%%27R)|p+4da^d*B&}Լ֚ !ꇻ(r$"aEH22YAB.DdddڽҥK<wދ/n30Htfl+AN<--/ለH **jݸ!S(޿/&fչ an.s}9yy{e㲝\^zo߱ANNL:eBoڽ{l촴4cv]2tЉnzK2]].\(W*srGԭ.pkE<v8T no|BY$Iwl5H6aNZR) E呤n'䤥K@Ϟ='L aÆq3***w Ld/>7V*|'-|s;.\HMUa\jջMtHH۾+V,􊍝u̸K.y~ +++y-ɓ̙-<==كgd$44tǎ"jB-@8qc$J}**;+k /<_7<;)(BRUӯ{xzM!%%ZT<2EJ=?Pfw츇@Њ?Jرcߟ3gKPcgov󦋋 dy[Y绸i5f.X}+}eA=;@ pvv_suvJVA<2QaJrsnڵk3۴i8D ԩ{METOujb ÐAk`谲?N lnaaP*yxxxzy[YYM!gV DnokgC$A0dHѾ! H=.[lll \\e%Eyښi !4+B>g>^% ;>VB)xv!lcM/M+5Bj.&$AT?dBFC_A4//c{BT/!:M}u,VBiA$EQ.Bi#^xBmV3$T5}:@ l^#j0OB&bL<~,p=vGn$7zBOeU%[iT*~Iع 4eȘĶ.+"1>b0RVURgcmKROJ2''GR &.)B=3jDggk(zp2gI"+++kkˈZ,/) OOPո/BHIIMdNml&`-90 0_.5:QYYYHhhEyZm/ʊOOϴ7xV " ojM /QVgef%PKA<6]cG`!C¼1;JJE"QY>?Z+))g$NlwqM]o@2e)CEY[8ޟZmU]:jֺƭX De &!d 1 I'srs9vP+$SSSű"w2lpl7u@|/>>ֽ{ە"Hjkk%s~Iҷ|:ǎ?W[Ph 7z?ᄏfjiH|`666OpmTBEE::&:uɤRuuu'==MR_eIl6ٹW^-ĦGku/777"< SNސFQ IDAT9{ffɥp8,!ޕ'WS]2j;88џBfؗlo9r4lRͮ*kiSmmN@khiѾw\fm۶m޼yn}6Y[MʘΙ3gToiEEEs?;uTu/ VXZVVfaaq[6?zhsfѣlac|||wcǎ%$:x 7o޼^СC#"",--\e>}/3_AkO),,C.^Axַo=]V}׷6,++` kfk7E YYY6lZl/7TTm?,bmfϙ-HٲeKZZg}dm߱˗}5k7|s#B 8Ɏ?Odq*GmTFDޣF6* SSSG}ˌ w60< z.d ~b zMMZ+Y,UK:,X[S]-W;.hӦMB0h:;[2 TZ_QWרk[O6W콙3h{wZ ԎuFH"U+hij6lHwqWϞ%,lٚ5y$3X]=RcMD ݺ,\"TSA?OL=lmmX9SOsEA@yy1, =iOD,pY~e54͛L&S__,Fޮ ,..z]&aan޴Bmg\m5o{ϾW:\Scc*V=~ַ@QQ5?~bcӽ2eem}𯗧ElmX=i)J)'[eZ]U% e&٦UUU^zKFH444t1ĩH  Ͱq)cc6n,.*~װ1.kiisJE:u5150m4hvѣ.*Pt}ƪILo捍ܼuŋvpm[6`iiig̡ڼy۰aC7mZvءlqwv?J-%akd233"!z4X,I@=g `tڵYEy9mƿ㲄Bwe1 ===M--c>fA IKSvF66B*"`IJ qQQ1Nanf6Yg̦DĆS[ :rUA( 3l8mX,f[,:GiDk`26=@&HyAujS:xy-⊊VzO5ռÛBȯG#Om0;w,ͭoUUUzz@p?tؼy>>*t }|g-T{=.^46?[jdeMB!Лx?O=^Wվ}R^hijzxp+W.]?kƵT_\񇟦L?8;__,]YUU`5V~ha_B7Cm$mȳ W3Ϩ [.֍rNt}'T#?jo_B!)LŚZoQL`2ʴjфB!fq8ڟTY3 Lx,[̽W`ܣA5H$<##cLa,`0t_oo2>E!p;w nܒ#IR[GNQTcnMoqIM%m.ͮk.uX4L&SOO^{iEB!(.*:37oI:@SSSS'FY=JBº:EkR->þ\.277[*+.ďB!tuuMMoC q \_9_҅?BEZZZJ6A*"\EB!nF,hj!g<!BNB!:3B!eߋB!8B!0@x!B==!BuB!|8B!!PM>E!@Xhg`&Z s H#3! ,@زf5+oHSX"mm!B)_{>' ['{)nO5 2Ν4Z\y<M-N*$._4Qc>u%HD|7}\{ Tԩ ֭yys6L6RWW`0BpqUUS-$?SS sK>CKV]mU˗.-Z`P!C-Xpe ={vc'N8&:¥˿ݻmWRsrrrO''KwqfU!By999:::}LM[[;'75JKԋ=zyeeECn[kI'$m׮ɤIOw t>x<,kkkKA8y5D!jKJlb`@{XZrzgE8Ҧvrpt 11`fffnn.}ym۷lʸǟΙַ5ãXyΖVVҗI)_|t]EBv[?obX bwHnII%þ&k&ܫ?ҹsg҈J_8c\qQ@Q{{牉җzzfNIIi!B򲲢 uuuE7 A}8ܞ.54N8""¥ѣ /] }0 vѣ_~ 555`fnF +ɓK Ju13榨RUBvM(ʶ)ʆ} ܽhG^8>5t M}oݾ}/{M#p8񢝝0k^vSfgg+ϓu/3`}Bo T0B!{Z͙ѽ{uu -1©۰,'LLM۴MڷwoDxXsȹgacQQQvn+ B | R+[%!BoH,NMI~ $I0$IqrqfMMސbk;!Bv$ɴԔ'O"!$| ]}<~͓Ǐ{4|?B!n""en.]7}mI?kEYY{7B!"X$ ?!⪊R꧲AJ0\XW'B!P;f `PЯx56.BV3l#v+B!h*I_7v%Ekzzy.) *2w ʿz5ڢ!BX$vh_ѭ[ÇƊy<#??%AEle@HW A͝O"QΙ3B!P4E5XeB!G5M:m;*^.E!PGӜɾE!&_B!M xx(jĈ&&7u{y`t `'B!;%m*hÇovJDr|4ãz}^mQCBOͽ_En^+KZ^"?Ҳρ@D99HW"j}׍Slμy=[^ᣞ7PI(̙A^<(UA!R;w ^WvNpt'#G D__v{_iPѴ@`ӏ֮n"ՌPt[vvSR"k*y%K֯gp8N?ظ+Bg?9yV&7o)XϟߊB!ha_ ߇`tiig멝|P:jLGhRRiP42Yƣ-g02Y#B $@í~}-+նeTu8PIT'%K"#@BRRD(|ICʧOU57aj Eo7pB@q_Ծȳ湫l6D%;+A^FFÇIy/Pʫ!M @@P㽖 ^0<9YNB!n0ԹxHC_o5|irpXiSٳO, S/4PxF:K#S@M'D B)2#~U_ej!Cn21BIP]WWm{%K"<= BB BB܎Wv^chAA<*eٮ\ɵpwvXM89aE?..g϶9e!Bkn? ú{uM zl CohM 3SS5VMOE!z%dJndqaFX,yfnZ,G$hjq9dΐ@!B3a?B!ԞJ_P?!hTlQ8B!!PM>/e޽SZ*C8C jvq K7V۶Rx}{6ZvGE~hWտ\[5ܬYTԬOWPdQIEj=KX[4&TBѕ.!"z3Dk}@ PoYwJWþm >}kۮB}|ۮB&4^.zKXH;-X3֯=:=TR[/RW֟i<0PyAu}wW{]dܩ Z1?}SFՉ$jԽN.URIf Pɯ*=еgoM_OT+﹑fcOKe|x5N˖S76_NN5(s˛k^NT^,-37o~U"ݥHR a"##T:v4(}u_[ 9s^ cG? $)u>*͚5G}?C7_W䔵\rzP&y15窧rG}8: `ڗΞ5V+}N *qqnsERF/Eñce׹NNlSS˛k^VΝ}iRMI(= P~TkYp1sNкﭬ/Xx* |a񪵅ee>}zo^8ٙdRsH<<>KH5d$&oCCCgrr?vD rʲa=bh/&T=RdĨ{oѣEl.+矻\S6U` j򴯿"6?̱ |xtSSP&'\i~=) '-kk˥Kg̠navv-(׭./**mo%#zz=l튱v݌>.m+vc*x^]W3ܕtI.cly@yqz!EadH5'p_m>~'L&O7-/09vv C{)ҦT:;;KŏjGqUmߠAd_=7$IDj{eiB͛'LHQndY,xSbk/edDlO[\p={^^B*##Io mni+yb-ٽ~ў];*++檞Ycu\ڎ 3m7 v62EIia!HÇHh*cc37o.UTH׭lVA?dKڂhіǓܲڄὌ`k@P\t[:J[8YCO^VP2*[Ѓ>_!I$Y>O~c6G Œ_i(Cڔ9!z^^ J44~>PpyRRyT4H{)ҦT"##J^[5ƞ u&߹k7I]4.ߤˑwW򫎖#B[_cz 7/#oy5knݾӬyblKI3~ttqX89;HKꚚ"oܰ?ʊnn0LXl1U5x))奤o(븺tzܞ=,pŪU_IDC=izzy}7ߘ8񇨼<-ڂ4 5 r9_Ѣ-1e*8a\?3 fGh}ϟ@r^[wj׮zTw7=*_nUk=L B,! eMd/{]\ߔ,Gp~η4hS6^e0rdƆ GXJS\޴\rdi/EڔJ$&$ٿniл*v>زiӋii ϧ7cƓ  }ֹ>IKkn Ϟ>78%*ݳ{W^nnrrҼibc9΀PyA|||ܾ-}Qozّ=yY_B$o%%&&U۷,WPO'pURF;]hzT0kN}__*t„UViZݫW٭[K1=:}Z^r2/99q4*(,.fw"xd ڂt~63Szcb ݤO[ZFш̥̉˦YyXlVP#$ui$ 4MUM;ogwEY%_XT)jUr8mJ%'ܥh8vl]~ټyj\+D5/G'{N*W])sުDG$hҽɥ^y?oF~8 N>}Ŋ@O^|:qS9vus+x++jqG>pPee_e_,4y2l} > ~}s { o䳕YLb8N*Sjfffǎqjg՚[7o;v@ww ,\;il cooM ˥K_hv-> ͙4};wlmjٲe˔/e֬C&??hx*3֮}'smAݾ8??}?? Zރ&lm}hkMѶTdhF wK?M|G╯7{H7?g);clߌ ~:zm޼# M\rȑFF#GjMo8^޴h/EV}|^)y5mP;ҜE0kkk^M U%fS8|@hOOо{#Â6}A5*儱Xm=W4z}!A OM:v vrt{㓾^6Y3e ~4s֭L'n<bF.wxyACOagww(q#q [U%BWHHRfdS͍XQuѐ!En$z͚3kr%:ݽSk&Npdzg[͜Ieaݺ=YbTǠ f'>-5UB!jξDDxXn)  KE4}0315USB!=Lܭ,=E7oެ-?S$%IȑZZ>L& !BHi C!jSBC!jGB"zSiV2Y-xXM7iӄXOT]G_~ީJߨYd<^}А%G TR5k_ b!:,ɧuO{y rvqQj'QM%ϯewtk}6:uZRzeþH;puIrjv*|ۭBv9 1*DXVnuf&T÷|0zԨHtc|0|ɓ'}!NgnuAAN+~Æ5S:9$&J_J Dؤ['*/WCg?;wewt:*ccMgbt„Bi0+t޹*:MQ/Vy$9r!u aiҢ[֙G[Pտ>4^.z6l ʂTW+﹑fcOK먠7+ !vM%)U7xvd \cCa2쓾7lXdd:> B!TkÞ? Ǯ{iЭ[,OU~l]{<~h 耀i_;{J}_իWs>]zZꙜI6JI ǎ-rEu4kN~jjP~RR]Jr:<1m"##"@kfe)Rw $kv%)TˍƎuOJr>sFznʿQwz%VHmPMn@&JRLYveO'dB#6w#QM_}󍁁/ZzMN2uȐ@H5dҤ&@hh(l_vڻ7<|߿&OO):\Q=y ǎ-OaII ɓ"<*0ckkX$YՏiMM=ɓ]X\Y#z:#8vv./*ST// SRP5k-.Uh xtp'clW'T`@hT%]2U#:;;K !PGE5pP."+?VWOzB$DG@^T02"ٽyP\\ =fff`6Jiiez_iiEAWih}PϵmG'Ogyذ;w$7Ufn\.?dHRt8U%}ӧJzI O 3m7 v62hie}C !I s1߾j)疻+IIK\F2+ fddXZZ):!EJy,hh`ɔ7t-?m֬<׽=zH낂:|o߾n}bpppxa^^D,V~z?OLln` FذpVzzj+/%S߾$޽g?th$id y7&!*/yQ{EW)%EOL.W"045;vp-$bB攪8K[gOC$.\xjlqAd Meokkavxt9,%)il ~tn컉 v-Bcrd[|PM6mIN:$ ߄ ݻrsjjjSҔ_pAJ޹} ǎ77OCo;}}[SytWTܸURt8U;Qt=-kk?WKOOQё03֮}7hci*:[r2xoM_VzxY:tx<V˖,[ւI͙4};wl_-V⻉$J;flӹskڥ9})SIݻ=zdMܳƾgΜ~B?ߏ9{IJ\f#8t&i)9)ɢB!Y;|0a_o ,\$|xתk##ҪA!R#@1LHȍڭ[4yl^^^nn. 6SEZyb\x<`{19yyY9T~ݏo޲u?ʦOLHkQB!Sh{sppdҤӧN&%%I0.^8TK}gO -6HK{p|*3f<*))M[TwB!U4y@C-e.hd9\pE i4KXYYp*}%<{< ͝1}GQiӧX>022Bviӧ7Y4B!Ps<""<̺5ǣb%fS8|@hQ'LLMWygj_|Zr:y,QKn!2VrJx0# $$NX`ܒEM"ő#G4 &Ap܌/KvyueB!LV}+$%O^{ƙ,H(QwC!zfVTThjq_B)++KYly!B>diy0_o}c]{wc4l&`C|8B!$٧OW"T ]]=;n>7,̍MLV^'dS&I!BW;882̗/bA 0Cʲuʊ KKϸqo*[6hiΝBB!:X\UYiccH. H(Ųqw`cǍ[p\+͚B!:&X\Q^ތnTW{IDATAD]]\oذȈ(!Bz=qthJO'6*!B52ӂh߯^Mwk٠uvvVB!ё$Y]Sd ?V=_KSdVmT"B!PGVPP2=#Cyj̚3GWOO6`goF%"BuX%%%eP[/)-UM$.]ܾ=xw[B!aUVU7 RQb7ºkz#5mtB!a>}^g릭䓶nݻu366nj /X+<#BQqI 0н0?_, ~ݒKKnoeϜ;B!:~n4좂j'X\VZ;N (.*jPB!ڝ2 H]]]QAATO:'$d435/}$B!RL !u I>AJ[-B!AC!@dB!~|B!1HB!A`!B M| 3B!g˶r)ecB!P2D",$Rb1`B!P&׿N YYBuu} B!Ԯ|ko]{&%Z>_.嫥^B!nUUUvdcc vvv\*t$ ޾Zڄ|L:M-B!jceDqaa&&&@5dzq&,׾/iiӍB!Pke gr\>GnBBBgH ։YC턚C #B3,(j`oo +rnd&uHB!ZD X,H**+ $!BE,%$$ {0@$kkv B!Pm>Xldl<ۻD,b09/B!jbX,ykHb !B=d0m-i]IENDB`rkward-0.6.4/doc/rkwardplugins/index.docbook0000664000175000017500000103462412633754363020525 0ustar thomasthomas RKWard'> R'> .pluginmap'> ]> Introduction to Writing Plugins for RKWard Thomas Friedrichsmeier
rkward-devel AT kde DOT org
Meik Michalke
rkward-devel AT kde DOT org
2006 2007 2008 2010 2011 2012 2013 2014 2015 Thomas Friedrichsmeier &FDLNotice; 2015-11-05 0.6.400 This is a guide to writing plugins for &rkward;. KDE R rkward plugins
Introduction Documentation as of &rkward; release 0.6.4. This document describes how to write your own plugins. Note, that at the time of this writing, some of the concepts are not yet set it stone. Therefore, this document should be regarded as an introduction to the current approach, and as a basis for discussion. All sorts of comments are welcome. The documentation has grown quite large over time. Don't let that scare you. We recommend reading through the four basic steps (as outlined, below), to get a basic idea of how things work. After that you may want to skim the table of contents to see which advanced topics could be of relevance to you. For questions and comments, please write to the &rkward; development mailing list. You do not need to read this in order to use &rkward;. This document is about extending &rkward;. It is targeted at advanced users, or people willing to help improve &rkward;. Writing a standard plugin is basically a four-step process: Placing a new Action in the menu hierarchy Describing the looks and behavior of the plugin GUI Defining, how R-code is to be generated from the settings, the user makes in the GUI Adding a help page to your plugin Those will be dealt with in turn. Some advanced concepts may be used in those four steps, but are dealt with in separate chapters, to keep things simple: GUI logic Embedding Plugins into Plugins Useful concepts for creating many series of similar plugins Also, none of the chapters shows all options, but rather only the basic concepts. A complete reference of options is provided separately. Preliminaries: What are plugins in &rkward;? How do they work? Of course the first question you might have is: What portions of &rkward; functionality is realized using plugins? Or: What can plugins do? One way to answer this is: Deselect all &pluginmap; files under SettingsConfigure &rkward;Plugins, and see what's missing. A slightly more helpful answer: Most actual statistics functions accessible via the GUI are realized using plugins. Also, you can create fairly flexible GUIs for all kinds of operations using plugins. The basic paradigm behind &rkward; plugins is the one we'll walk you through in this document: An XML file describes what the GUI looks like. An additional JavaScript file is used to generate &r; syntax from the GUI settings. That is, plugins do not really have to perform any statistical calculations. Rather plugins generate the &r; syntax needed to run those calculations. The &r; syntax is then sent to the &r; backend for evaluation, and typically a result is shown in the output window. Read on in the next chapters to see how this is done. Creating menu entries When you create a new plugin, you need to tell &rkward; about it. So the first thing to do, is to write a &pluginmap; file (or modify an existing one). The format of &pluginmap; is XML. I'll walk you through an example (also of course, be sure you have &rkward; configured to load your &pluginmap; -- SettingsConfigure &rkward;Plugins): After reading this chapter, have a look at the rkwarddev package as well. It provides some &r; functions to create most of &rkward;'s XML tags for you. <!DOCTYPE rkpluginmap> The doctype is not really interpreted, but set it to "rkpluginmap" anyway. <document base_prefix="" namespace="myplugins" id="mypluginmap"> The base_prefix attribute can be used, if all your plugins reside in a common directory. Basically, then you can omit that directory from the filenames specified below. It safe to leave this at "". As you will see below, all plugins get a unique identifier, id. The namespace is a way to organize those IDs, and make it less likely to create a duplicate identifier accidentally. Internally, basically the namespace and then a :: gets prepended to all the identifiers you specify in this &pluginmap;. In general, if you intend to distribute your plugins in an R package, it is a good idea to use the package name as namespace parameter. Plugins shipped with the official &rkward; distribution have namespace="rkward". The id attribute is optional, but specifying an id for your &pluginmap; makes it possible for other people to make their &pluginmap;s load your &pluginmap;, automatically (see the section on dependencies). <components> Components? Aren't we talking about plugins? Yes, but in the future, plugins will be no more than a special class of components. What we do here, then, is to register all components/plugins with &rkward;. Let's look at an example entry: <component type="standard" id="t_test_two_vars" file="t_test_two_vars.xml" label="Two Variable t-Test" /> First the type attribute: Leave this to "standard" for now. Further types are not yet implemented. The id we've already hinted at. Each component has to be given a unique (in its namespace) identifier. Pick one that is easily recognizable. Avoid spaces and any special characters. Those are not banned, so far, but might have special meanings. With the file attribute, you specify where the description of the actual plugin itself is located. This is relative to the directory the &pluginmap; file is in, and the base_prefix above. Finally, give the component a label. This label will be shown whereever the plugin is placed in the menu (or in the future perhaps in other places as well). Typically a &pluginmap; file will contain several components, so here are a few more: <component type="standard" id="unimplemented_test" file="means/unimplemented.xml" /> <component type="standard" id="fictional_t_test" file="means/ttests/fictional.xml" label="This is a fictional t-test" /> <component type="standard" id="descriptive" file="descriptive.xml" label="Descriptive Statistics" /> <component type="standard" id="corr_matrix" file="corr_matrix.xml" label="Correlation Matrix" /> <component type="standard" id="simple_anova" file="simple_anova.xml" label="Simple Anova" /> </components> Ok, this was the first step. &rkward; now knows those plugins exist. But how to invoke them? They need to be placed in a menu hierarchy: <hierarchy> <menu id="analysis" label="Analysis"> Right below the <hierarchy> tag, you start describing, in which <menu> your plugins should go. With the above line, you basically say, that your plugin should be in the Analysis menu (not necessarily directly there, but in a submenu). The Analysis menu is standard in &rkward;, so it does not actually have to be created from scratch. However, if it did not exist yet, using the label attribute you'd give it its name. Finally, the id once again identifies this <menu>. This is needed, so several &pluginmap; files can place their plugins in the same menus. They do this by looking for a <menu> with the given id. If the ID does not yet exist, a new menu will be created. Otherwise the entries will be added to the existing menu. <menu id="means" label="Means"> Basically the same thing here: Now we define a submenu to the Analysis menu. It is to be called Means. <menu id="ttests" label="t-Tests"> And a final level in the menu hierarchy: A submenu of the submenu Means. <entry component="t_test_two_vars" /> Now, finally, this is the menu we want to place the plugin in. The <entry> tag signals, this actually is the real thing, instead of another submenu. The component attribute refers to the id you gave the plugin/component above. <entry component="fictional_t_test" /> </menu> <entry component="fictional_t_test" /> </menu> <menu id="frequency" label="Frequency" index="2"/> In case you have lost track: This is another submenu to the Analysis menu. See the screenshot below. We'll skip some of what's not visible, marked with [...]. [...] </menu> <entry component="corr_matrix"/> <entry component="descriptive"/> <entry component="simple_anova"/> </menu> These are the final entries visible in the screenshots below. <menu id="plots" label="Plots"> [...] </menu> Of course you can also place your plugins in menus other than Analysis. <menu id="file" label="File"> [...] </menu> Even in standard-menus such as File. All you need is the correct id. </hierarchy> </document> That's how to do it. And this screenshot shows the result: Menu hierarchy created by the code shown above Menu hierarchy created by the code shown above Confused? The easiest way to get started is probably taking some of the existing &pluginmap; files shipped with the distribution, and modifying them to your needs. Also, if you need help, don't hesitate to write to the development mailing list. Controlling the order of menu entries By default, all items (entries / submenus) inside a menu will be sorted alphabetically, automatically. In some cases you may want more control. In this case you can group elements as follows: You can define groups inside any menu like this. All elements belonging to the same group will be grouped together: <group id="somegroup"/> If you want the group to be visually separated from other entries, use: <group id="somegroup" separated="true"/> Entries, menus, and groups can be appended to a specified group, using: <entry component="..." group="somegroup"/> In fact, it is also possible to define groups (without separator lines) implicitly: <entry component="first" group="a"/> <entry component="third"/> <entry component="second" group="a"/> Group names are specific to each menu. Group "a" in menu "Data" does not conflict with group "a" in menu "Analysis", for example. The most common use case is defining groups at the top, or at the bottom of a menu. For this, there are pre-defined groups "top" and "bottom" in each menu. Entries within each group are sorted, alphabetically. Groups appear in the order of declaration (unless appended to another group, of course). Menus and entries without group specification logically form a group (""), too. Defining the GUI Defining a dialog In the previous chapter you've seen how to register a plugin with &rkward;. The most important ingredient was specifying the path to an XML file with a description of what the plugin actually looks like. In this chapter you'll learn how to create this XML file. After reading this chapter, have a look at the rkwarddev package as well. It provides some &r; functions to create most of &rkward;'s XML tags for you. Once again we'll walk you through an example. The example is a (slightly simplified) version of the two variable t-Test. <!DOCTYPE rkplugin> The doctype is not really interpreted, yet. Set it to rkplugin, anyway. <document> <code file="t_test_two_vars.js"/> All plugins generate some code. Currently the only way to do so is using JS, as detailed in the next chapter. This defines, where to look for the JS code. The filename is relative to the directory the plugin XML is in. <help file="t_test_two_vars.rkh"/> It is usually a good idea to also provide a help page for your plugin. The filename of that help page is given, here, relative to the directory, the plugin XML is in. Writing help pages is documented here. If you do not provide a help file, omit this line. <dialog label="Two Variable t-Test"> As you know, plugins may have either a dialog or a wizard interface or both. Here we start defining a dialog interface. the label attribute specifies the caption of the dialog. <tabbook> <tab label="Basic settings"> GUI elements can be organized using a tabbook. Here we define a tabbook as the first element in the dialog. Use <tabbook>[...]</tabbook> to define the tabbook and then for each page in the tabbook use <tab>[...]</tab>. The label attribute in the <tab> element allows you to specify a caption for that page of the tabbook. <row id="main_settings_row"> The <row> and <column> tags specify the layout of the GUI elements. Here you say, that you'd like to place some elements side-by-side (left to right). The id attribute is not strictly necessary, but we'll use it later on, when adding a wizard interface to our plugin. The first element to place in the row, is: <varselector id="vars"/> Using this simple tag you create a list from which the user can select variables. You have to specify an id for this element, so &rkward; knows how to find it. You may NOT use a dot (.) in the id string. <column> Next, we nest a <column> inside the row. That is the following elements will be placed above each other (top-to-bottom), and all will be to the right of the <varselector>. <varslot types="number" id="x" source="vars" required="true" label="compare"/> <varslot types="number" id="y" source="vars" required="true" label="against" i18n_context="compare against"/> These elements are the counterpart to the <varselector>. They represent slots into which the user can put variables. You will note that the source is set to the same value as the id of the <varselector>. This means, the <varslot>s will each take their variables from the varselector. The <varslot>s also have to be given an id. They may have a label, and they may be set to required. This means that the Submit button will not be enabled until the <varslot> holds a valid value. Finally the type attribute is not interpreted yet, but it will be used to take care that only the correct types of variables will be allowed in the <varslot>. In case you are wondering about the i18n_context-attribute: This is to provide context to help the correct translation of the word "against", used as the <varslot>'s label, but does not affect the functionality of the plugin, directly. More on this in a separate chapter. <radio id="hypothesis" label="using test hypothesis"> <option value="two.sided" label="Two-sided"/> <option value="greater" label="First is greater"/> <option value="less" label="Second is greater"/> </radio> Here, you define a group of <radio> exclusive buttons. The group has a label and an id. Each <option> (button) has a label and is assigned a value. This is the value the <radio> element will return when the option is selected. </column> </row> </tab> Each tag has to be closed. We've put all the elements we wanted (the two <varslots> and the <radio>) in the <column>. We put all elements we wanted (the <varselector> and the <column> with those elements) in the <row>. And we've put all the elements we wanted into the first page in the <tabbook>. We're not yet done defining the <tabbook> (more pages to come), and of course there's more to come in the <dialog>, too. But this screenshot is basically what we've done so far: t-Test plugin t-Test plugin Note that we have not specified the Submit, Close, &etc; buttons or the code view. Those elements get generated automatically. But of course we still have to define the second page of the <tabbook>: <tab label="Options"> <checkbox id="varequal" label="assume equal variances" value=", var.equal=TRUE"/> By default elements will be placed top-to-bottom like in a <column>. Since that is what we want here, we don't have to explicitly state a <row> or <column> layout. The first element we define is a checkbox. Just like the <radio><option>s, the checkbox has a label and a value. The value is what gets returned, if the check box is checked. Of course the checkbox also needs an id. <frame label="Confidence Interval" id="frame_conf_int"> Here's yet another layout element: In order to signal that the two elements below belong together, we draw a <frame> (box). That frame may have a label (caption). Since the frame is just a passive layout element, it does not need an id, we still define one here, as we'll refer to it later, when defining an additional wizard interface. <checkbox id="confint" label="print confidence interval" value="1" checked="true"/> <spinbox type="real" id="conflevel" label="confidence level" min="0" max="1" initial="0.95"/> </frame> Inside the <frame> we place another <checkbox> (using checked="true", we signal that check box should be checked by default), and a <spinbox>. The spinbox allows the user to select a value between "min" and "max" with the default/initial value "0.95". Setting the type to "real" signals that real numbers are accepted as opposed to type="integer" which would accept integers only. It is also possible, and often preferable, to make the <frame> itself checkable, instead of adding a <checkbox> inside. See the reference for details. This is not done here, for illustrational purposes. </tab> </tabbook> </dialog> That's all for the second page of the <tabbook>, all pages in the <tabbook> and all elements in the <dialog>. We're finished defining what the dialog looks like. </document> Finally we close the <document> tag, and that's it. The GUI is defined. You can save the file now. But how does &r; syntax get generated from the GUI-settings? We'll deal with that in the next chapter. First, however, we'll look into adding a wizard interface, and some general considerations. Adding a wizard interface Actually we don't have to define an additional <wizard> interface, but here's how that would be done. To add a wizard interface, you'll add a <wizard> tag at the same level as the <dialog> tag: <wizard label="Two Variable t-Test"> <page id="firstpage"> <text>As a first step, select the two variables you want to compare against each other. And specify, which one you theorize to be greater. Select two-sided, if your theory does not tell you, which variable is greater.</text> <copy id="main_settings_row"/> </page> Some of this is pretty self explanatory: We add a <wizard> tag with a label for the wizard. Since a wizard can hold several pages that are shown one after another, we next define the first <page>, and put an explanatory <text> note in there. Then we use a <copy> tag. What this does, is really it saves us having to define yet again, what we already wrote for the <dialog>: The copy tag looks for another tag with the same id earlier in the XML. This happens to be defined in the <dialog> section, and is a <row> in which there are the <varselector>, <varslots> and the hypothesis <radio> control. All of this is copied 1:1 and inserted right at the <copy> element. Now to the second page: <page id="secondpage"> <text>Below are some advanced options. It's generally safe not to assume the variables have equal variances. An appropriate correction will be applied then. Chosing "assume equal variances" may increase test-strength, however.</text> <copy id="varequal"/> <text>Sometimes it's helpful to get an estimate of the confidence interval of the difference in means. Below you can specify whether one should be shown, and which confidence-level should be applied (95% corresponds to a 5% level of significance).</text> <copy id="frame_conf_int"/> </page> </wizard> Much of the same thing here. We add some texts, and in between that <copy> further sections from the dialog interface. You may of course make the wizard interface look very different to the plain dialog, and not use the <copy> tag at all. Be sure, however, to assign corresponding elements the same id in both interfaces. This is not only used to transfer settings from the dialog interface to the wizard interface and back, when the user switches interfaces (which does not yet happen in the current version of &rkward;), but also simplifies writing your code template (see below). Some considerations on GUI design This section contains some general considerations on which GUI elements to use where. If this is your first attempt of creating a plugin, feel free to skip over this section, as it isn't relevant to getting a basic GUI working. Come back here, later, to see, whether you can refine your plugin's GUI in some way or another. <radio> vs. <checkbox> vs. <dropdown> The three elements <radio>, <checkbox>, <dropdown>, all serve a similar function: To select one out of several options. Obviously, a check box only allows to choose between two options: checked or not checked, so you cannot use it, if there are more than two options to chose from. But when to use which of the elements? Some rules of thumb: If you find yourself creating a <radio> or <dropdown> with only two options, ask yourself, whether the question is essentially a yes / no type of question. E.g. a choice between adjust results and do not adjust results, or between remove missing values and keep missing values. In this case a <checkbox> is the best choice: It uses little space, will have the least words of labels, and is easiest to read for the user. There are very few situations where you should chose a <radio> over a <checkbox>, when there are only two options. An example of that might be: Method of calculation: 'pearson'/'spearman'. Here, more methods might be thinkable, and they don't really form a pair of opposites. Chosing between a <radio> and a <dropdown> is mostly a question of space. The <dropdown> has the advantage of using little space, even if there are a lot of options to chose from. On the other hand, a <radio> has the advantage of making all possible choices visible to the user at once, without clicking on the dropdown arrow. Generally, if there are six or more options to chose from, a <dropdown> is preferable. If there are five or less options, a <radio> is the better choice. Generating R code from GUI settings Using JavaScript in RKWard plugins Now we have a GUI defined, but we still need to generate some &r; code from that. For that, we need another text file, code.js, located in the same directory as the description.xml. You may or may not be familiar with JavaScript (or, to be technically precise: ECMA-script). Documentation on JS can be found in abundance, both in printed form, and on the Internet (⪚: https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide). But for most purposes you will not need to know much about JS at all, as we'll only use some very basic features. After reading this chapter, have a look at the rkwarddev package as well. It provides some &r; functions to create JavaScript code commonly used in &rkward;. It can also autodetect variables used in a plugin XML file and create basic JavaScript code from that for you to start with. Plugin .js files are assumed to be UTF-8 encoded. Be sure to check you editor's encoding, if using any non-ascii characters. For the two variable t-test, the code.js file looks as follows (with comments in between): preprocess() function preprocess () { } The JS file is organized into three separate functions: preprocess(), calculate(), and printout(). This is because not all code is needed at all stages. Currently the preprocess-function is not really used in many places (typically you will omit it altogether). calculate() function calculate () { echo ('res <- t.test (x=' + getString ("x") + ', y=' + getString ("y") + ', hypothesis="' + getString ("hypothesis") + '"' + getString ("varequal")); var conflevel = getString ("conflevel"); if (conflevel != "0.95") echo (', conf.level=' + conflevel); echo (')\n'); } This function generates the actual R syntax to be run from the GUI settings. Let's look at this in detail: The code to be used is generated using echo() statement. Looking at the echo() statement step by step, the first part of it is res <- t.test ( as plain text. Next we need to fill in the value, the user selected as the first variable. We fetch this using getString ("x"), and append it to the string to be echoed. This prints out the value of the GUI-element with id="x": our first <checkbox>. Next, we append a ', ', and do the same to fetch the value of the element "y" - the second <checkbox>. For the hypothesis (the <radio> group), and the equal variances <checkbox>, the procedure is very similar. Note that instead of concatenating the output snippets with +, you can also use several echo() statments. Everything is printed on a single line. To produce a line break in the generated code, insert a "\n" in the echoed string. In theory, you can even produce many lines with a single echo-statement, but please keep it to one line (or less) of generated code per echo(). Besides getString(), there are also functions getBoolean(), which will try to return the value as a logical (suitable for using in an if()-statement), and getList(), which will try to return list-like data in a JS Array(). We will show examples of those, later. When looking at existing plugins, you will also find plenty of plugins using getValue(), instead of getString(), and in fact the two are almost identical. However using getString(), getBoolean() and getList() is the recommended practice since version 0.6.1. It gets a little more tricky for the confidence level. For reasons of aesthetics, we don't want to explicitly specify the confidence level to use, if it corresponds to the default value. Hence, instead of printing the value unconditionally, we first fetch into a variable. Then we check, whether that variable differs from "0.95" and if so print out an additional argument. Finally, we echo a closing bracket and a line break: ")\n". That's all for the calculate function. printout() function printout () { echo ('rk.header (' + i18n ("Two Variable t-Test") + ')\n'); echo ('rk.print (res)\n'); } And this was all there is to the printout function in most cases. rk.header() prints a standard headline for the results. Note that in the .js files, you have to mark up all translatable strings by hand, using i18n(), or some alternative commands. More on this in the chapter on internationaliation. You can also add some more information to this, if you like, ⪚: function printout () { new Header (i18n ("Two Variable t-Test")) .addFromUI ("varequal") .add (i18n ("Confidence level"), getString ("conflevel")) // Note: written like this for illustration purposes. More automatic: // .addFromUI ("conflevel") .print (); echo ('rk.print (res)\n'); } rk.print() utilizes the R2HTML package to provide HTML formatted output. Another helpful function is rk.results(), which can also output different kinds of result tables. If in doubt, however, just use rk.print(), and be done with. The JS class Header is a JS level helper to generate a call to rk.header() (just take a look at the generated &r; code). In some cases you may want to call echo ('rk.header (...)') directly to print a header for your output. Note that internally, the output is just a plain HTML document at this point of time. Therefore you might be tempted to add custom HTML using rk.cat.output(). While this will work, please don't do this. The output format may change (⪚ to ODF) in the future, so it's best not to introduce HTML specific code. Rather keep things simple with rk.header(), rk.print(), rk.results(), and -- if needed -- rk.print.literal(). If those don't seem to satisfy your formatting needs, contact us on the mailing list for help. Congratulations! You created your first plugin. Read on in the next chapters for more advanced concepts. Conventions, policies, and background There are many ways to write &r; code for a certain task, and there are even more ways to generate this &r; code from JS. How exactly you do it, is left up to you. Still there are a number of considerations that you should follow, and background information you should understand. Understanding the <function>local()</function> environment More often than not you will have to create one or more temporary &r; objects in the code generated by your plugin. Normally, you do not want those to be placed in the user's workspace, potentially even overwriting user variables. Hence, all plugin generated code is run in a local() environment (see &r; help page on function local()). This means, all variables you create are temporary and will not be saved permanently. If the user explicitly asks for a variable to be saved, you will need to assign to that object using .GlobalEnv$objectname <- value. In general, do not use the <<- operator. It will not necessarily assign in .GlobalEnv. One important pitfall is using eval(). Here, you need to note that eval will by default use the current environment for evaluation, &ie; the local one. This will work well most of the times, but but not always. Thus, if you need to use eval(), you will probably want to specify the envir parameter: eval(..., envir=globalenv()). Code formatting The most important thing is for your generated &r; code to work, but it should be also easy to read. Therefore, please also keep an eye on formatting. Some considerations: Normal top-level &r; statements should be left aligned. Statements in a lower block should be indented with one tab (see example below). If you do very complex calculations, add a comment here and there, esp. to mark up logical sections. Note that there is a dedicated function comment() for inserting translatable comments in the generated code. For example, generated code might look like this. The same code without indentation or comments would be pretty hard to read, despite its modest complexity: # first determine the wobble and rotation my.wobble <- wobble (x, y) my.rotation <- wobble.rotation (my.wobble, z) # boggling method needs to be chosen according to rotation if (my.rotation > wobble.rotation.limit (x)) { method <- "foo" result <- boggle.foo (my.wobble, my.rotation) } else { method <- "bar" result <- boggle.bar (my.wobble, my.rotation) } Dealing with complex options Many plugins can do more than one thing. For instance, the Descriptive Statistics plugin can compute mean, range, sum, product, median, length, &etc; However, typically the user will only chose to have some of those calculations performed. In this case, please try to keep the generated code as simple as possible. It should only contain portions relevant to the options that are actually selected. To achieve this, here is an example of a common design patterns as you would use it (in JS; here, "domean", "domedian", and "dosd" would be <checkbox> elements): function calculate () { echo ('x <- <' + getString ("x") + ')\n'); echo ('results <- list ()\n'); if (getBoolean ("domean.state")) echo ("results$" + i18n ("Mean value") + " <- mean (x)\n"); if (getBoolean ("domedian.state")) echo ("results$" + i18n ("Median") + " <- median (x)\n"); if (getBoolean ("dosd.state")) echo ("results$" + i18n ("Standard deviation") + " <- sd (x)\n"); //... } Tips and tricks Here are a few assorted tricks which may make writing plugins less tedious: If you need the value of a GUI setting at several places in you plugin's code, consider assigning it to a variable in JS, and using that instead of fetching it again and again with getString()/getBoolean()/getList(). This is faster, more readable, and less typing all at the same time: function calculate () { var narm = ""; // na.rm=FALSE is the default in all functions below if (getBoolean ("remove_nas")) { $narm = ", na.rm=TRUE"; } // ... echo ("results$foo <- foo (x" + narm + ")\n"); echo ("results$bar <- bar (x" + narm + ")\n"); echo ("results$foobar <- foobar (x" + narm "\n"); // ... } Writing a help page When your plugin basically works, the time has come to provide a help page. While typically you will not want to explain all the underlying concepts in depth, you may want to add some more explanation for some of the options, and link to related plugins and &r; functions. After reading this chapter, have a look at the rkwarddev package as well. It provides some &r; functions to create most of &rkward;'s XML tags for you. It's also capable of creating basic help file skeletons from existing plugin XML files for you to start with. You may recall putting this inside your plugin XML (if you haven't put this in, do so now): <document> [...] <help file="filename.rkh" /> [...] </document> Where, obviously, you'd replace filename with a more appropriate name. Now it's time to create this .rkh file. Here is a self-descriptive example: <!DOCTYPE rkhelp> <document> <summary> In this section, you'll put some short, very basic information about what the plugin does. This section will always show up on the very top of the help page. </summary> <usage> The usage section may contain a little more practical information. It does not explain all the settings in detail (that's done in the "settings" section), however. To start a new paragraph, insert an empty line, as shown above. This line, in contrast, will be in the same paragraph. In all sections you can insert some simple HTML code, such as <b>bold</b> or <i>italic</i> text. Please keep formatting to the minimum needed, however. The usage section is always the second section shown in a help page. </usage> <section id="sectionid" title="Generic section" short_title="Generic"> If you need to, you can add additional sections between the usage and settings sections. However, usually, you will not need this while documenting plugins. The "id"-attribute provides an anchor point to jump to this section from the navigation menu. The "short_title" attribute provides a short title to use in the navigation bar. This is optional, by default the main "title" will be used both as a heading to the section, and as the link name in the navigation bar. In any section you may want to insert links to further information. You do this by adding <link href="URL">link name</link> Where URL could be an external link such as http://rkward.kde.org . Several special URLs are supported in the help pages: <link href="rkward://page/path/page_id"/> This links to a top level rkward help page (not for a plugin). <link href="rkward://component/[namespace/]component_id"/> This links to the help page of another plugin. The [namespace/] part may be omitted (in this case, rkward is assumed as the standard namespace, ⪚: <link href="rkward://component/import_spss"/> or <link href="rkward://component/rkward/import_spss"/> are equivalent). The component_id is the same that you specified in the &pluginmap;. <link href="rkward://rhelp/rfunction"/> Links to the R help page on "rfunction". Note that the link names will be generated automatically for these types of links. </section> <settings> <caption id="id_of_tab_or_frame"/> <setting id="id_of_element"> Description of the GUI element identified by the given id </setting> <setting id="id_of_elementb" title="description"> Usually the title of the GUI element will be extracted from the XML definition of the plugin, automatically. However, for some GUI elements, this description may not be enough to identify them, reliably. In this case, you can add an explicit title using the "title" attribute. </setting> <setting id="id_of_elementc"> Description of the GUI element identified by "id_of_elementc" </setting> [...] </settings> <related> The related section typically just contains some links, such as: <ul> <li><link href="rkward://rhelp/mean"/></li> <li><link href="rkward://rhelp/median"/></li> <li><link href="rkward://component/related_component"/></li> </ul> </related> <technical> The technical section (optional, always last) may contain some technical details of the plugin implementation, which are of interest only to RKWard developers. This is particularly relevant for plugins that are designed to be embedded in many other plugins, and could detail, which options are available to customize the embedded plugin, and which code sections contain which R code. </technical> </document> Logic interactions between GUI elements GUI logic All the basic concepts of creating a plugin for &rkward; have been described in the previous chapters. Those basic concepts should be sufficient for many -- if not most -- cases. However, sometimes you want more control over how your plugin's GUI behaves. For instance, suppose you want to extend the t-test example used in this documentation to allow both: comparing a variable against another variable (as shown), and comparing a variable against a constant value. Now, one way of doing this would be to add a radio-control that switches between the two modes, and adding a spinbox to enter the constant value to compare against. Consider this simplified example: <!DOCTYPE rkplugin> <document> <code file="code.js"/> <dialog label="T-Test"> <row> <varselector id="vars"/> <column> <varslot id="x" types="number" source="vars" required="true" label="compare"/> <radio id="mode" label="Compare against"> <option value="variable" checked="true" label="another variable (select below)"/> <option value="constant" label="a constant value (set below)"/> </radio> <varslot id="y" types="number" source="vars" required="true" label="variable" i18n_context="Noun; a variable"/> <spinbox id="constant" initial="0" label="constant" i18n_context="Noun; a constant"/> </column> </row> </dialog> </document> So far so good, but there are a number of problems with this GUI. First, both the varslot and the spinbox are always shown, whereas only one of the two is really used. Worse, the varslot always requires a valid selection, even if you compare against a constant. Obviously, if we create a multi-purpose GUI like this, we want more flexibility. Enter: the <logic> section (inserted at the same level as <code>, <dialog>, or <wizard>). [...] <code file="code.js"/> <logic> <convert id="varmode" mode="equals" sources="mode.string" standard="variable" /> <connect client="y.visible" governor="varmode" /> <connect client="constant.visible" governor="varmode.not" /> </logic> <dialog label="T-Test"> [...] The first line inside the logic section is a <convert> tag. Basically, this provides a new boolean (on or off, true or false) property, which can be used later on. This property ("varmode") is true, whenever the upper radio button is selected and false whenever the lower radio button is selected. How is this done? First, under sources, the source properties to work on are listed (in this case only one each; you could list several as sources="mode.string;somethingelse", then "varmode" would only be true, if both "mode.string" and "somethingelse" are equal to the string "variable"). Note that in this case we don't just write "mode" (as we would in getString("mode"), but "mode.string". This is actually the internal way a radio control works: It has a property string, which holds its string value. getString("mode") is just a shorthand, and equivalent to getString("mode.string"). See the reference for all properties of the different GUI elements. Second, we set the mode of conversion to mode="equals". This means, we want to check, whether the source(s) is (are) equal to a certain value. Finally standard is the value to compare against, so with standard="variable", we check whether the property "mode.string" is equal to the string "variable" (the value of the upper radio option). If it is equal, then the property varmode is true, else it is false. Now to the real stuff: We <connect> the "varmode" property to y.visible, which controls whether the varslot "y" is shown or not. Note that any element which is made invisible is implicitly non-required. Thus, if the upper radio-option is selected, the varslot "y" is required, and visible. Else it is not required and hidden. For the spinbox, we want the exact reverse. Fortunately, we do not need another <convert> for this: Boolean properties can be negated very easily by appending the modifier "not", so we <connect> "varmode.not" to the spinbox's visibility property. In effect, either the varslot is shown and required, or the spinbox is shown and required - depending on which option is selected in the radio control. The GUI is changing itself according to the radio option. Try the example, if you like. For a complete list of properties, refer to the reference. One more property, however, is special in that all GUI elements have it: enabled. This is slightly less drastic that visible. It does not show/hide the GUI element, but only enables/disables it. Disabled elements are typically shown grayed out, and do not react to user input. Besides <convert> and <connect>, there are several further elements for use in the <logic> section. E.g. conditional constructs can also be implemented using the <switch>-element. Refer to the reference on logic elements for details. Scripted GUI logic While connecting properties as described above is often enough, sometimes it is more flexible or more convenient to use JS to script the GUI logic. In this way, the above example could be re-written as: [...] <code file="code.js"/> ' <logic> <script><![CDATA[ // ECMAScript code in this block // the top-level statement is only called once gui.addChangeCommand ("mode.string", "modeChanged ()"); // this function is called whenever the "mode" was changed modeChanged = function () { var varmode = (gui.getString ("mode.string") == "variable"); gui.setValue ("y.enabled", varmode); gui.setValue ("constant.enabled", !varmode); } ]]></script> </logic> <dialog label="T-Test"> [...] The first line of code tells &rkward; to call the function modeChanged() whenever the value of the id="mode" radio box changes. Inside this function, we define a helper-variable "varmode" which is true when the mode is "variable", false is it is "constant". Then we use gui.setValue() to set the and enabled properties of "y" and "constant", in just the same way as we did using <connect> statements, before. The scripted approach to GUI logic becomes particularly useful when you want to change the available option according to the type of object that the user has selected. See the reference for available functions. Note that the scripted approach to GUI logic can be mixed with <connect> and <convert>-statements if you like. Also note that the <script> tag allows to specify a script file name in addition to or as an alternative to inlining the script code. Typically, inlining the script code as shown above is most convenient, however. Embedding Plugins into Plugins Use cases for embedding When writing plugins, you will often find that you're creating a number of plugins that only differ in some respects, but have a lot more in common. For instance, for plotting, there are a number of generic &r; options that can be used with mostly all types of plots. Should you create a GUI and JS-template for those over and over again? Obviously that would be quite a hassle. Fortunately, you don't have to do that. Rather you create the common functionality once, and later you can embed it into several plugins. In fact it is possible to embed any plugin into any other plugin, even if the original author of the embedded plugin never thought, somebody would want to embed their plugin into another one. Embedding inside a dialog Ok, enough said. How does it work? Simple: Just use the <embed> tag. Here's a stripped down example: <dialog> <tabbook> <tab [...]> [...] </tab> <tab label="Plot Options" i18n_context="Options concerning the plot"> <embed id="plotoptions" component="rkward::plot_options"/> </tab> <tab [...]> [...] </tab> </tabbook> </dialog> What happens here, is that the entire GUI or the plot options plugin (except of course for the standard elements like Submit button, &etc;) is embedded right into your plugin (try it!). As you can see the syntax of the <embed>-tag is fairly simple. It takes an id as most elements. The parameter component specifies which plugin to embed, as defined in the &pluginmap; file ("rkward::plot_options" is the result of concatenating the namespace rkward, a separator ::, and the name of the component plot_options). Code generation when embedding So far so good, but what about the generated code? How are the code for the embedding and embedded plugin merged? In the embedding plugin's JS code, simply write something like this: function printout () { // ... echo ("myplotfunction ([...]" + getString ("plotoptions.code.printout"); + ")\n"); // ... } So essentially, we're fetching the code generated by the embedded plugin just like we're fetching any other GUI setting. Here the string "plotoptions.code.printout" can be deparsed to: The printout section of the generated code of the element with the id plotoptions (plotoptions is the ID we gave for the <embed> tag above). And yes, if you want advanced control, you can even fetch the values of individual GUI elements inside the embedded plugin (but not the other way around, as the embedded plugin does not know anything about its surroundings). Embedding inside a wizard If your plugin provides a wizard GUI, embedding works basically in the same way. You'll generally use: <wizard [...]> [...] <page id="page12"> [...] </page> <embed id="plotoptions" component="rkward::plot_options"/> <page id="page13"> [...] </page> [...] </wizard> If the embedded plugin provides a wizard interface, its pages will be inserted right between "page12" and "page13" of your plugin. If the embedded plugin provides a dialog interface only, a single new page will be added between your pages "page12" and "page13". The user will never notice. Less embedded embedding: Further Options button While embedding is cool, you should be careful not to overdo it. Too many functions inside a GUI just make it hard to find the relevant options. Of course, sometimes you may want to embed a great deal of options (like all the options to plot()), but as those are really optional, you don't want them prominently in your GUI. An alternative is to embed those options as a button: <dialog> <tabbook> [...] <tab label="Options"> [...] <embed id="plotoptions" component="rkward::plot_options" as_button="true" label="Specify plotting options"/> </tab> [...] </tabbook> </dialog> In this case, a single push button will be added to your plugin, labelled Specify plotting options. When you press that button, a separate dialog will come up, with all the options of the embedded plugin. Even while this embedded GUI is not visible most of the time, you can fetch its settings just as described above. Probably the button approach should only ever be used for plugins that can never be invalid (for missing/bad settings). Otherwise the user would not be able to submit the code, but might have a hard time finding out, the reason for that is hidden behind some button. Embedding/defining incomplete plugins Some plugins -- and as a matter of fact, the plot_options used as an example above, is one of them -- are not complete by themselves. They simply do not have the GUI elements to select some important values. They are meant to be used only embedded into other plugins. In how far is the plot_options plugin incomplete? Well, for some option settings, it needs to know the name of the objects/expressions for the x and y axes (actually it will do fine if it only has either, but it needs at least one to function properly). However, it does not have a mechansim of selecting those objects, or entering them any other way. So how does it know about them? In the logic section of the plot_options plugin there are two additional lines, not covered, yet: <logic> <external id="xvar" /> <external id="yvar" /> [...] </logic> This defines two additional properties in the plot_options plugin, whose sole purpose is to be connected to some (yet unknown) properties of the embedding plugin. In the plot_options plugin those two properties are simply used like any other, and for instance there are calls to getString("xvar") in the plot_options JS template. Now, for the incomplete plugin there is no way of knowing, where it will be embedded, and what the relevant settings in the embedding plugin will be called. So we need to add two additional lines in the embedding plugin's logic section as well: <logic> [...] <connect client="plotoptions.xvar" governor="xvarslot.available" /> <connect client="plotoptions.yvar" governor="yvarslot.available" /> </logic> This is nothing new in principle, we've covered <connect> statements in the chapter of GUI logic. You simply connect the values in two varlots (called "xvarslot" and "yvarslot" in this example) to the receiving external properties of the embedded plugin. That's it. Everything else is taken care of automatically. Dealing with many similar plugins Overview on different approaches Sometimes, you may wish to develop plugins for a series of similar functions. As an example, consider the distribution plots. These generate fairly similar code, and of course it's desirable to make the graphical interfaces look similar to each other. Finally large sections of the help files can be identical. Only a few parameters are different for each plugin. The naive approach to this is to develop one plugin, then basically copy and paste the entire contents of the .js, .xml, and .rkh files, then changing the few portions that are different. However, what if sometime later you find a spelling mistake that has been copied and pasted to all plugins? What if you want to add support for a new feature? You'd have to visit all plugins again, and change each single one. A tiresome and tedious process. A second approach would be to use embedding. However, in some cases this does not lend itself well to the problem at hand, mostly because the chunks you can embed are sometimes too large to be useful, and it places some constraints on the layout. For these cases, the concepts including .js files including .xml files and snippets can be very useful (but see the thoughts on when it is preferable to use embedding). One word of caution, before you begin reading, though: These concepts can help making it simpler to deal with many similar plugins, and can improve maintainability and readability of those plugins. However, overdoing it can easily lead to the reverse effect. Use with some caution. Using the JS include statement You can easily include one script file into another in &rkward; plugins. The value of this becomes immediately obvious, if some sections of your JS code are similar across plugins. You can simply define those sections in a separate .js file, and include this in all the plugin .js files. For example, as in: // this is a file called "common_functions.js" function doCommonStuff () { // perhaps fetch some options, etc. // ... comment ("This is R code you want in several different plugins\n"); // ... } // this is one of your regular plugin .js files // include the common functions include ("common_functions.js"); function calculate () { // do something // ... // insert the common code doCommonStuff (); } Note that sometimes it's even more useful to reverse this, and define the skeleton of preprocess(), calculate(), and printout() functions is a common file, and make these call back for those part which are different across plugins. E.g.: // this is a file called "common_functions.js" function calculate () { // do some things which are the same in all plugins // ... // add in something that is different across plugins getSpecifics (); // ... } // this is one of your regular plugin .js files // include the common functions include ("common_functions.js"); // note: no calculate() function is defined in here. // it in the common_functions.js, instead. function getSpecifics () { // print some R code } One issue you should be aware of when using this technique is variable scoping. See the JS manual on variable scopes. This technique is heavily used in the distribution plot and distribution CLT plot plugins, so you may want to look there for examples. Including <literal role="extension">.xml</literal> files Basically the same feature of including files is also available for use in the .xml, &pluginmap; and .rkh files. At any place in these files you can place an <include> tag as shown below. The effect is that the entire contents of that XML file (to be precise: everything within the <document> tag of that file) is included verbatim at this point in the file. Note that you can only include another XML file. <document> [...] <include file="another_xml_file.xml"/> [...] </document> The attribute file is the filename relative to the directory the current file is located in. Using <snippets> While including files as shown in the previous section is fairly powerful, it become most useful when used in combination with <snippets>. Snippets are really smaller sections which you can insert at another point in the file. An example illustrates this best: <document> <snippets> <snippet id="note"> <frame> <text> This will be inserted at two places in the GUI </text> </frame> </snippet> </snippets> <dialog label="test"> <column> <insert snippet="note"/> [...] <insert snippet="note"/> </column> </dialog> </document> Hence, you define the snippet at one place at the top of the XML file, and then you <insert> it at any place(s) you wish. While this example is not too useful in itself, think about combining this with an <include>d .xml file. Note that you can also place snippets for the .rkh file in the same file. You'd simply <include> the file there as well, and <insert> the relevant snippet: <!-- This is a file called "common_snippets.xml" --> <document> <snippet id="common_options"> <spinbox id="something" [...]/> [...] </snippet> <snippet id="common_note"> <text>An important note for this type of plugin</text> </snippet> <snippet id="common_help"> <setting id="something">This does something</setting> [...] </snippet> </document> <!-- This is the .xml file of the plugin --> <document> <snippets> <!-- Import the common snippets --> <include file="common_snippets.xml"/> </snippets> <dialog label="test2"> <insert snippet="common_note"/> <spinbox id="something_plugin_specific" [...] /> <insert snippet="common_options"/> </dialog> </document> Similar to inclusion in JS, the reverse approach is often even more useful: <!-- This is a file called "common_layout.xml" --> <document> <column> <insert snippet="note"> [...] <insert snippet="plugin_parameters"> </column> [...] </document> <!-- This is the .xml file of the plugin --> <document> <snippets> <snippet id="note"> <text>The note used for this specific plugin</text> </snippet> <snippet id="plugin_parameters"> <frame label="Parameters specific to this plugin"> [...] </frame> </snippet> </snippets> <dialog label="test3"> <include file="common_layout.xml"/> </dialog> </document> Finally, it is also possible to <insert> snippets into other snippets, provided that: a) there is only one level of nesting, and b) the <snippets> section is placed at the top of the file (before a nested snippet is inserted); this is because <insert> statements are resolved from top to bottom. <include> and <snippets> vs. <embed> At first glance, <include> and <snippets> provides functionality rather similar to embedding: It allows to reuse some portions of code across plugins. So what's the difference between these approaches, and when should you use which? The key difference between these concepts is that embeddable plugins are a more tight bundle. They combine a complete GUI, code to generate &r; code from this, and a help page. In contrast, include and insert allow much more fine grained control, but at the price of less modularity. That is, a plugin embedding another plugin will typically not need to know much about the internal details of the embedded plugin. A prime example is the plot_options plugin. Plugins wishing to embed this do not necessarily need to know about all the options provided, or how they are provided. This is a good thing, as otherwise a change in the plot_options plugin might make it necessary to adjust all plugins embedding this (a lot). In contrast, include and insert really exposes all the internal details, and plugins using this will -- for example -- need to know the exact ids and perhaps even the type of the elements used. Hence the rule of thumb is this: include and insert are great if the relevant options are only needed for a clearly limited group of plugins. Embedded plugins are better, if the group of plugins it may be useful to is not clearly defined, and if the functionality can easily be modularized. Another rule of thumb: If you can put the common portions into a single chunk, then do so, and use embedding. If you need lots of small snippets to define the common portions -- well, use <snippets>. A final way to look at it: If all plugins provide highly similar functionality, includes and inserts are probably a good idea. If they merely share one or two common modules, embedding is likely better. Concepts for use in specialized plugins This chapter contains information on some topics that are useful only to certain classes of plugins. Plugins than produce a plot Creating a plot from a plugin is easy to do. However, there are a few sublte gotchas to avoid, and also some great generic functionality that you should be aware of. This section shows you the basic concepts, and concludes with a canonical example that you should follow whenever creating plot plugins. Drawing a plot to the output window In order to draw a plot to the output window, use rk.graph.on() directly before creating the plot, and rk.graph.off(), directly afterwards. This is similar to ⪚ callling postscript() and dev.off() in a regular R session. Importantly, however, you must always call rk.graph.off() after calling rk.graph.on(). Otherwise the output file will be left in a broken state. To ensure rk.graph.off() really gets called, you should wrap all R commands between the two calls in try() statement. Never heard of that? Don't worry, it's easy. All you need to do is follow the pattern shown in example, below. Adding preview functionality A very useful feature for all plugins generating a plot/graph is to provide an automatically updating preview. To do so, you will need two things: Adding a <preview> check box to your GUI definition, and adjusting the generated code for the preview. Adding a <preview> check box is simple. Just place the following somewhere in your GUI. It will take care of all the behind-the-scenes magic of creating a preview device, updaing the preview whenever the setting have changed, &etc; Example: <document> [...] <dialog [...]> [...] <preview id="preview"/> [...] </dialog> [...] </document> And that's it for the GUI definition. Adjusting the JS template is a little more work. You will have to create a new function called preview() in addition to the preprocess(), calculate(), &etc; functions. This function should generate the code needed to produce the plot, and only that. Esp. no printing of headers, rk.graphics.on(), or similar calls. See the example, below for the typical pattern that you will use. Generic plot options You will have noticed that most plotting plugins in RKWard provide a wide range of generic options ⪚ for customizing axis titles or figure margins. Adding these options to your plugin is easy. They are provided by an embeddable plugin called rkward::plot_options. Embed this in your plugin UI like this: <document> [...] <logic [...]> <connect client="plotoptions.xvar" governor="x.available"/> <set id="plotoptions.allow_type" to="true"/> <set id="plotoptions.allow_ylim" to="true"/> <set id="plotoptions.allow_xlim" to="false"/> <set id="plotoptions.allow_log" to="false"/> <set id="plotoptions.allow_grid" to="true"/> </logic> <dialog [...]> [...] <embed id="plotoptions" component="rkward::plot_options" as_button="true" label="Plot Options"/> [...] </dialog> [...] </document> This will add a button to your UI to bring up a window with plot options. The logic section is just an example. It allows you some control over the plot options plugin. Read more in the plot_options plugin's help page (linked from the help page of any plugin providing the generic options). Next you need to make sure that the code corresponding to your plot options is added to the generated code for your plot. To do so, fetch the properties code.preprocess, code.printout, and code.calculate from the embedded plot options plugin, and insert them into your code as shown in the example, below. A canonical example Here's an example .JS file that you should use as a template, whenever you create a plotting plugin: function preprocess () { // the "somepackage" is needed to create the plot echo ("require (somepackage)\n"); } function printout () { // all the real work is moved to a custom defined function doPrintout (), below doPrintout (true); // in this case, 'true' means: We want all the headers that should be printed in the output, not just the preview } function preview () { // we call all stages of the general code. Only the printout () function needs to be slightly different for the plot preview preprocess (); // calculate (); // in this example, the plugin has no calculate () function. doPrintout (false); // in this case, 'false' means: Create the plot, but not any headers or other output. } function doPrintout (full) { // this function takes care of generating the code for the printout() section. If "full" is set to true, // it generates the full code, including headers. If full is set to false, only the essentials will // be generated. if (full) { echo ('rk.header (' + i18n ("An example plot") + ')\n\n'); echo ('rk.graph.on ()\n'); } // only the following section will be generated for full==false // remember: everything between rk.graph.on() and rk.graph.off() should be wrapped inside a try() statement: echo ('try ({\n'); // insert any option-setting code that should be run before the actual plotting commands. // The code itself is provided by the embedded plot options plugin. printIndentedUnlessEmpty() takes care of pretty formatting. printIndentedUnlessEmpty ('\t', getString ("plotoptions.code.preprocess"), '', '\n'); // create the actual plot. plotoptions.code.printout provides the part of the generic plot options // that have to be added to the plotting call, itself. echo ('plot (5, 5' + getString ("plotoptions.code.printout") + ')\n'); // insert any option-setting code that should be run after the actual plot. printIndentedUnlessEmpty ('\t', getString ("plotoptions.code.calculate"), '\n'); echo ('})'\n); // the closure of the try() statement if (full) { echo ('rk.graph.off ()\n'); } } Context-dependent plugins So far we have assumed, all plugins are always meaningful, and all placed in the main menu. However, some plugins are meaningful only (or additionally) in a certain context. For instance a plugin to export the contents of an &r; X11 graphics device is obviously most useful, when placed in the menu of an X11 device, not in the main menubar. Also, such a plugin should know about the device number that it should operate on, without having to ask the user about this. We call such plugins context-dependent. Correspondingly, in the &pluginmap; file, they are not (or not only) placed in the main <hierarchy> but rather into a <context> element. So far only two different contexts are supported (more will come later): x11 and file import. We'll deal with those in turn. Even if you are only interested in the import context, please also read the section on the x11 context, as this is slightly more elaborate. X11 device context To use a plugin in the context of an x11 device - that is place it in the menubar of the window you get when you call x11() in the console, first declare it as usual in the &pluginmap; file: <document [...]> <components> [...] <component id="my_x11_plugin" file="my_x11_plugin.xml" label="An X11 context plugin"/> [...] </components> However, you do not need to define it in the hierarchy (you can, if it is also meaningful as a top-level plugin): <hierarchy> [...] </hierarchy> Instead, add a definition of the "x11" context, and add it to the menus there: <context id="x11"> [...] <menu id="edit"> [...] <entry id="my_x11_plugin"/> </menu> </context> </document> In the logic section of the plugin xml, you can now declare two <external> properties: devnum and context. context (if declared) will be set to "x11" when the plugin is invoked in this context. devnum will be set to the number of the graphics device to operate on. And that's all. Import data context Before reading this section, please make sure to read the section on the X11 device context, as that explains the basic concepts. The "import" context is used to declare import file filter plugins. You simply place those in a context with id="import" in the &pluginmap; file. However, there is one additional twist when declaring these plugins: In order to offer a unified file selection dialog for all supported file types, you need to declare one additional bit of information on your component: <document [...]> <components> [...] <component id="my_xyz_import_plugin" file="my_xyz_import_plugin.xml" label="Import XYZ files"> <attribute id="format" value="*.xyz *.zyx" label="XYZ data files"/> </component> [...] </components> <hierarchy> [...] </hierarchy> <context id="import"> [...] <menu id="import"> [...] <entry id="my_xyz_import_plugin"/> </menu> </context> [...] </document> The attribute line simply says, that the associate filename extensions for XYZ files are *.xyz or *.zyx, and that the filter should be labelled XYZ data files in the file selection dialog. You can declare two <external> properties in your plugin. filename will be set to the selected file name, and context will be set to "import". Querying R for information In some cases, you may want to fetch further information from R, to be presented in your plugin's UI. For instance, you may want to offer a selection of the levels of a factor that the user has selected for analysis. Since version 0.6.2 or &rkward; it is possible to do so. Before we start, it is important that you are aware of some caveats: R Code run from inside the plugin's UI logic is evaluated in R's event loop, meaning they can be run while other computations are running. This is to make sure your plugin's UI will be usable, even while R is busy doing other things. However, this makes it really important, that your code does not have side effects. In particular: Do not make any assignments in .GlobalEnv or any other non-local environment. Do not print anything to the output file. Do not plot anything on-screen. In general, do not do anything that has side-effects. Your code may read in information, not "do" anything. With this in mind, here's the general pattern. You will use this inside a scripted UI logic section: <script><![CDATA[ last_command_id = -1; gui.addChangeCommand ("variable", "update ()"); update = function () { gui.setValue ("selector.enabled", 0); variable = gui.getValue ("variable"); if (variable == "") return; last_command_id = doRCommand ('levels (' + variable + ')', "commandFinished"); } commandFinished = function (result, id) { if (id != last_command_id) return; // another result is about to arrive if (typeof (result) == "undefined") { gui.setListValue ("selector.available", Array ("ERROR")); return; } gui.setValue ("selector.enabled", 1); gui.setListValue ("selector.available", result); } ]]></script> Here, variable is a property holding an object name (⪚ inside a <varslot>). Whenever that changes, you will want to update the display of levels inside the <valueselector>, named selector. The key function here is doRCommand(), taking as first parameter the command string to run, and as second parameter the name of a function to call, when the command has finished. Note that the command is running asynchronously, and this makes things a bit more complex. For one thing you want to make sure, that your <valueselector> remains disabled, while it does not contain up-to-date information. Another thing is that you could potentially have queued more than one command, before you get the first results. This is why every command is given an "id", and we store that in last_command_id for later reference. When the command is done, the specified callback is called (commandFinished, in this case) with two parameters: The result itself, and the id of the correspoding command. The result will be of a type resembling the representation in R, &ie; a numeric Array, if the result is numeric, &etc; It can even be an R list(), but in this case it will be represented as a JS Array() whithout names. Note that even this example is somewhat simplified. In reality you should take additional precautions, ⪚ to avoid putting an extreme amount of levels into the selector. The good news is that probably you do not have to do all this yourself. The above example is taken from the rkward::level_select plugin, for instance, which you can simply embed in your own plugin. This even allows you to specify a different expression to run in place of levels(). Referencing the current object For many plugins it is desirable to work on the current object. For instance a sort plugin could pre-select the data.frame that is currently being edited for sorting. The name of the current object is available to plugins as a pre-defined property called current_object. You can connect to this property in the usual way. If no object is current, the property equates to an empty string. Currently the current_object can only be of class data.frame, but please do not rely on this, since this will be extended to other types of data in the future. If you are interested in data.frame objects, only, connect to the current_dataframe property, instead. Alternatively, you can enforce type requirements by using appropriate constraints on your <varslot>s, or by using GUI logic scripting. Repeating (a set of) options Sometimes you want to repeat a set of options for an arbitrary number of items. E.g. suppose you want to implement a plugin for sorting a data.frame. You may want to allow for sorting by an arbitrary number of columns (in case of ties among the first column(s)). This could simply be realized by allowing the user to select multiple variables in a <varslot> with multi="true". But if you want to extend this, ⪚ allowing the user to specify for each variable whether it should be converted to character / numeric, or whether sorting should be ascending or descending, you need more flexibility. Other examples would be plotting multiple lines in one plot (allowing to select object, line style, line color, &etc; for each line), or specifying a mapping for recoding from a set of old values to new values. Enter the <optionset>. Let's look at a simple example, first: <dialog [...]> [...] <optionset id="set" min_rows="1"> <content> <row> <input id="firstname" label="Given name(s)" size="small"> <input id="lastname" label="Family name" size="small"> <radio id="gender" label="Gender"> <optioncolumn label="Male" value="m"/> <optioncolumn label="Female" value="f"/> </radio> </row> </content> <optioncolumn id="firstnames" label="Given name(s)" connect="firstname.text"> <optioncolumn id="lastnames" label="Family name" connect="lastname.text"> <optioncolumn id="gender" connect="gender.string"> </optionset> [...] </dialog> Here, we created a UI for specifying a number of persons (⪚ authors). The UI requires at least one entry (min_rows="1"). Inside the <optionset>-element, we begin by specifying the <content>, &ie; those elements that belong to the option set. You will be familiar with most elements inside the <content>. Next we specify the variables of interest that we will want to read from the option set in our JS file. As we will be dealing with an arbitrary number of items, we cannot just read getString ("firstname") in JS. Rather, for each value of interest, we specify an <optioncolumn>. For the first optioncolumn in the example, <connect="firstname.text"> means that the content of the <input> element "firstname" is read for each item. <optioncolumn>s for which a label is given, will be shown in the display, in a column by that label. In JS, we can now fetch the first names for all authors using getList("set.firstname"), getList("set.lastnames") for the family names, and getList("set.gender") for an array of "m"/"f" strings. Note that there are no restrictions on what you can place inside an <optionset>. You can even use embedded components. Just as with any other element, all you have to do is to collect the output variables of interest in an <optioncolumn>-specification. In the case of embedded plugins, this is often a section of the "code" property. E.g.: <dialog [...]> [...] <optionset id="set" min_rows="1"> <content> [...] <embed id="color" component="rkward::color_chooser" label="Color"/> </content> [...] <optioncolumn id="color_params" connect="color.code.printout"> </optionset> [...] </dialog> Of course you can also use UI logic inside an optionset. There are two options for doing this: You can do so by making connection (or scripting) in the main <logic> section of your plugin, as usual. However, you will access the UI elements in the contents region as (⪚) "set.contents.firstname.XYZ". Note the prefix "set" (the id you have assigned to the set and "contents". Alternatively, you can add a separate <logic> section as a child element of your <optionset>. In this case, ids will be addressed relative to the contents region, ⪚ "firstname.XYZ". Only the <script>-element is not allowed in the logic section of an optionset. If you want to use scripting, you will have to utilize the plugin's main <logic> section. When scripting logic in an optionset, all you can do is access the current content region. Thus, typically, it is only meaningful to connect elements inside the contents region to each other. Connecting a property outside the <optionset> to a property inside the content region, may be useful for initialization. However, modifying the contents region after initialization will not apply to items that the user has already defined. Only to the currently selected item in the set. "Driven" optionsets So far we have considered an <optionset> that provides buttons for adding / removing items. However, in some cases, it is much more natural to select items outside the <optionset>, and only provide options for customizing some aspects of each item in an <optionset>. E.g. suppose you want to allow the user to plot several objects inside one plot. For each object, the user should be able to specify line color. You could solve this by placing a <varselector> and <varslot> inside the <content> area, allowing the user to select one item at a time. However, it will mean much less clicks for the user, if you use a <varslot multi="true"> outside the <optionset>, instead. Then you will connect this selection of objects to a so-called "driven" optionset. Here's how: <dialog [...]> <logic> <connect client="set.vars" governor="vars.available"/> <connect client="set.varnames" governor="vars.available.shortname"/> </logic> [...] <varselector id="varsel"/> <varslot id="vars" label="Objects to plot"/> <optionset id="set" keycolumn="var"> <content> [...] <embed id="color" component="rkward::color_chooser" label="Line color"/> </content> [...] <optioncolumn id="vars" external="true"> <optioncolumn id="varnames" external="true" label="Variable"> <optioncolumn id="color_params" connect="color.code.printout"> </optionset> [...] </dialog> We'll start looking at the example at the bottom. You'll note that two <optioncolumn> specifications have external="true". This tells &rkward; that these are controlled from outside the <optionset>. Here, the sole purpose of the "varnames"-optioncolumn is to provide easy-to-read labels in the optionset display (it is connected to the "shortname" modifier of the property holding the selected objects). The purpose of the "vars"-optioncolumn is to serve as the "key" column, as specified by <optionset keycolumn="vars"...>. This means that for each entry in this list, the set will offer one set of options, and options are logically tied to these entries. This column is connected to the property holding the selected objects in the <varslot>. That is for each object that is selected there, the <optionset> will allow to specify line color. External column can also be connected to properties inside the <content> region. However, it is important to note that optioncolumns declared external="true" should never be modified from inside the <optionset>, and optioncolumns declared external="false" (the default) should never be modified from outside the <optionset>. Alternatives: When not to use optionsets Optionsets are a powerful tool, but they can sometimes do more harm than good, as they add considerable complexity, both from the perspective of a plugin developer, and from the perspective of a user. Thus, think twice, when using them. Here's some adivce: For some simple cases, the <matrix> element may provide a useful lightweight alternative. Don't make your plugin do too much. We gave the example of using an optionset for a plugin to draw several lines within one plot. But in general it is not a good idea to create a plugin that will produce individual plots for each item in an optionset. Rather make the plugin produce one plot, and the user can call it multiple times. If you don't expect more than two or three items in a set, consider repeating the options, manually, instead. Handling dependencies and compatibility issues &rkward; version compatibility We do our best to make sure that plugins developed for an old version of &rkward; will remain functional in later versions of &rkward;. However, the reverse is not always true as new features are been added. Since not all users are running the latest version of &rkward;, this means your plugin may not work for everybody. When you are aware of such compatibility issues, you should make sure to document this fact in your &pluginmap; file, using the <dependencies> element. The <dependencies> can either be specified as a direct child of the &pluginmap;'s <document> element, or as a child element of individual <component> definitions. In the first case, the dependencies apply to all plugins in the map. In the latter case only to the individual <component>(s). You can also mix top "global" and "specific" dependencies. In this case the "global" dependencies are added to those of the individual component. Let's look at a small example: <document ...> <dependencies rkward_min_version="0.5.0c" /> <components ...> <component id="myplugin" file="reduced_version_of_myplugin.xml" ...> <dependencies rkward_max_version="0.6.0z" /> </component> <component id="myplugin" file="fancy_version_of_myplugin.xml" ...> <dependencies rkward_min_version="0.6.1" /> </component> ... x </components ...> </document> In this example, all plugins are known to require at least version 0.5.0c of &rkward;. One plugin, with id="myplugin" is provided in two alternative variants. The first, stripped down, version will be used for &rkward; versions before 0.6.1. The latter utilizes features that are new in &rkward; 0.6.1, and will only be used from &rkward; 0.6.1 onwards. Providing alternative variants like this is a very user friendly way to make use of new features, while still keeping support for earlier versions of &rkward;. Alternative versions should share the same id (warnings will be produced, otherwise), and may only be defined within the same &pluginmap; file. Plugin which are not compatible with the running version of &rkward;, and which do not come with an alternative version will be ignored with a warning. Actually &rkward; 0.6.1 is the first version to interpret dependencies - and to report dependency errors - at all. Thus, contrary to what the example may suggest, specifying any earlier versions in the dependencies will not have any direct effect (but may still be a good idea for documentation purposes). Sometimes it will even be possible to handle version incompatibility issues inside a single &pluginmap; file, using the <dependency_check> element, described in the following section. &r; version compatibility Similar to rkward_min_version and rkward_max_version, the <dependencies> element allows specification of the attributes R_min_version and R_max_version. However, there are the following differences: Plugins which fail to meet the &r; version requirement are not currently skipped when reading a &pluginmap; file. The user can still call the plugin, and will not see any immediate warning (in future versions, a warning message will probably be shown) In consequence it is also not possible to define alternative versions of a plugin depending on the running version of &r;. However, it is often easy to achieve backwards compatibility as shown below. If you are aware of &r; compatibility issues, please consider using this method, instead of defining a dependency on a particular version of &r;. In many cases, it is easily possible to provide reduced functionality, if a certain feature is not available in the running version of &r;. Consider the following short example of a plugin .xml file: <dialog [...]> <logic> <dependency_check id="ris210" R_min_version="2.10.0"/> <connect client="compression.xz.enabled" governor="ris210"/> </logic> [...] <radio id="compression" label="Compression method"> <option label="None" value=""> <option label="gzip" value="gzip"> <option id="xz" label="xz" value="xz"> </radio> [...] </dialog> In this example the compression option "xz" will simply be disabled when the &r; runtime version is older than 2.10.0 (which did not support xz compression). The <dependency_check> element supports the same attributes as the <dependencies> element in &pluginmap; files. It creates a boolean property, which is true, if the specified dependencies are met, false otherwise. Dependencies on &r; packages Dependencies on specific &r; packages can be defined, but as of &rkward; 0.6.1, these dependencies are neither checked, nor installed / loaded, automatically. They are shown in the plugin help files, however. Here is an example definition: <dependencies> <package name="heisenberg" min_version="0.11-2" repository="http://rforge.r-project.org" /> </dependencies> Always make sure to add appropriate require() calls, if you plugin needs certain packages to be loaded. If you distribute your &pluginmap; as an &r; package, and all plugins depend on a particular package, then you should define that dependency on the &r; package level. Defining dependencies to &r; packages on the level of the &rkward; &pluginmap; is most useful, if only some of your plugins need the dependency, the dependency is not available from CRAN, or your &pluginmap; is not distributed as an &r; package. Dependencies on other &rkward; &pluginmap;s If your plugins depend on plugins defined in another &pluginmap; (that is not part of your package) you can define this dependency like this: <dependencies> <pluginmap name="heisenberg_plugins" url="http://eternalwondermaths.example.org/hsb" /> </dependencies> Currently will neither load, nor install, nor even warn about missing &pluginmap;s, but at least information on dependencies (and where to obtain them) will be shown on the plugin's help page. You do not have to (and you should not) declare dependencies on &pluginmap;s that are shipped with the official &rkward; distribution, or on &pluginmap;s that are inside your own package. Further, if a required &pluginmap; is distributed as an &r; package, declare a dependency of the package (as shown in the previous section), rather than on the map. To make sure that required &pluginmap;s are actually loaded, use the <require>-tag (refer to the reference for details). An example To clarify how dependency definitions can be mixed, here's a combined example: <document ...> <dependencies rkward_min_version="0.5.0c"> <package name="heisenberg" min_version="0.11-2" repository="http://rforge.r-project.org" /> <package name="DreamsOfPi" min_version="0.2" /> <pluginmap name="heisenberg_plugins" url="http://eternalwondermaths.example.org/hsb" /> <dependencies> <require map="heisenberg::heisenberg_plugins"/> <components ...> <component id="myplugin" file="reduced_version_of_myplugin.xml" ...> <dependencies rkward_max_version="0.6.0z" /> </component> <component id="myplugin" file="fancy_version_of_myplugin.xml" ...> <dependencies rkward_min_version="0.6.1" /> </component> ... x </components ...> </document> Plugin translations So far we have used a few concepts regarding translations or "i18n" (short for "internationaliation", which has 18 characters between i and n) in passing. In this chapter we give a more in-depth account of what i18n functionaliy for &rkward; plugins. For the most part you will not need all of this in your plugins. However, it may be a good idea to read over this chapter in full, as understanding these concept should help you creating plugins that are fully translatable, and that allow for a high quality of translations. General considerations One important point to understand about software translations, in contrast to translations of other text materials, is that translators will often have a rather hard time getting a complete picture of what they are translating. Software translations are necessarily based on rather short fragments of text: Every label you give to an <option> in a <radio>, every string that you mark for translation in an i18n()-function call, will form a separate "translation unit". In essence, each such fragment will be presented to the translator in isolation. Well, not complete isolation, as we do try to provide translator with as much meaningful context as can be extracted, automatically. But at some points translators will need additional context to make sense of a string, especially where strings are short. i18n in &rkward;'s xml files For &rkward;'s xml files, i18n will mostly just work. If you are writing your own .pluginmap (⪚ for an external plugin), you will have to specify a po_id next to the pluginmap's id. This defines the "message catalog" to use. In general this should be set identical to the id of your .pluginmap, but if you provide several .pluginmaps and want to control, how message catalogs are divided up, this allows you to do so. The po_id is inherited by any .pluginmap you include, unless that declares a different po_id, and by all plugins declared in it. For plugins and help pages, you do not need to tell &rkward; which strings are to be translated, because that is generally evident from their usage. However, as explained above, you should keep an eye out for strings that may be ambiguous or need some explaining in order to be translated, correctly. For strings that could have different meanings, provide an i18n_context like this: <checkbox id="scale" label="Scale" i18n_context="Show the scale"/> <checkbox id="scale" label="Scale" i18n_context="Scale the plot"/> Providing i18n_context will cause the two strings to be translated separately. Otherwise they would share a single translation. In addition, the context is shown to the translator. The i18n_context-attribute is supported on all elements that can have translatable strings, somewhere, including elements that contain text inside them (⪚ <text>-elements). In other cases the string to translate has a single non-ambiguous meaning, but may still need some explaining. In this case you can add a comment that will be shown to translators. Examples might include: <!-- i18n: No, this is not a typo for screen plot! --> <component id="scree_plot" label="Scree plot"/> <!-- i18n: If you can, please make this string short. Having more than some 15 chars looks really ugly at this point, and the meaning should be mostly self-evident to the user (selection from a list of values shown next to this element) --> <valueslot id="selected" label="Pick one"/> Note that such comments must precede the element they apply to, and must start with either "i18n:" or "TRANSLATORS:". Finally, in rare cases, you may want to exclude certain strings from translation. This may make sense, for example, if you offer a choice between several R function names in a <radio>-control. You do not want these to be translated, then (but depending on the context, you should consider giving a descriptive label, instead): <radio id="transformation" label="R function to apply"> <option id="as.list" noi18n_label="as.list()"/> <option id="as.vector" noi18n_label="as.vector()"/> [...] </radio> Note that you will omit the label-attribute, then, and specify noi18n_label, instead. Also, note that in contrast to i18n_context and comments, using noi18n_label will make your plugin incompatible with versions of &rkward; prior to 0.6.3. i18n in &rkward;'s js files and sections In contrast to the .xml files, making the js files of a plugin translatable requires more custom work. The key difference, here, is that there is no decent automatic way to tell, whether a string is meant to be displayed as a human readable string, or a piece of code. So you need to mark this up, yourself. We have already shown examples of this, all along. Here is a more complete description of the i18n-functions available in js code, and some hints for more complex cases: i18n (msgid, [...]) The most important function. Marks the string for translation. The string (whether translated or not) is returned quoted using double quotes ('"'). An arbitrary number of placeholders can be used in the string like shown below. Using such placeholders instead of concatenating small substrings is much easier for translators.: i18n ("Compare objects %1 and %2", getString ('x'), getString ('y')); i18nc (msgctxt, msgid, [...]) Same as i18n(), but additionally providing a message context: i18nc ("proper name, not state of mind", "Mood test"); i18np (msgid_singular, msgid_plural, n, [...]) Same as i18n(), but for messages that may be different in singular or plural form (and some languages have differentiate yet more numerical forms). Note that just like with i18n(), you can use an arbitrary number of replacements, but the first ('%1') is required, and has to be an integer. i18np ("Comparing a single pair", "Comparing %1 distinct pairs", n_pairs); i18ncp (msgctxt, msgid_singular, msgid_plural, n, [...]) i18np() with added message context. comment (comment, [indentation]) Echos a code comment, marked for translation. In contrast to the other i18n() functions, this is not quoted, but a '#' is added to each line of the comment. comment ("Transpose the matrix"); echo ('x <- t (x)\n'); To add comments to the translators (see above for a discussion of the differences between comment and context), add a comment starting with "i18n:" or "translators:" directly above the i18n()-call to comment. E.g.: // i18n: Spelling is correct: Scree plot. echo ('rk.header (' + i18n ("Scree plot") + ')\n'); i18n and quotes For the most part, you will not have to worry about i18n() behavior with respect to quotes. As, typically, translatable strings are string literals, quoting them is just the right thing to do, and saves you some typing. Also, in functions like makeHeaderCode()/Header() that usually quote their arguments, i18n()'ed strings are protected from duplicate quoting. Essentially, this works, by sending the translated string first through quote() (to make it quoted), then through noquote() (to protect it from additional quoting). Should you require a translatable string that is not quoted, use i18n(noquote ("My message")). Should you require a translatable string to be quoted, a second time, send it through quote(), twice. That said, it is generally not a good idea to make bits like function names or variable names translatable. For one thing, R, the programming language, is inherently in English, and there is no internationalization of the language itself. Code comments are a different beast, but you should use the comment()-function for those. Secondly, making syntactically relevant parts of the generated code translatable means that translations could actually break your plugin. E.g. if an unsuspecting translator translates a string meant as a variable name in two distinct words with a space in between. i18n and backwards compatibility One unfortunate aspect of the lack of i18n()-support in &rkward; versions up to 0.6.2 is that adding i18n() calls will make the plugin require &rkward; version 0.6.3. If your plugin is developed outside &rkward;'s official release, this may be a problem. Here are some possible options on how to handle this: Provide the plugin in two versions for &rkward; >= 0.6.3 and &rkward; < 0.6.3, as described in the chapter on handling dependencies Simply don't translate the strings in the .js-file, yet. Obviously this is an easy, but rather inelegant solution. Include some support code in your .js-file(s) like shown below: // js-function "comment" was not defined before 0.6.3 if (typeof (comment) == 'undefined) { // define function i18n(), and any others you may need. Note that your implementation could actually be simpler than // shown, here, ⪚ if you do not make use of placeholders. i18n = function (msgid) { var ret = msgid; for (var i = 1; i < arguments.length; i++) { ret = ret.replace(new RegExp("%" + i, 'g'), arguments[i]); } if (msgid.noquote) { ret.noquote = msgid.noquote; return (ret); } return (noquote (quote (ret))); } } Translation maintainance Now that you have made your plugin translatable, how do you actually get it translated? In general you only need to worry about this, when developing an external plugin. For plugins in &rkward;'s main repository, all the magic is done for you. Here's the basic workflow. Note that you need the "gettext" tools, installed: Mark up all strings, providing context and comments as needed Run python scripts/update_plugin_messages.py --extract-only /path/to/my.pluginmap. scripts/update_plugin_messages.py is not currently part of the source releases, but can be found in a source repository checkout. Distribute the resulting rkward__POID.pot file to your translators. For external plugins, it is recommended to place it in a subfolder "po" in inst/rkward. Translator opens the file in a translation tool such as lokalize. Actually, even if you are not going to prepare any translation, yourself, you should try this step for yourself. Browse the extracted strings looking out for problems / ambiguities. Translator saves the translation as rkward__POID.xx.po (where xx is the language code), and sends it back to you. Copy rkward__POID.xx.po to your sources, next to rkward__POID.pot. Run python scripts/update_plugin_messages.py /path/to/my.pluginmap (Note: without --extract-only, this time). This will merge the translation with any interim string changes, compile the translation, and install it into DIR_OF_PLUGINMAP/po/xx/LC_MESSAGES/rkward__POID.mo (where xx is the language code, again). You should also include the non-compiled translation (&ie; rkward__POID.xx.po) in your distribution, in the "po" subdirectory. For any update of your plugin, run python scripts/update_plugin_messages.py /path/to/my.pluginmap to update the .pot file, but also the existing .po-files, and the compiled message catalogs. Writing plugin translations We assume you know your trade as a translator, or are willing to read up on it, elsewhere. A few words specifically about translations of RKWard plugins, though: RKWard plugins were not translatable until version 0.6.3, and were mostly not written with i18n in mind, before then. Thus you are going to encounter rather more ambiguous strings, and other i18n problems than in other mature projects. Please don't just silently work around these, but let us (or the plugin maintainers) know, so we can fix these issues. Many RKWard plugins refer to highly specialized terms, from data handling and statistics, but also from other fields of science. In many cases, a good translation will require at least basic knowledge of these fields. In some cases, there is no good translation for a technical term, and the best option may be to leave the term untranslated, or to include the English term in parentheses. Don't focus too much on the 100% mark of translated strings, focus on providing a good translation, even if that means skipping some strings (or even skipping some message catalogs as a whole). Other users may be able to fill in any gaps in technical terms. At the time of this writing, &rkward;'s project hosting is about to change, and this also affect the translation workflow. Do read comments accompanying the .pot-files, on how translations should be handled. If in doubt, it is never wrong to send your work the rkward-devel mailing list, or to ask for up-to-date instructions, there. Author, license and version information So you have written a set of plugins, and you are getting ready to share your work. To make sure users will know what your work is all about, under what terms they can use and distribute it, and whom they should contact about issues or ideas, you should add some information about your plugins. This can be done using the <about> element. It can be used in either the &pluginmap; or in individual plugin .xml files (in both cases as a direct child of the document tag). When specified in the &pluginmap; it will apply to all plugins. If <about> is specified in both places, the <about> information in the plugin .xml file will override that in the &pluginmap; file. You can also add an <about> element to .rkh-pages, which are not connected to a plugin, if there is a need for that. Here's an example &pluginmap; file with only a few explanations, below. In cases of doubt, more information may be available from the reference. <document namespace="rkward" id="SquaretheCircle_rkward" > <about name="Square the Circle" shortinfo="Squares the circle using Heisenberg compensation." version="0.1-3" releasedate="2011-09-19" url="http://eternalwondermaths.example.org/23/stc.html" license="GPL" category="Geometry" > <author given="E.A." family="Dölle" email="doelle@eternalwondermaths.example.org" role="aut" /> <author given="A." family="Assistant" email="alterego@eternalwondermaths.example.org" role="cre, ctb" /> </about> <dependencies> ... </dependencies> <components> ... </components> <hierarchy> ... </hierarchy> </document> Most of this should explain itself, so we’ll not discuss each and every tag element. But let’s look at some details that probably need some commentary for easier understanding. The category element in <about> can be defined rather freely, but should be meaningful, as it’s thought to be used to order plugins into groups. All other attributes in this opening tag are mandatory and must be filled with reasonable content. At least one <author> with a valid email address and the role aut (author) must also be given. In case your plugin causes problems or someone would like to share its gratitude with you, it should be easy to contact someone who’s involved. For further information on other valid roles, like ctb for code contributors or cre for package maintenance, please refer to the R documentation on person(). Remember that you can use <include> and / or <insert> to repeat information across several .xml files (⪚ information on an author who was involved with several plugins). More information. You don't have to write this XML code by hand. If you use the function rk.plugin.skeleton() from the rkwarddev package and provide all necessary information via the about option, it will automatically create a &pluginmap; file with a working <about> section for you. Share your work with others External plugins As of version 0.5.5, &rkward; provides a comfortable way to install additional third party plugins which do not belong to the core package itself. We call these external plugins. They come in form of an &r; package and can be managed directly via the usual package management features of &r; and/or &rkward;. This section of the documentation describes how external plugins are to be packaged, so that &rkward; can use them. The plugin creation itself is of course identical to the previous sections. That is, you should probably first write a working plugin, and then check back here to learn how to distribute it. Since external plugins are a relatively young feature, details of this might probably change in future releases. You’re welcome to contribute your ideas to improve the process. These docs explain the details of external plugins so you can learn how they work. In addition to that, also have a look at the rkwarddev package, which was designed to automate a lot of the writing process. Why external plugins? The number of packages to extend the functionality of &r; is immense already, and climbing. On one hand, we want to encourage you to write plugins for even the most specialised tasks you need solved. On the other hand, the average user should not get lost in huge menu trees full of unknown statistical terms. Therefore it seemed reasonable to keep the plugin handling in &rkward; quite modular as well. The &rkward; team maintains its own public package repository at http://files.kde.org/rkward/R, designated to host your external plugins. As a rule of thumb, plugins that seem to serve a widely used purpose (⪚ t-Tests) should become part of the core package, while those who serve a rather limited group of people with special interests should be provided as an optional package. For you as a plugin author it’s best practice to just start with an external plugin. Structure of a plugin package For external plugins to install and work properly, they must follow some structural guidelines regarding their file hierarchy. File hierarchy Lets have a look at the prototypic file hierarchy of an elaborate plugin archive. You don’t have to include all of these directories and/or files for a plugin to work (read on to learn what’s absolutely necessary), consider this a best practice example: plugin_name/ inst/ rkward/ plugins/ plugin_name.xml plugin_name.js plugin_name.rkh ... po/ ll/ LC_MESSAGES/ rkward__plugin_name_rkward.mo rkward__plugin_name_rkward.ll.po rkward__plugin_name_rkward.pot tests/ testsuite_name/ RKTestStandards.sometest_name.rkcommands.R RKTestStandards.sometest_name.rkout ... testsuite.R plugin_name.pluginmap ... ChangeLog README AUTHORS LICENSE DESCRIPTION In this example, all occasions of plugin_name, testsuite_name and sometest_name are to be replaced with their correct names, accordingly. Also, ll is a placeholder for a language abbreviation (⪚, de, en or es). You don't have to create this file hierarchy by hand. If you use the function rk.plugin.skeleton() from the rkwarddev package, it will automatically create all necessary files and directories for you, except the po directory which is created and managed by the translation script. Basic plugin components It is mandatory to include at least three files: a &pluginmap;, a plugin .xml description and a plugin .js file. That is, even the "plugins" directory is optional. It might just help to give your files some order, especially if you include more that one plugin/dialog in the archive, which is of course no problem. You can have as many directories for the actual plugin files as you see fit, they just have to resemble the &pluginmap;, respectively. It is also possible to even include several &pluginmap; files, if it fits your needs, but you should include them all in plugin_name.pluginmap then. Each &r; package must have a valid DESCRIPTION file, which is also crucial for &rkward; recognizing it as a plugin provider. Most of the information it carries is also needed in the plugin Meta-information and possibly dependencies, but in a different format (the &r; documentation explains the DESCRIPTION file in detail). In addition to the general contents of a DESCRIPTION file, make sure to also include the line Enhances: rkward. This will cause &rkward; to automatically scan the package for plugins if it is installed. An example DESCRIPTION file looks like this: Package: SquaretheCircle Type: Package Title: Square the circle Version: 0.1-3 Date: 2011-09-19 Author: E.A. Dölle <doelle@eternalwondermaths.example.org> Maintainer: A. Assistant <alterego@eternalwondermaths.example.org> Enhances: rkward Description: Squares the circle using Heisenberg compensation. License: GPL LazyLoad: yes URL: http://eternalwondermaths.example.org/23/stc.html Authors@R: c(person(given="E.A.", family="Dölle", role="aut", email="doelle@eternalwondermaths.example.org"), person(given="A.", family="Assistant", role=c("cre", "ctb"), email="alterego@eternalwondermaths.example.org")) You don't have to write this file by hand. If you use the function rk.plugin.skeleton() from the rkwarddev package and provide all necessary information via the about option, it will automatically create a working DESCRIPTION file for you. Additional information (optional) ChangeLog, README, AUTHORS, LICENSE should be self-explaining and are entirely optional. Actually, they won’t be interpreted by &rkward;, so they are rather meant to carry additional information that might be relevant ⪚ for distributors. Most of their relevant content (author credits, licence terms &etc;) will be included in the actual plugin files anyway, though (see the section on meta-information). Note that all of these files could also be placed somewhere in the "inst" directory, if you want them not only to be present in the source archive but the installed package as well. Automated plugin testing (optional) Another optional directory is "tests", which is meant to provide files needed for automated plugin testing. These tests are helpful to quickly check if your plugins still work with new versions of &r; or &rkward;. If you want to include tests, you should really restrain yourself to the naming scheme and hierarchy shown here. That is, tests should reside in a directory called tests, which includes a file testsuite.R and a folder with tests standards named after the appropriate test suite. You can, however, provide more than one test suite; in that case, if you don’t want to append them all in the one testsuite.R file, you can split them in ⪚ one file for each test suite and create one testsuite.R with source() calls for each suite file. In either case, create separate subdirectories with test standards for each defined suite. The benefits of upholding to this structure is that plugin tests can be run simply by calling rktests.makplugintests() from the rkwardtests package without additional arguments. Have a look at the online documentation on Automated Plugin Testing for further details. Building the plugin package As explained earlier, external &rkward; plugins are in effect &r; packages, and therefore the packaging process is identical. In contrast to "real" &r; packages, a pure plugin package doesn't carry any further &r; code (although you can of course add &rkward; plugins to usual &r; packages as well, using the same methods explained here). This should make it even easier to create a functioning package, as long as you have a valid DESCRIPTION file and adhere to the file hierarchy explained in previous sections. The easiest way to actually build and test your plugin is to use the R command on the command line, for example: R SquaretheCircle R SquaretheCircle_0.1-3.tar.gz You don't have to build the package like this on the command line. If you use the function rk.build.package() from the rkwarddev package, it will build and/or check your plugin package for you. Plugin development with the <application>rkwarddev</application> package Overview Writing external plugins involves writing files in three languages (XML, JavaScript and R) and the creation of a standardized hierarchy of directories. To make this a lot easier for willing plugin developers, we're providing the rkwarddev package. It provides a number of simple &r; functions to create the XML code for all dialog elements like tabbooks, checkboxes, dropdownlists or filebrowsers, as well as functions to create JavaScript code and &rkward; help files to start with. The function rk.plugin.skeleton() creates the expected directory tree and all necessary files where they are supposed to be. This package is not installed by default, but has to be installed manually from &rkward;'s own repository. You can either do that by using the GUI interface (SettingsConfigure packages), or from any running &r; session: install.packages("rkwarddev", repos="http://files.kde.org/rkward/R") library(rkwarddev) rkwarddev depends on another small package called XiMpLe, which is a very simple XML parser and generator and also present in the same repository. The full documentation in PDF format can also be found there. A more detailed introduction to working with the package can be found in the rkwarddev vignette. Practical example To get you an idea how scripting a plugin looks like, compared to the direct approach you have seen in the previous chapters, we'll create the full t-test plugin once again -- this time only with the &r; functions of the rkwarddev package. The package will add a new GUI dialog to &rkward; under FileExportCreate &rkward; plugin skeleton. Like the name suggests, you can create plugin skeletons for further editing with it. This dialog itself was in turn generated by an rkwarddev script which you can find in the demo directory of the installed package and package sources, as an additional example. You can also run it by calling demo("skeleton_dialog") GUI description You will immediately notice that the workflow is considerably different: Contrary to writing the XML code directly, you do not begin with the <document> definition, but directly with the plugin elements you'd like to have in the dialog. You can assign each interface element -- be it check boxes, dropdown menus, variable slots or anything else -- to individual &r; objects, and then combine these objects to the actual GUI. The package has functions for each XML tag that can be used to define the plugin GUI, and most of them even have the same name, only with the prefix rk.XML.*. For example, defining a <varselector> and two <varslot> elements for the "x" and "y" variable of the t-test example can be done by: variables <- rk.XML.varselector(id.name="vars") var.x <- rk.XML.varslot("compare", source=variables, types="number", required=TRUE, id.name="x") var.y <- rk.XML.varslot("against", source=variables, types="number", required=TRUE, id.name="y") The most interesting detail is probably source=variables: A prominent feature of the package is that all functions can generate automatic IDs, so you don't have to bother with either thinking of id values or remembering them to refer to a specific plugin element. You can simply give the &r; objects as reference, as all functions who need an ID from some other element can also read it from these objects. rk.XML.varselector() is a little special, as it usually has no specific content to make an ID from (it can, but only if you specify a label), so we have to set an ID name. But rk.XML.varslot() wouldn't need the id.name arguments here, so this would suffice: variables <- rk.XML.varselector(id.name="vars") var.x <- rk.XML.varslot("compare", source=variables, types="number", required=TRUE) var.y <- rk.XML.varslot("against", source=variables, types="number", required=TRUE) In order to recreate the example code to the point, you'd have to set all ID values manually. But since the package shall make our lives easier, from now on we will no longer care about that. rkwarddev is capable of a lot of automation to help you build your plugins. However, it might be preferable to not use it to its full extend. If your goal is to produce code that is not only working but can also be easily read and compared to your generator script by a human being, you should consider to always set useful IDs with id.name. Naming your R objects identical to these IDs will also help in getting script code that is easy to understand. If you want to see how the XML code of the defined element looks like if you exported it to a file, you can just call the object by its name. So, if you now called var.x in your &r; session, you should see something like this: <varslot id="vrsl_compare" label="compare" source="vars" types="number" required="true" /> Some tags are only useful in the context of others. Therefore, for instance, you won't find a function for the <option> tag. Instead, both radio buttons and dropdown lists are defined including their options as a named list, where the names represent the labels to be shown in the dialog, and their value is a named vector which can have two entries, val for the value of an option and the boolean chk to specify if this option is checked by default. test.hypothesis <- rk.XML.radio("using test hypothesis", options=list( "Two-sided"=c(val="two.sided"), "First is greater"=c(val="greater"), "Second is greater"=c(val="less") ) ) The result looks like this: <radio id="rad_usngtsth" label="using test hypothesis"> <option label="Two-sided" value="two.sided" /> <option label="First is greater" value="greater" /> <option label="Second is greater" value="less" /> </radio> All that's missing from the elements of the Basic settings tab is the check box for paired samples, and the structuring of all of these elements in rows and columns: check.paired <- rk.XML.cbox("Paired sample", value="1", un.value="0") basic.settings <- rk.XML.row(variables, rk.XML.col(var.x, var.y, test.hypothesis, check.paired)) rk.XML.cbox() is a rare exception where the function name does not contain the full tag name, to save some typing for this often used element. This is what basic.settings now contains: <row id="row_vTFSPP10TF"> <varselector id="vars" /> <column id="clm_vrsTFSPP10"> <varslot id="vrsl_compare" label="compare" source="vars" types="number" required="true" /> <varslot id="vrsl_against" label="against" i18n_context="compare against" source="vars" types="number" required="true" /> <radio id="rad_usngtsth" label="using test hypothesis"> <option label="Two-sided" value="two.sided" /> <option label="First is greater" value="greater" /> <option label="Second is greater" value="less" /> </radio> <checkbox id="chc_Pardsmpl" label="Paired sample" value="1" value_unchecked="0" /> </column> </row> In a similar manner, the next lines will create &r; objects for the elements of the Options tab, introducing functions for spinboxes, frames and stretch: check.eqvar <- rk.XML.cbox("assume equal variances", value="1", un.value="0") conf.level <- rk.XML.spinbox("confidence level", min=0, max=1, initial=0.95) check.conf <- rk.XML.cbox("print confidence interval", val="1", chk=TRUE) conf.frame <- rk.XML.frame(conf.level, check.conf, rk.XML.stretch(), label="Confidence Interval") Now all we need to do is to put the objects together in a tabbook, and place that in a dialog section: full.dialog <- rk.XML.dialog( label="Two Variable t-Test", rk.XML.tabbook(tabs=list("Basic settings"=basic.settings, "Options"=list(check.eqvar, conf.frame))) ) We can also create the wizard section with its two pages using the same objects, so their IDs will be extracted for the <copy> tags: full.wizard <- rk.XML.wizard( label="Two Variable t-Test", rk.XML.page( rk.XML.text("As a first step, select the two variables you want to compare against each other. And specify, which one you theorize to be greater. Select two-sided, if your theory does not tell you, which variable is greater."), rk.XML.copy(basic.settings)), rk.XML.page( rk.XML.text("Below are some advanced options. It's generally safe not to assume the variables have equal variances. An appropriate correction will be applied then. Chosing \"assume equal variances\" may increase test-strength, however."), rk.XML.copy(check.eqvar), rk.XML.text("Sometimes it's helpful to get an estimate of the confidence interval of the difference in means. Below you can specify whether one should be shown, and which confidence-level should be applied (95% corresponds to a 5% level of significance)."), rk.XML.copy(conf.frame))) That's it for the GUI. The global document will be combined in the end by rk.plugin.skeleton(). JavaScript code Until now, using the rkwarddev package might not seem to have helped so much. This is going to change right now. First of all, just like we didn't have to care about IDs for elements when defining the GUI layout, we don't have to care about JavaScript variable names in the next step. If you want more control, you can write plain JavaScript code and have it pasted to the generated file. But it's probably much more efficient to do it the rkwarddev way. Most notably you don't have to define any variable yourself, as rk.plugin.skeleton() can scan your XML code and automatically define all variables you will probably need -- for instance, you wouldn't bother to include a check box if you don't use its value or state afterwards. So we can start writing the actual &r; code generating JS immediately. The function rk.JS.scan() can also scan existing XML files for variables. The package has some functions for JS code constructs that are commonly used in &rkward; plugins, like the echo() function or if() {...} else {...} conditions. There are some differences between JS and R, ⪚, for paste() in &r; you use the comma to concatenate character strings, whereas for echo() in JS you use +, and lines must end with a semicolon. By using the &r; functions, you can almost forget about these differences and keep writing &r; code. These functions can take different classes of input objects: Either plain text, &r; objects with XML code like above, or in turn results of some other JS functions of the package. In the end, you will always call rk.paste.JS(), which behaves similar to paste(), but depending on the input objects it will replace them with their XML ID, JavaScript variable name or even complete JavaScript code blocks. For the t-test example, we need two JS objects: One to calculate the results, and one to print them in the printout() function: JS.calc <- rk.paste.JS( echo("res <- t.test (x=", var.x, ", y=", var.y, ", hypothesis=\"", test.hypothesis, "\""), js( if(check.paired){ echo(", paired=TRUE") }, if(id("!", check.paired, " && ", check.eqvar)){ echo(", var.equal=TRUE") }, if(conf.level != "0.95"){ echo(", conf.level=", conf.level) }, linebreaks=TRUE ), echo(")\n"), level=2 ) JS.print <- rk.paste.JS(echo("rk.print (res)\n"), level=2) As you can see, rkwarddev also provides an &r; implementation of the echo() function. It returns exactly one character string with a valid JS version of itself. You might also notice that all of the &r; objects here are the ones we created earlier. They will automatically be replaced with their variable names, so this should be quite intuitive. Whenever you need just this replacement, the function id() can be used, which also will return exactly one character string from all the objects it was given (you could say it behaves like paste() with a very specific object substitution). The js() function is a wrapper that allows you to use &r;'s if(){...} else {...} conditions like you are used to. They will be translated directly into JS code. It also preserves some operators like <, >= or ||, so you can logically compare your &r; objects without the need for quoting most of the time. Let's have a look at the resulting JS.calc object, which now contains a character string with this content: echo("res <- t.test (x=" + vrslCompare + ", y=" + vrslAgainst + ", hypothesis=\"" + radUsngtsth + "\""); if(chcPardsmpl) { echo(", paired=TRUE"); } else {} if(!chcPardsmpl && chcAssmqlvr) { echo(", var.equal=TRUE"); } else {} if(spnCnfdnclv != "0.95") { echo(", conf.level=" + spnCnfdnclv); } else {} echo(")\n"); Alternatively for if() conditions nested in js(), you can use the ite() function, which behaves similar to &r;'s ifelse(). However, conditional statements constructed using ite() are usually harder to read and should be replaced by js() whenever possible. Plugin map This section is very short: We don't need to write a &pluginmap; at all, as it can be generated automatically by rk.plugin.skeleton(). The menu hierarchy can be specified via the pluginmap option: [...] pluginmap=list( name="Two Variable t-Test", hierarchy=list("analysis", "means", "t-Test")) [...] Help page This section is very short as well: rk.plugin.skeleton() cannot write a whole help page from the information it has. But it can scan the XML document also for elements which probably deserve a help page entry, and automatically create a help page template for our plugin. All we have to do afterwards is to write some lines for each listed section. The function rk.rkh.scan() can also scan existing XML files to create a help file skeleton. Generate the plugin files Now comes the final step, in which we'll hand over all generated objects to rk.plugin.skeleton(): plugin.dir <- rk.plugin.skeleton("t-Test", xml=list( dialog=full.dialog, wizard=full.wizard), js=list( results.header="Two Variable t-Test", calculate=JS.calc, printout=JS.print), pluginmap=list( name="Two Variable t-Test", hierarchy=list("analysis", "means", "t-Test")), load=TRUE, edit=TRUE, show=TRUE) The files will be created in a temporal directory by default. The last three options are not necessary, but very handy: load=TRUE will automatically add the new plugin to &rkward;s configuration (since it's in a temp dir and hence will cease to exist when &rkward; is closed, it will automatically be removed again by &rkward; during its next start), edit=TRUE will open all created files for editing in &rkward; editor tabs, and show=TRUE will attempt to directly launch the plugin, so you can examine what it looks like without a klick. You might consider adding overwrite=TRUE if you're about to run your script repeatedly (⪚ after changes to the code), as by default no files will be overwritten. The result object plugin.dir contains the path to the directory in which the plugin was created. This can be useful in combination with the function rk.build.package(), to build an actual &r; package to share your plugin with others -- ⪚ by sending it to the &rkward; development team to be added to our plugin repository. The full script To recapitulate all of the above, here's the full script to create the working t-test example. Adding to the already explained code, it also loads the package if needed, and it uses the local() environment, so all the created objects will not end up in your current workspace (except for plugin.dir): require(rkwarddev) local({ variables <- rk.XML.varselector(id.name="vars") var.x <- rk.XML.varslot("compare", source=variables, types="number", required=TRUE) var.y <- rk.XML.varslot("against", source=variables, types="number", required=TRUE) test.hypothesis <- rk.XML.radio("using test hypothesis", options=list( "Two-sided"=c(val="two.sided"), "First is greater"=c(val="greater"), "Second is greater"=c(val="less") ) ) check.paired <- rk.XML.cbox("Paired sample", value="1", un.value="0") basic.settings <- rk.XML.row(variables, rk.XML.col(var.x, var.y, test.hypothesis, check.paired)) check.eqvar <- rk.XML.cbox("assume equal variances", value="1", un.value="0") conf.level <- rk.XML.spinbox("confidence level", min=0, max=1, initial=0.95) check.conf <- rk.XML.cbox("print confidence interval", val="1", chk=TRUE) conf.frame <- rk.XML.frame(conf.level, check.conf, rk.XML.stretch(), label="Confidence Interval") full.dialog <- rk.XML.dialog( label="Two Variable t-Test", rk.XML.tabbook(tabs=list("Basic settings"=basic.settings, "Options"=list(check.eqvar, conf.frame))) ) full.wizard <- rk.XML.wizard( label="Two Variable t-Test", rk.XML.page( rk.XML.text("As a first step, select the two variables you want to compare against each other. And specify, which one you theorize to be greater. Select two-sided, if your theory does not tell you, which variable is greater."), rk.XML.copy(basic.settings)), rk.XML.page( rk.XML.text("Below are some advanced options. It's generally safe not to assume the variables have equal variances. An appropriate correction will be applied then. Chosing \"assume equal variances\" may increase test-strength, however."), rk.XML.copy(check.eqvar), rk.XML.text("Sometimes it's helpful to get an estimate of the confidence interval of the difference in means. Below you can specify whether one should be shown, and which confidence-level should be applied (95% corresponds to a 5% level of significance)."), rk.XML.copy(conf.frame))) JS.calc <- rk.paste.JS( echo("res <- t.test (x=", var.x, ", y=", var.y, ", hypothesis=\"", test.hypothesis, "\""), js( if(check.paired){ echo(", paired=TRUE") }, if(id("!", check.paired, " && ", check.eqvar)){ echo(", var.equal=TRUE") }, if(conf.level != "0.95"){ echo(", conf.level=", conf.level) }, linebreaks=TRUE ), echo(")\n"), level=2) JS.print <- rk.paste.JS(echo("rk.print (res)\n"), level=2) plugin.dir <<- rk.plugin.skeleton("t-Test", xml=list( dialog=full.dialog, wizard=full.wizard), js=list( results.header="Two Variable t-Test", calculate=JS.calc, printout=JS.print), pluginmap=list( name="Two Variable t-Test", hierarchy=list("analysis", "means", "t-Test")), load=TRUE, edit=TRUE, show=TRUE, overwrite=TRUE) }) Adding help pages If you want to write a help page for your plugin, the most straight forward way to do so is to add the particular instructions directly to the definitions of the XML elements they belong to: variables <- rk.XML.varselector( id.name="vars", help="Select the data object you would like to analyse.", component="Data" ) The text given to the help parameter can then be fetched by rk.rkh.scan() and written to the help page of this plugin component. For this to work technically, however, rk.rkh.scan() must know which &r; objects belong to one plugin component. This is why you must also provide the component parameter and make sure it is identical for all objects belonging together. Since you will usually combine many objects into one dialog and might also like to be able to re-use objects like the <varslot> in multiple components of your plugins, it is possible to globally define a component with the rk.set.comp(). If set, it is assumend that all the following objects used in your script belong to that particular component, until rk.set.comp() is being called again with a different component name. You can then omit the component parameter: rk.set.comp("Data") variables <- rk.XML.varselector( id.name="vars", help="Select the data object you would like to analyse." ) To add global sections like <summary> or <usage> to the help page, you use functions like rk.rkh.summary() or rk.rkh.usage() accordingly. Their results are then used to set the list elements like summary or usage in the rkh parameter of rk.plugin.component()/rk.plugin.skeleton(). Translating plugins The rkwarddev package is capable of producing external plugins with full i18n support. For instance, all relevant functions generating XML objects offer an optional parameter to specify i18n_context or noi18n_label: varComment <- rk.XML.varselector(id.name="vars", i18n=list(comment="Main variable selector")) varContext <- rk.XML.varselector(id.name="vars", i18n=list(context="Main variable selector")) cboxNoi18n <- rk.XML.cbox(label="Power", id.name="power", i18n=FALSE) The above examples produce output like this: # varComment <!-- i18n: Main variable selector --> <varselector id="vars" /> # varContext <varselector id="vars" i18n_context="Main variable selector" /> # cboxNoi18n <checkbox id="power" noi18n_label="Power" value="true" /> There's also support for translatable JS code. In fact, the package tries add i18n() calls by default in places where this is usually helpful. The rk.JS.header() function is a good example: jsHeader <- rk.JS.header("Test results") This produces the following JS code: new Header(i18n("Test results")).print(); But you can also manually mark strings in your JS code as translatable, by using the i18n() function just like you would if you wrote the JS file directly. Reference Types of properties/Modifiers At some places in this introduction we've talked about properties of GUI elements or otherwise. In fact there are several different types of properties. Usually you do not need to worry about this, as you can use common sense to connect any property to any other property. However, internally, there are different types of properties. What this matters for, is when fetching some special values in the JS-template. In getString ("id")/getBoolean ("id")/getList ("id") statements you can also specify some so called modifiers like this: getString ("id.modifier"). This modifier will affect, in which way the value is printed. Read on for the list of properties, and the modifiers they each make available: String properties The most simple type of property, used to simply hold a piece of text. Modifiers: No modifier ("") The string as defined / set. quoted The string in quoted form (suitable for passing to R as character). Boolean properties Properties that can either be on or off, true or false. For instance the properties created by <convert>-tags, also the property accompanying a <checkbox> (see below). The following values will be returned according to the given modifier: No modifier ("") By default the property will return 1 if it is true, and 0 otherwise. The recommended way to fetch boolean values is using getBoolean(). Note that for getString(), the string "0" will be returned when the property is false. This string would evaluate to true, not to false in JS. "labeled" Returns the string "true" when true, "false", when false, or whichever custom strings have been specified (typically in a <checkbox>). "true" Return the string as if the property was true, even if it is false "false" Return the string as if the property was false, even if it is true "not" This actually returns another Boolean property, which is the reverse of the current (&ie; false if true, true if false) "numeric" Obsolete, provided for backwards compatibility. Same as no modifier "". Return "1" if the property is true, or "0" if it is false. Integer properties A property designed to hold an integer value (but of course it still returns a numeric character string to the JS-template). It does not accept any modifiers. Used in <spinbox>es (see below) Real number properties A property designed to hold a real number value (but of course it still returns a numeric character string to the JS-template). Used in <spinbox>es (see below) No modifier ("") For getValue() / getString(), this returns the same as "formatted". In future versions, it will be possible to obtain a numeric represenation, instead. "formatted" Returns the formatted number (as a string). RObject properties A property designed a selection of one or more &r; objects. Used most prominently in varselectors and varslots. The following values will be returned according to the given modifier: No modifier ("") By default the property will the full name of the selected object. If more than one object is selected, the object names will be separated by line breaks ("\n"). "shortname" Like above, but returns only short name(s) for the object(s). For instance an object inside a list would only be given the name it has inside the list, without the name of the list. "label" Like above, but returns the &rkward; label(s) of the object(s) (if no label available, this is the same as shortname) String list properties This property holds a list of strings. No modifier ("") For getValue()/getString(), this returns all strings separated by "\n". Any "\n" characters in each item are escaped as literal "\n". However, the recommended usage is to fetch the value with getList(), instead, which will return an array of strings. "joined" Returns the list as a single string, with items joined by "\n". In contrast to no modifier (""), the individual strings are _not_ escaped. Code properties A property held by plugins that generated code. This is important for embedding plugins, in order to embed the code generated by the embedded plugin into the code generated by the embedding (top-level) plugin. The following values will be returned according to the given modifier: No modifier ("") Returns the full code, &ie; the sections "preprocess", "calculate", "printout", and (but not "preview") concatenated to one string. "preprocess" Returns only the preprocess section of the code "calculate" Returns only the calculate section of the code "printout" Returns only the printout section of the code "preview" Returns the preview section of the code General purpose elements to be used in any XML file (<literal role="extension">.xml</literal>, <literal role="extension">.rkh</literal>, &pluginmap;) <snippets> Allowed as a direct child of the <document> node and only there. Should be placed near the top of the file. See section on using snippets. Only one <snippets> element may be present. Optional, no attributes. <snippet> Defines a single snippet. Allowed only as a direct child of the <snippets/> element. Attributes: <id> An identifier string for the snippet. Required. <insert> Insert the contents of a <snippet>. Allowed anywhere. Attributes: <snippet> The identifier string of the snippet to insert. Required. <include> Include the contents of another XML file (everything inside the <document> element of that file). Allowed anywhere. Attributes: <file> The filename, relative to the directory, the current file is in. Required. Elements to be used in the XML description of the plugin Properties held by the elements are listed in a separate section. General elements <document> Needs to be present in each description.xml-file as the root-node. No special function. No attributes <about> Information about this plugin (author, licence, &etc;). This element is allowed in both an individual plugin's .xml file, and in &pluginmap; files. Refer to the &pluginmap; file reference for reference details, the chapter on 'about' information for an introduction. <code> Defines where to look for the JS template to the plugin. Use only once per file, as a direct child of the document-tag. Attributes: file Filename of the JS template, relative to the directory the plugin-xml is in <help> Defines where to look for the help file for the plugin. Use only once per file, as a direct child of the document-tag. Attributes: file Filename of the help file, relative to the directory the plugin-xml is in <copy> Can be used as a child (direct or indirect) of the main layout elements, &ie; <dialog> and <wizard>. This is used to copy an entire block a xml elements 1:1. Attributes: id The ID to look for. The <copy> tag will look for a previous XML element that has been given the same ID, and copy it including all descendant elements. copy_element_tag_name In some few cases, you will want an almost literal copy, but change the tag-name of the element to copy. The most important example of this is, when you want to copy an entire <tab> from a dialog interface to the <page> of a wizard interface. In this case, you'd set coyp_elemnent_tag_name="page" to do this conversion automatically. Interface definitions <dialog> Defines a dialog-type interface. Place the GUI-definition inside this tag. Use only once per file, as a direct child of the document-tag. At least one of "dialog" or "wizard" tags is required for a plugin. Attributes: label Caption for the dialog recommended Should the dialog be used as the "recommended" interface (&ie; the interface that will be shown by default, unless the user has configured RKWard to default to a specific interface)? This attribute does not currently have an effect, as it is implicitly "true", unless the wizard is recommended. <wizard> Defines a wizard-type interface. Place the GUI-definition inside this tag. Use only once per file, as a direct child of the document-tag. At least one of "dialog" or "wizard" tags is required for a plugin. Accepts only <page> or <embed>-tags as direct children. Attributes: label Caption for the wizard recommended Should the wizard be used as the "recommended" interface (&ie; the interface that will be shown by default, unless the user has configured RKWard to default to a specific interface)? Optional, defaults to "false". Layout elements All elements in this section accept an attribute id="identifierstring". This attribute is optional for all elements. It can be used, for example, to hide/disable the entire layout element and all the elements contained therein (see chapter GUI logic). The id-string may not contain "." (dot) or ";" (semicolon), and should generally be limited to alphanumeric characters and the underscore ("_"). Only the additional attributes are listed. <page> Defines a new page inside a wizard. Only allowed as a direct child of a <wizard> element. <row> All direct children of a "row" tag will be placed left-to-right. <column> All direct children of a "column" tag will be placed top-to-bottom. <stretch> By default, elements in the GUI take up all the space that's available. For instance, if you have two columns side by side, the left one is packed with elements, but the right one only contains a lonely <radio>, the <radio> control will expand vertically, even though it does not really need the available space, and it will look ugly. In this case you really want to add a "blank" below the <radio>. For this, use the <stretch> element. It will simply use up some space. Don't overuse this element, usually it's a good idea for GUI elements to get all the available space, only sometimes will the layout become spaced out. The <stretch> element does not take any arguments, not even an "id". Also you can place no children inside the <stretch> element (in other words, you'll only ever use it as "<stretch/>") <frame> Draws a frame/box around its direct children. Can be used to visually group related options. Layout inside a frame is top-to-bottom, unless you place a <row> inside. Attributes: label Caption for the frame (optional) checkable Frames can be made checkable. In this case, all contained elements will be disabled when the frame is unchecked, and enabled, when it is checked. (optional, defaults to "false") checked For checkable frames only: Should the frame be checked by default? Defaults to "true". Not interpreted for non-checkable frames. <tabbook> Organizes elements in a tabbook. Accepts only <tab>-tags as direct children. <tab> Defines a page in a tabbook. Place the GUI-definition for the tab inside this tag. May be used only as a direct child of a <tabbook> tag. A <tabbook> should have at least two defined tabs. Attributes: label Caption for the tab page (required) <text> Shows the text enclosed in this tag in the GUI. Some simple HTML-style markup is supported (notably <b>, <i>, <p>, and <br/>). Please keep formatting to a minimum, however. Inserting a completely empty line adds a hard line break. Attributes: type Type of the text. One of "normal", "warning" or "error". This influences the look of the text (optional, defaults to normal) Active elements All elements in this section accept an attribute id="identifierstring". This attribute is required for all elements. Only the additional attributes are listed. The id-string may not contain "." (dots). <varselector> Provides a list of available objects from which the user can select one or more. Requires one or more <varslot>s as a counterpart to be useful. Attributes: label Label for the varselector (optional, defaults to "Select variable(s)") <varslot> Used in conjunction with a "varselector" to allow the user to select one or more variables. Attributes: label Label for the varslot (recommended, defaults to "Variable:") source The varselector to fetch the selection from (required, unless you connect manually or using source_property) source_property An arbitrary property to copy values from, when the select button is clicked. If specified, this overrides the "source"-attribute. required Whether - for submitting the code - it is required that this varslot holds a valid value. See required-property (optional, defaults to false) multi Whether the varslot holds only one (default, "false"), or several objects allow_duplicates Whether the varslot may accept only unique objects (default, "false"), or if the same object may be added several times. min_vars Only meaningful if multi="true": Minimum number of vars to be selected for the selection to be considered valid (optional, defaults to "1") min_vars_if_any Only meaningful if multi="true": Some varslots may be considered valid, if, for instance, the varslot is either empty, or holds at least two values. This specifies how many variables have to be selected if any at all (2 in the example). (optional, defaults to "1") max_vars Only meaningful if multi="true": Maximum number of variables to select (optional, defaults to "0", which means no maximum) classes If you specify one or more &r; classnames (separated by spaces (" ")), here, the varslot will only accept objects belonging to those classes (optional, use with great care, the user should not be prevented from making valid choices, and &r; has a lot of different classes!) types If you specify one or more variables types (separated by spaced (" ")), here, the varslot will only accept objects of those types. Valid types are "unknown", "number", "string", "factor", "invalid". (Optional, use with great care, the user should not be prevented from making valid choices, and rkward does not always know the type of a variable) num_dimensions The number of dimensions, an object needs to have. "0" (the default) means, any number of dimensions is acceptable. (optional, defaults to "0") min_length The minimum length, an object needs to have in order to be acceptable. (optional, defaults to "0") max_length The maximum length, an object needs to have in order to be acceptable. (optional, defaults to the largest integer number representable on the system) <valueselector> Provides a list of available strings (not R objects) to be selected in one or more accompanying <valueslot>s. String options can be defined using <option>-tags as direct children (see below), or set using dynamic properties. Attributes: label Label for the valueselector (optional, defaults to no label) <valueslot> Used in conjunction with a <valueselector> to allow the user to select one or more string items. This element is mostly identical to <varslot>, and shares the same attributes, except for those which refer to properties of the acceptable items (&ie; classes, types, num_dimensions, min_length, max_length). <radio> Defines a group of radio-exclusive buttons (only one can be selected at a time). Requires at least two <option>-tags as direct children. No other tags are allowed as children. Attributes: label Label for the radio control (recommended, defaults to "Select one:") <dropdown> Defines a group of options of which one and only one can be selected at the same time, using a dropdown list. This is functionally equivalent to a <radio>, but looks different. Requires at least two <option>-tags as direct children. No other tags are allowed as children. Attributes: label Label for the dropdown list (recommended, defaults to "Select one:") <select> Provides a list of available strings from which the user can select an arbitrary number. String options can be defined using <option>-tags as direct children (see below), or set using dynamic properties. Attributes: label Label for the <select> (optional, defaults to no label) <option> Can only be used as a direct child of a <radio>, <dropdown>, <valueselector> or <select> element. Represents one selectable option in a radio control or dropdown list. As <option> elements are always part of one of the selection elements, they do not normally have an "id" of their own, but see below. Attributes: label Label for the option (required) value The string value the parent element will return if this option is checked/selected (required) checked Whether the option should be checked/selected by default "true" or "false". In a <radio> or <dropdown>, only one option may be set to checked="true", and if no option is set to checked, the first option in the parent element will be checked/selected automatically. In a <select>, any number of options may be set to checked. (optional, default to "false") id Specifying the "id" parameter for the <option> elements is optional (and in fact it's recommended, not to set an "id", unless you really need one). However, specifying an "id" will allow you to enable/disable <option>s, dynamically, by connecting to the boolean property id_of_radio.id_of_optionX.enabled. Currently this works for options inside <radio> or <dropdown> elements, only; <valueselector> and <select> options do not currently support ids. <checkbox> Defines a check box, &ie; a single option that can either be set to on or off. Attributes: label Label for the check box (required) value The value the check box will return if checked (required) value_unchecked The value that will be returned if the check box is not checked (optional, defaults to "", &ie; an empty string) checked Whether the option should be checked by default "true" or "false" (optional, default to "false") <frame> The frame element is generally used as a pure layout element, and is listed in the section on layout elements. However, it can also be made checkable, thus acting like a simple check box at the same time. <input> Defines a free text input field. Attributes: label Label for the input field (required) initial Initial text of the text field (optional, defaults to "", &ie; an empty string) size One of "small", "medium", or "large". "large" defines a multi-line input field, "small", and "medium" are single line fields (optional, defaults to "medium") required Whether - for submitting the code - it is required that this input is not empty. See required-property (optional, defaults to false) <matrix> A table for entering matrix data (or vectors) in the GUI. This input element is not optimized for entering editing large amounts of data. While there is no strict limit on the size of a <matrix>, in general it should not exceed around ten rows / columns. If you expect larger data, allow users to select it as an R object (which may be a good idea as an alternative option, in almost every instance where you use a matrix element). Attributes: label Label for the table (required) mode One of "integer", "real", or "string". The type of data that will be accepted in the table (required) min Minimum acceptable value (for matrices of type "integer" or "real") (optional, defaults to the smallest representable value) max Maximum acceptable value (for matrices of type "integer" or "real") (optional, defaults to the largest representable value) allow_missings Whether missing (empty) values are allowed in the matrix. This is implied for matrices or mode "string" (optional, defaults to false). allow_user_resize_columns When set to true, the user can add columns by typing on the rightmost (inactive) cells (optional, defaults to true). allow_user_resize_rows When set to true, the user can add rows by typing on the bottommost (inactive) cells (optional, defaults to true). rows Number of rows in the matrix. Has no effect for allow_user_resize_rows="true". This can also be controlled by setting the "rows" property". (optional, defaults to 2). columns Number of columns in the matrix. Has no effect for allow_user_resize_columns="true". This can also be controlled by setting the "columns" property". (optional, defaults to 2). min_rows Minimum number of rows in the matrix. The matrix will refuse shrink below this size. (optional, defaults to 0; see also: allow_missings.). min_columns Minimum number of columns in the matrix. The matrix will refuse shrink below this size. (optional, defaults to 0; see also: allow_missings.). fixed_height Force the GUI element to stay at its initial height. Do not use in combination with matrices, where the number of rows may change in any way. Useful, esp. when creating a vector input element (columns="1"). With this option set to true, no horizontal scroll bar will be shown, even in the matrix exceeds the available width (as this would affect the height). (optional, defaults to false). fixed_width Slightly misnamed: Assume the column count will not change. The last (or typically only) column will be stretched to take up the available width. Do not use in combination with matrices, where the number of columns may change in any way. Useful, esp. when creating a vector input element (rows="1"). (optional, defaults to false). horiz_headers Strings to ues for the horiztonal header, separated by ";". The header will be hidden, if set to "". (optional, defaults to column number). vert_headers Strings to ues for the vertical header, separated by ";". The header will be hidden, if set to "". (optional, defaults to row number). <optionset> A UI for repeating a set of options for an arbitrary number of items (introduction to optionsets). Attributes: min_rows If specified, the set will be marked invalid, unless it has at least this number of rows (optional, integer). min_rows_if_any Like min_rows, but will only be tested, if there is at least one row (optional, integer). max_rows If specified, the set will be marked invalid, unless it has at most this number of rows (optional, integer). keycolumn Id of the column to act as keycolumn. An optionset with a (valid) keycolumn will act as a "driven" optionset. An optionset with no keycolumn will allow manual insertion / removal of items. The keycolumn must be marked as external. (optional, defaults to no keycolumn). Child-elements: <optioncolumn> Declares one optioncolumn of the set. For each value that you want to fetch from the optionset, you must declare a separate <optioncolumn>. Attributes: id The id of the optioncolumn (required, string). external Set to true, if the optioncolumn is controlled from outside the optionset (optional, boolean, defaults to false). label If given, the optioncolumn will be displayed in a column by that label (optional, string, defaults to not displayed). connect The property to connect this optioncolumn to, given as id inside the <content>-area. For external <optioncolumn>s, the corresponding value will be set to the externally set value. For regular (non-external) <optioncolumn>s, the corresponding row of the <optioncolumn>-property, will be set when the property changes inside the content-area. (optional, string, defaults to not connected). default Only for external columns: The value to assume for this column, if no value is known for an entry. Rarely useful. (Optional, defaults to empty string) <content> Declare the content / UI of the set. No attributes. All usual active, passive, and layout elements are allowed as childname elements. In addition, in earlier versions of RKWard (up to 0.6.3), the special child-element <optiondisplay> was allowed. This is obsolete in RKWard 0.6.4, and should simply be removed from existing plugins. <logic> Optional specification of UI logic to apply inside the contents region the optionset. See the reference on <logic> <browser> An element designed to select a single filename (or directory name). Note that this field will take any string, even though it is meant to be used for files, only: label Label for the browser (optional, defaults to "Enter filename") initial Initial text of the browser (optional, defaults to "", &ie; an empty string) type One of "file", "dir", or "savefile". To select an existing file, existing directory, or non-existing file, respectively (optional, defaults to "file") allow_urls Whether (non-local) urls can be selected (optional, defaults to "false") filter File type filter, ⪚ ("*.txt *.csv" for .txt and .csv files. Try not to induce limits unless absolutely needed, though) (optional, defaults to "", &ie; all files) required Whether - for submitting the code - it is required that the field is not empty. Note that this does not necessarily mean, the selected filename is valid! See required-property (optional, defaults to true) <saveobject> An element designed to select the name of an &r; object to save to (&ie; generally not already existing, in contrast to a varslot): label Label for the input (optional, defaults to "Save to:") initial Initial text of the input (optional, defaults to "my.data") required Whether - for submitting the code - it is required that the field holds a permissible object name. See required-property (optional, defaults to true) checkable In many use cases, saving to an &r; object is optional. In these cases, a check box can be integrated into the saveobject-element using this attribute. When set to true, the saveobject will be activated / deactivated by the check box. See the active-property of saveobject (optional, defaults to false) checked For checkable saveobject-elements, only: Whether the control is checked/enabled by default (optional, defaults to false) <spinbox> A spinbox in which the user can select a numeric value, using either direct keyboard input or small up/down arrows. Attributes: label Label for the spinbox (recommend, default to "Enter value:") min The lowest value the user is allowed to enter in the spinbox (optional, defaults to the lowest value technically representable in the spinbox) max The largest value the user is allowed to enter in the spinbox (optional, defaults to the highest value technically representable in the spinbox) initial The initial value shown in the spinbox (optional, defaults to "0") type One of "real" or "integer". Whether the spinbox will accept real numbers or only integers (optional, defaults to "real") default_precision Only meaningful if the spinbox is of type="real". Specifies the default number of decimal places shown in the spinbox (only this many trailing zeros will be shown). When the user presses the up/down arrows, this decimal place will be changed. The user may still be able to enter values with a higher precision, however (see below) (optional, defaults to "2") max_precision The maximum number of digits that can be meaningfully represented (optional, defaults to "8") <formula> This advanced element allows the user to select a formula/set of interactions from selected variables. For instance for a GLM, this element can be used to allow the user to specify the interaction-terms in the model. Attributes: fixed_factors The ID of the varslot holding the selected fixed factors (required) dependent The ID of the varslot holding the selected dependent variable (required) <embed> Embed a different plugin into this one (see chapter on embedding). Attributes: component The registered name of the component to embed (see chapter on registering components (required) as_button If set to "true", only a pushbutton will be placed in the embedding GUI, the embedded GUI will only be shown (in a separate window) when the pushbutton is pressed (optional, default is "false") label Only meaningful if as_button="true": The label of the button (recommend, default is "Options") <preview> Checkbox to toggle preview functionality (see chapter on graph previews). Attributes: label Label of the box (optional, default is "Preview") Logic section <logic> The containing element for the logic section. All elements below are allowed only inside the <logic> element. The <logic> element is allowed only as a direct child of the <document> element (at most once per document), or of <optionset> elements (at most once per optionset). The document's logic section applies to both <dialog> and <wizard> GUIs in the same way. <external> Creates a new (string) property that is supposed to be connected to an outside property if the plugin gets embedded. See section on "incomplete" plugins. Attributes: id The ID of the new property (required) default The default string value of the new property, &ie; the value used, if the property is not connected to an outside property (optional, defaults to an empty string) <i18n> Creates a new (string) property that is supposed to be provide an i18n'ed label. Attributes: id The ID of the new property (required) label The label. This will be translated. (required) <set> Set a property to a fixed value (of course, if you additionally connect the property to some other property, the value does not remain fixed). For instance, if you embed a plugin, but want to hide some of its elements, you might set the visibility property of those elements to false. Useful esp. for embedded/embedding plugins. Note: If there are several <set> elements for a single id, the latest one to be defined takes precedence. This will sometimes be useful to rely on when using <include>d parts. Attributes: id The ID of the property to set (required) to The string value to set the property to (required). Note: For boolean properties such as visibility, enabledness, you'll typically set the to attribute to either to="true" or to="false". <convert> Create a new boolean properties that depends on the state of one or more different properties. Attributes: id The ID of the new property (required) sources The ids of the properties this property will depend on. One or more properties may be specified, separated by ";" (required) mode The mode of conversion/operation. One of "equals", "notequals", "range", "and", "or". If in mode equals, the property will only be true, if the value of all of its sources equals the attribute standard (see below). If in at mode notequals, the property will only be true, if the value of all of its sources are different from the attribute standard (see below). If in mode range, the sources have to be numeric (integer or real). The property will only be true, if all sources are in the range specified by the attributes min and max (see below). If in mode and, the sources have to be boolean properties. The property will only be true, if all the sources are true simultaniously. If in mode or, the sources have to be boolean properties. The property will only be true, if at least one of the sources is true. (required) standard Only meaningful in modes equals or notequals: the string value to compare against (required if in one of these modes) min Only meaningful in mode range: the minimum value to compare against (optional, defaults to the lowest floating point number representable on the machine) max Only meaningful in mode range: the maximum value to compare against (optional, defaults to the largest floating point number representable on the machine) require_true If set to "true", the property will become required, and will only be considered valid, if its state is true/on. Hence, if the property is false, it will block the Submit button (optional, defaults to "false". If you use this, make sure the user can easily detect what's wrong, such as by showing an explanatory <text>) <switch> Create a new property that will relay to different target properties (or fixed strings) based on the value of a condition property. This allows to create logic similar to if() or switch() constructs. Attributes: id The ID of the new property (required) condition The id of the condition property (required) Child elements: <true> If the condition property is boolean, you can specify the two child elements <true> and <false> (and only these). (Required, if <false> is also given) <false> If the condition property is boolean, you can specify the two child elements <true> and <false> (and only these). (Required, if <true> is also given) <case> If the condition property is not boolean, you can supply and arbitrary number of <case>-elements, one for each value of the condition property that you want to match (at least one such element is required, if the condition property is not boolean) <default> If the condition property is not boolean, the optional <default>-element, allows to specify the behavior, if no <case> element is matches the value of the condition property (optional, allowed only once, in combination with one or more <case> elements). Child elements <true>, <false>, <case>, and <default> take the following attributes: standard Only for <case>-elements: The value to match the condition property against (required, string). fixed_value A fixed string that should be supplied as the value of the <switch> property, if the current condition matches (required, if dynamic_value is not supplied). dynamic_value The id of the target property that should be supplied as the value of the <switch> property, if the current condition matches (required, if fixed_value is not supplied). <connect> Connects two properties. The client property will be changed whenever the governor property changes (but not the other way around!). Attributes: client The ID of the client property, &ie; the property that will be adjusted (required) governor The ID of the governor property, &ie; the property that will adjusts the client property. This may include a modifier (required) reconcile If "true", the client property will make adjust the governor property on connection in such a way that the governor property will only accept values that are also acceptable by the client (⪚ suppose the governor is a numeric property with min value "0", and the client is a numeric property with min value "100". The min of both properties will be adjusted to 100, if reconcile="true"). Generally works only for properties of the same basic type (optional, default to "false") <dependency_check> Creates a boolean property that is true, if the specified dependencies are met, false otherwise. The xml syntax of the element is the same as that of the <dependencies> element, described in the &pluginmap; reference. As of &rkward; 0.6.1, only the &rkward; and &r; version specifications are taken into account, not dependencies on packages or pluginmaps. <script> Define script code to control UI logic. See the section on scripted GUJI logic for details. The script code to run can be given either using the "file" attribute, or as a (commented) text of the element. The <script> element is not allowed in the <logic> section of an optionset. Attributes: file File name of the script file. (required) Properties of plugin elements All layout elements, and all active elements hold the following properties, accessible via "id_of_element.name_of_property": visible Whether the GUI element is visible or not (boolean) enabled Whether the GUI element is enabled or not (boolean) required Whether the GUI element is required (to hold a valid setting) or not. Note that any element which is disabled or hidden is also implicitly non-required (boolean). In addition to this, some elements have additional properties you can connect to. Most active elements also have a "default" property whose value will be returned on calls to getBoolean/getString/getList ("..."), if no specific property was named, as described below. <text> Default property is text text The text displayed (text) <varselector> No default property selected The objects currently selected. You probably do not want to use this. Used internally (RObject) root The root/parent object of the objects offered for selection (RObject) <varslot> Default property is "available" available All objects held in the varslot (RObject) selected Of the objects held in the varslot, those that are currently selected. You probably do not want to use this. Used internally (RObject) source A copy of the objects selected in the corresponding varselector. You probably do not want to use this. Used internally (RObject) <valueselector> Default property is "selected" selected The strings currently selected. Modifier "labeled" to retrieve the corresponding labels. In a <valueselector> you probably do not want to use this, directly (only in a <select>). (read/write StringList) available The list of string values to select from. (read/write StringList) labels Labels to display for the string values. (read/write StringList) <valueslot> Same as <varslot>, but the properties are lists of strings, instead of RObjects. <radio> Default property is "string" string The value of the currently selected option (string) number The number of the currently selected option (options are numbered top-to-bottom, starting at 0) (integer) <dropdown> Same as <radio> <select> Same as <valueselector> <option> No default property. "enabled" is the *only* property, and it is not currently available for options inside a <select> or <valueselector>. <option> does not have the "visible" or "required" properties. enabled Whether this single option should be enabled or disabled. In most cases you will enable/disable the entire <radio< or <dropdown<, instead. But this can be used to dynamically set the enabledness of a single option inside a <radio< or <dropdown< (bool) <checkbox> Default property is "state.labeled", which means that the values specified by the value, and value_unchecked-attributes are returned, not the displayed label of the check box. state State of the check box (on or off). Note that useful modifiers of this property (as of all boolean properties) are "not" and "labeled" (see types of properties). However, often it is most useful to connect to the property with no modifier, &ie; "checkbox_id.state", which will return the state of the check box in a format suitable for use in an if statement (0 or 1). (boolean) <frame> Default property is "checked", if - and only if - the frame is checkable. For non-checkable frames, there is no default property. checked Available for checkable frames, only: state of the check box (on or off). Note that useful modifiers of this property (as of all boolean properties) are "not" and "numeric" (see types of properties). (boolean) <input> Default property is "text" text Current text in the input field (string) <matrix> Default property is "cbind". rows Number of rows in the matrix (integer). If the matrix allows the user to add / remove rows, this property should be treated as read-only. Otherwise, changing it, will change the size of the matrix. columns Number of columns in the matrix (integer). If the matrix allows the user to add / remove columns, this property should be treated as read-only. Otherwise, changing it, will change the size of the matrix. tsv Data in the matrix in tsv format (string; read-write). Note that compared to the usual tsv layout, columns, not rows, are separated by newline characters, and cells within a column are separated by tabulator characters. 0,1,2... The data from a single column (0 for leftmost column). getValue()/getString() returns this as a single string, separated by "\n". However, the recommended way to get this is using getList(), which returns this column as an array of strings. cbind Data in a format suitable for pasting to R, wrapped in a cbind statement (string; read-only). <optionset> No default property. row_count Number of items in the optionset (integer). Read-only. current_row Currently active item in the optionset (integer). -1 for no active item. Read-write. optioncolumn_ids For each <optioncolumn> you define, a string list property will be created with the specified id. <browser> Default property is "selection" selection Current text (selected file name) in the browser (string) <saveobject> Default property is "selection" selection Full name of the selected object (string; read-only - to set this programmatically, use "parent", and "objectname") parent The parent object of the selected object. This is always an existing &r; object of a type that can contain other objects (⪚ a list or data.frame). When set to an empty string or an invalid object, ".GlobalEnv" is assumed (RObject) objectname The base-name of the selected object, &ie; the string entered by the user (changed to a valid &r; name, if necessary) (string) active For checkable saveobjects, only: Whether the control is checked/enabled. Always true for non-checkable saveobjects (bool) <spinbox> Default property is either "int" or "real.formatted" depending on the spinbox's mode int Integer value held by the spinbox, or nearest integer, if in real mode (integer) real Real value held by the spinbox (and integer, if in integer) (real) <formula> Default property is "model" model The current model string (string) table The data.frame holding the required variables. If variables from only one data.frame are used, the name of that data.frame is returned. Otherwise a new data.frame is constructed as required (string) labels If variables from multiple data.frames are involved, their names may get mangled (for instance, if both data.frames contain a variable named "x"). This returns a list with the mangled names as indices and the descriptive label as value (string) fixed_factors The fixed factors. You probably do not want to use this. Used internally (RObject) dependent The dependent variable(s). You probably do not want to use this. Used internally (RObject) <embed> No default property code The code generated by the embedded plugin (code) <preview> Default property is "state" state Whether the preview box is checked (not necessarily whether the preview has already been shown) (boolean) <convert> This element (used in the <logic> section) is special, in that is technically *is* a property, instead of just holding one or more properties. It is of boolean kind. Note that useful modifiers of this property (as of all boolean properties) are "not" and "numeric" (see types of properties) <switch> This element (used in the <logic> section) is special, in that is technically *is* a (string) property, instead of just holding one or more properties. It allows to switch between several target properties depending on the value of a condition property, or to re-map values of the condition property. Any modifiers that you supply are passed on to the target properties, thus, ⪚ if all target properties are RObject properties, you can use the "shortname" modifier on the switch, too. However, if the target properties are of different types, using modifiers may lead to errors. For fixed_values, any modifier is dropped, silently. Note that target properties, when accessed through a switch, are always read-only! Embeddable plugins shipped with the official RKWard release A number of embeddable plugins is shipped with &rkward;, and can be used in your own plugins. Detailed documentation is currently available only in these plugins source or help files. However, here is a list to give you a quick overview of what is available: Standard embeddable plugins ID Pluginmap Description Example usage rkward::plot_options embedded.pluginmap Provides a wide range of options for plots. Most plotting plugins utilize this. Plots->Barplot, most other plotting plugins rkward::color_chooser embedded.pluginmap Very simple plugin for specifying a color. Current implementation provides a list of color names. Future implementations may provide more elaborate color picking. Plots->Histogram rkward::plot_stepfun_options embedded.pluginmap Step function plot options Plots->ECDF plot rkward::histogram_options embedded.pluginmap Histogram (plot) options Plots->Histogram rkward::barplot_embed embedded.pluginmap Barplot options Plots->Barplot rkward::one_var_tabulation embedded.pluginmap Provides tabulation on a single variable. Plots->Barplot rkward::limit_vector_length embedded.pluginmap Limit the length of a vector (to the n largest or smallest elements). Plots->Barplot rkward::grid embedded.pluginmap Add grid to a plot In an existing plot window: Edit->Draw grid. rkward::level_select embedded.pluginmap Provides a <valueselector> filled with the levels (or unique values) of a vector. Data->Recode Categorical data rkward::multi_input embedded.pluginmap Combines spinbox, input and radio controls to provide input for character, numeric, logical data. Data->Recode Categorical data
Elements for use in &pluginmap; files <document> Needs to be present in each &pluginmap; file as the root-node (exactly once). Attributes: base_prefix Filenames specified in the &pluginmap; file are assumed to be relative to the directory of the &pluginmap; file + the prefix you specify here. Useful, esp., if all your components are located below a single subdirectory. namespace A namespace for the component ids. When looking up components for embedding, the components will beretrievable via a string "namespace::component_id". Set to "rkward" for now. id An optional identifier string for this &pluginmap;. Specifying this allows third authors to refer to and load your &pluginmap; from theirs (see chapter on handling dependencies. priority One of "hidden", "low", "medium", or "high". &pluginmap;s with priority "medium" or "high" are activated, automatically, when &rkward; first finds them. Use priority="hidden" for &pluginmap;s that are not meant to be activated, directory (only meant for inclusion). In the current implementation this does not actually hide the &pluginmap;, however. (Optional, defaults to "medium"). <dependencies> This element, specifying dependencies, is allowed as a direct child of the <document> element (once), and as a child of <component> elements (once for each <component> element). Specifies the dependencies that must be met in order to use the plugin(s). See the chapter on dependencies for an overview. Attributes: rkward_min_version, rkward_max_version Minimum and maximum allowed version of &rkward;. Version specifications may include non-numeric suffixes, like "0.5.7z-devel1". If a specified dependency is not met, the plugin(s) it applies to will be ignored. More information. Optional; if not specified, no minimum / maximum version of &rkward; will be required. R_min_version, R_max_version Minimum and maximum allowed version of &rkward;. Version specifications may not include non-numeric suffixes, like "0.5.7z-devel1". The R version dependency will be shown on the plugins' help pages, but does not have any direct effect, as of &rkward; 0.6.1. More information. Optional; if not specified, no minimum / maximum version of &r; will be required. Child elements: <package> Adds a dependency on a specific &r; package. Attributes: name Package name (required). min_version, max_version Minimum / maximum allowed version (optional). repository Repository where the package can be found. Optional, but highly recommended, if the package is not available on CRAN. <pluginmap> Adds a dependency on a specific &rkward; &pluginmap;. Attributes: name Id string of the required &pluginmap; (required). min_version, max_version Minimum / maximum allowed version (optional). url URL where the &pluginmap; can be found. Required. <about> May be present exactly once as a direct child of the <document> element. Contains meta information on the &pluginmap; (or plugin). See the chapter on 'about' information for an overview. Attributes: name User visible name. Optional. Does not have to be the same as the "id". version Version number. Optional. The format is not restricted, but to be on the safe side, please follow common versioning schemes such as "x.y.z". releasedate Release date specification. Optional in format "YYYY-MM-DD". shortinfo A short description of the plugin / &pluginmap;. Optional. url URL where more information can be found. Optional, but recommended. copyright Copyright specification, ⪚ "2012-2013 by John Doe". Optional, but recommended. licence License specification, ⪚ "GPL" or "BSD". Please make sure to accompany your files with a complete copy of the relevant licence! Optional, but recommended. category Category of plugin(s), ⪚ "Item response theory". As of &rkward; 0.6.1, no categories are predefined. Optional. Child elements: <author> Adds information on an author. Attributes: name, given, family Either specify the full name for name, or specify both given and family, separately. role Author role description (optional). email E-mail address, where author can be contacted. Required. May be set to the rkward-devel mailing list, if you are subscribed, and your plugin is meant to be included in the official &rkward; release. url URL with more information on the author, ⪚ homepage (optional). <components> Needs to be present exactly once as a direct child of the <document> element. Contains the individual <component>-elements described below. No attributes. <component> One or more <component> elements should be given as direct children of the <components> element (and only there). Registers a component/plugin with rkward. Attributes: type For future extension: The type of component/plugin. Always set to "standard" for now (the only type currently supported). id The ID by which this component can be retrieved (for placing it in the menu (see below), or for embedding). See <document/gt;-namespace above. file Required at least for components of type="standard": The filename of the XML file describing the GUI. label The label for this component, when placed in the menu hierarchy. <attribute> Defines an attribute of a component. Only meaningful for import plugins so far. Only allowed as a direct child of <component>. Attributes: id Id of the attribute value Value of the attribute labels Label associated with the attribute <hierarchy> Needs to be present exactly once as a direct child of the <document> element. Described where the components declared above should be placed in the menu hierarchy. Accepts only <menu> elements as direct children. No attributes. <menu> One or more <menu> elements should be given as direct children of the <hierarchy> element. Declares a new (sub-)menu. If a menu by the given ID (see below) already exists, the two menus are merged. The <menu> element is allowed either as a direct child of the <hierarchy> element (top level menu), or as the direct child on any other <menu> element (sub-menu). Conversely, the <menu> element accepts other <menu> elements or <entry> elements as children. Attributes: id An identifier string of the menu. Useful, when menu definitions are read from several &pluginmap; files, to make sure plugins can be placed in the same menu(s). Some menu-ids such as "file" refer to predefined menus (in this case the "File" menu). Be sure to check with existing &pluginmap; files to use consistent ids. label A label for the menu. group Allows to control ordering of menu entries. See menu item ordering. Optional. <entry> A menu entry, &ie; a menu option to invoke a plugin. May be used only as a direct child of a <menu> element, accepts no child elements. Attributes: component The ID of the component that should be invoked, when this menu entry is activated. group Allows to control ordering of menu entries. See menu item ordering. Optional. <group> Declares a group of items in the menu. See menu item ordering. Attributes: id The name of this group. separated Optional. If set to "true" the item in this group will be visually separated from surrounding items. group The name of the group to append this group to (optional). <context> Declares the entries in a context. Only allowed as a direct child of the <document> tag. Accepts only <menu> tags as direct children. Attributes: id The ID of the context. Only two contexts are implemented so far: "x11" and "import". <require> Include another &pluginmap; file. This &pluginmap; file is only loaded once, even if it is <require>d from several other files. The most important use case is to include a pluginmap file, which declares some comopnents, which are embedded by components declared in this &pluginmap;. <require>-elements are only allowed as direct children of the <document>-node. Attributes: file The filename of the &pluginmap; to include. This is seen relative to the directory of the current &pluginmap; file + the base_prefix (see above, <document>-element). If you do not know the relative path to the &pluginmap; to be included, use the map attribute to refer to it by id, instead. map To include a &pluginmap; file from a different package (or an &rkward; &pluginmap; from your external &pluginmap;, you can refer to it by its namespacename::id, as specified in the required &pluginmap;s <document> element. Inclusion will fail, if no &pluginmap; by that id is known (⪚ not installed on the user's system). You should use this method for including &pluginmap;s outside your package, only. For maps inside your package, specifying a relative path (file attribute) is faster, and more reliable. Elements for use in .rkh (help) files <document> Needs to be present in each .xml file as the root-node (exactly once). No attributes. <title> Title of the help page. This is not interpreted for help pages for a plugin (this takes the title from the plugin itself), only for stand-alone pages. No attributes. The text contained within the <title> tag will become the caption of the help page. May only be defined once, as a direct child of the <document> node. <summary> A short summary of the help page (or what this plugin is used for). This will always be shown at the top of the help page. No attributes. The text contained within the <summary> tag will be displayed. Recommended but not required. May only be defined once, as a direct child of the <document> node. <usage> A slightly more elaborate summary of the usage. This will always be shown directly after the <summary>. No attributes. The text contained within the <usage> tag will be displayed. Recommended for plugin help pages, but not required. May only be defined once, as a direct child of the <document> node. <section> A general purposes section. May be used any number of times as a direct child of the <document> node. These sections are displayed in the order of their definition, but all after the <usage> section and before the <settings> section. The text contained within the <sectoin> tag will be displayed. id An identifier needed to jump to this section from the navigation bar (or a link). Needs to be unique within the file. Required, no default. title The title (caption) of this section. Required, no default. short_title A short title suitable to be displayed in the navigation bar. Optional, defaults to the full title. <settings> Defines the section containing reference on the various GUI settings. Only meaningful and only used for plugin related help pages. Use as a direct child of the <document>. May contain only <setting> and <caption> elements as direct children. No attributes. <setting> Explains a single setting in the GUI. Only allowed as a direct child of the <settings> element. The text contained within the element is displayed. id The ID of the setting in the plugin .xml. Required, no default. title An optional title for the setting. If omitted (omission is recommended in most cases), the title will be taken from the plugin .xml. <caption> A caption to visually group several settings. May only be used as a direct child of the <settings> element. id The ID of the corresponding element (typically a <frame>, <page> or <tab>) in the plugin .xml. title An optional title for the caption. If omitted (omission is recommended in most cases), the title will be taken from the plugin .xml. <related> Defines a section containing links to further related information. Will always be displayed after the <settings> section. No attributes. The text contained within the <related> tag will be displayed. Typically this will contain an HTML-style list. Recommended for plugin help pages, but not required. May only be defined once, as a direct child of the <document> node. <technical> Defines a section containing technical information of no relevance to end users (such as internal structure of the plugin). Will always be displayed last in a help page. No attributes. The text contained within the <related> tag will be displayed. Not required, and not recommended for most plugin help pages. May only be defined once, as a direct child of the <document> node. <link> A link. Can be used in any of the sections described above. href The target url. Note that several rkward specific URLs are available. See section on writing help pages for details. <label> Inserts the value of a UI label. Can be used in any of the sections described above. id The id of the element in the plugin, of which to copy the label-attribute. <various html tags> Most basic html tags are allowed within the sections. Please keep manual formatting to a minimum, however. Functions available for GUI logic scripting Class "Component" Class which represents a single component or component-property. The most important instance of this class is the variable "gui" which is predefined as the root property of the current component. The following methods are available for instances of class "Component": absoluteId(base_id)Returns the absolute ID of base_id, or - if base_id is omitted - the identifier of the component. getValue(id)Discouraged. Use getString(), getBoolean(), or getList(), instead. Returns the value of the given child property. Returns the value of this property, if ID is omitted. getString(id)Returns the value of the given child property as a string. Returns the value of this property, if ID is omitted. getBoolean(id)Returns the value of the given child property as a boolean (if possible). Returns the value of this property, if ID is omitted. getList(id)Returns the value of the given child property as an array of strings (if possible). Returns the value of this property, if ID is omitted. setValue(id, value)Set the value of the given child property to value. getChild(id)Return an instance of the child-property with the given id. addChangeCommand(id, command)Execute command whenever the child property given by id changes. Class "RObject" Class which represents a single &r; object. An instance of this class can be obtained by using makeRObject(objectname). The following methods are available for instances of class "RObject": If any commands are still pending in the backend, the information provided by these methods can be out-of-date by the time that the plugin code is run. Do not rely on it for critical operations (risking loss of data). getName()Returns the absolute name of the object. exists()Returns whether the object exists. You should generally check this before using any of the methods listed below. dimensions()Returns an array of dimensions (similar to dim() in R). classes()Returns an array of classes (similar to class() in R). isClass(class)Returns true, if the object is of class class. isDataFrame()Returns true, if the object is a data.frame. isMatrix()Returns true, if the object is a matrix. isList()Returns true, if the object is a list. isFunction()Returns true, if the object is a function. isEnvironment()Returns true, if the object is an environment. isDataNumeric()Returns true, if the object is a vector of numeric data. isDataFactor()Returns true, if the object is a vector of factor data. isDataCharacter()Returns true, if the object is a vector of character data. isDataLogical()Returns true, if the object is a vector of logical data. parent()Returns an instance of "RObject" representing the parent of this object. child(childname)Returns an instance of "RObject" representing the child childname of this object. Class "RObjectArray" An array of RObject instances. An instance of this class can be obtained by using makeRObjectArray(objectnames). It is particularly useful when dealing with varslots which allow to select multiple objects. include()-function include(filename)can be used to include a separate JS file. doRCommand()-function doRCommand(command, callback) can be used to query R for information. Please read the section on querying R from inside a plugin for details, and caveats.
Troubleshooting during plugin development So you've read all the documentation, did everything right, and still cannot get it to work? Don't worry, we'll work it out. First thing to do is: Activate "RKWard Debug Messages" - window (available from the "Windows" - menu, or right click on one of the tool bars), and then start your plugin, again. As a general rule of thumb, you should not see any output in the messages window when your plugin gets invoked, or at any other time. If there is one, it's likely related to your plugin. See if it helps you. If everything seems fine on the console, try to increase the debug-level (from the command line, using rkward --debug-level 3, or by setting debug level to 3 in Settings->Configure RKWard->Debug. Not all messages shown at higher debug levels necessarily indicate a problem, but chance are, your problem shows up somewhere between the messages. If you still cannot find out what's wrong, don't despair. We know this is complicated stuff, and - after all - possibly you've also come across a bug in &rkward;, and &rkward; needs to be fixed. Just write to the development mailing list, and tell us about the problem. We'll be happy to help you. Finally, even if you found out how to do it on your own, but found the documentation to be not-so-helpful or even wrong in some respects, please tell us on the mailing list as well, so we can fix/improve the documentation. License &underFDL; &documentation.index;
rkward-0.6.4/doc/rkwardplugins/CMakeLists.txt0000644000175000017500000000062312455741220020567 0ustar thomasthomas# NOTE: KDE4_CREATE_HANDBOOK does not support multiple docbooks inside one directory. That's why this is separate. KDE4_CREATE_HANDBOOK (index.docbook INSTALL_DESTINATION ${HTML_INSTALL_DIR}/en) ADD_CUSTOM_TARGET (webdocs COMMAND meinproc4 --stylesheet ${DATA_INSTALL_DIR}/ksgmltools2/customization/kde-web.xsl ${CMAKE_CURRENT_SOURCE_DIR}/index.docbook WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) rkward-0.6.4/doc/rkwardplugins/menu_hierarchy_example.png0000644000175000017500000011343412455741220023257 0ustar thomasthomasPNG  IHDR (WsBITO pHYs:duh IDATxw\ߦAޫt]~vEDP A@={ozSN`)EQ*$el!7o޼<&:E;2/ v ¿MA;J㕵fԶ!d ` qpD{";6~hllH"vCUU !tȟ@~Q9WkhY++s(T@8B?0막g,O%EtZ t7}˯iz6boОv;bp@VwE^p ''7<ApGQl<y!dVhmŵ`}Qww\x+biW`dJ$]w$B l~qWTE#ǟVXl Ous  È". | 3\C&Sz'=em>-lH$T q#J?a*JoQZXZ-ȩPۙ5h/ *J҄QB eiv_ 0R.T*U],񷦢hT\9IhIy *h਩&(=r,#E{K4tw?gB8Me`C 6G:zF&3g͎ٹ{u Edjlk7 ]=!C܃aF2 {^5`׃0fޏ{2߸ygҭn[X2P2f]e֊M_edljfnߟ` E o υ u裦 }d)++\4gy4E {6)驯/J3fL~~>TYYO9 }øq?'G۷ Vk3RRR/”/_lټ-f)//IncI7n;yd~j![ׂ-h>9EsXUH$(WpVGXi`20w#CGb]4/UWOL|bӤ1fwqac:}ܴw,I; zp¤>sr %̚cVwі:X~KO8 ԲYg͞3e؆I޽;a]qƵD"A^1#wD8޸q#$nOfϙckk>ĝ=/?p_|̡aSVV:{##9s疕aBSS3QG'''c~]--crK_QQ`CCޙΝ;ZcƌaeQWm)gffb\\^^q 0#3eY3fȑ#Yu3Ǝ򿬬tYJPGGheE Y5VSSY/^]A@м|^޶6XΟ?k⌦}ձ3z##cMM#F$'_VN;ǎ;pѣGsO-ly^^^cƌ1740]Z3+x%Eqfz&us^޳w/KC^~~n~~.Kr_\ w/V[[{0m?5|m,w?~رS\'{ seKc QVQ___XPc``.[Y歛JJYll‚_z7X̘9399CȳOo7%sqd_v'uu6U'6YoTVRyR^0%%5.arkk׆`ׇ$'nM~݆aÇ'%=2d}1uBCCSSSv,(Ƕ6b`0>|st:2޾|x$.vnڠ(x۬222d2yY9۞g^XYU{`ظE>:EQ-M=$$>Gcd;Z&&fgDQ4:zŋQZQ: ]];bccUk, knO{d2G `ff*+#Q#q''$z{/VSx{kQ(S3331^dDdlll~(W[" q /[~CL=Ç&7&9T*Wj>s/*gjj¡/'''!!1}LL#'+kaaϒ}Ֆ,Ogwn;oO:+;.+ vO###++44ɲoalW/,*zl\eutt,2i߻ך:RIC@0?&&+rCXѣtu0 @`0969rHGG(W(˗W_x ϟKQWJJۏc@aMAPKJJ##6>~tę7nG$Z %( grdag:vk466۾}{8?'D~nz)cccfY 'vXO>!'?~TQQaWdyk 22uuuCw#GogL޺/ve8|`ll,VѣU*USSS{eԎ;cb` .쯡7@,QN[FcEaaӧI'>Ǐ߼9`g~4^S)i55uBnp4 ws Ӏe0UzfG=}gދ.=vD)NlSɛ>n4l^9Q譢 ( _m\yE+vm04&F$ȢGQ"FtxFsl`:w 6n޺#߽{ғϞ>|0 j}B`IwCQfw7L>;]F y  ?`ooGGp˂ u&p׋ >&ݻw ~v毳(˶m G5!!%޹s7JKKjffԔvum٫0k%+(dgrz;w ץlg|WS룩meeI ]<`@,)pJoo5EА5AAƦ/U+,Yjllя# ŭwlmz(..NϢYxή#e.!0oV j]]ss q䛷E>v9Hj>ދh5EEG.).Z Zj&Yjmm.pاStL Hp<.~ُ?H$aO98vtt266`;AkF(8h [ n*mc2[4o|ccӀL:pY BC׬144^|y rtt̗%YGu9’ժBC ? eޥlrscj׭ 200[D06{l!իMLLׄsyخp4w<#CcZ,;wn k׆ZZZذ! G|\ZB[e9R%TK Nr"ߚ|O'74%Y> +͙}Q_W{ܜ5зz!`}"X_gpٱqXfe0Ms6a0OlA u4:Ntc8* EJRB4H5HIJIHPD"!  J ~8]a=/Χc ٭߀$<gs4'Ab0a^Ki$-+^E4"L(_{wͫ8 LFBWgEkj^|Q[_؈ B PBDvvv/=~<~dҤ? >|(.Oqyl]-p:k!6˳D @K*/ h_^}}[^spu5aaaؒ r(My|SS_Q _"":Owt22UW716MKMcS"9p@`YЛbOt8I,He2V6&^08WƌDFD׈堗/u#"wÒ$66]JIIM $ܾAj2I^h6͌-?8j]w22ttK>~8p0v߼~dmn?E JuqqR]N!eHҽ(PR%R  rI^w8$.d Nu LL7{Νjk( A¨P..ntzm4ʄ)E0a𞷖ݻ.P@TT, 55,,FdeՍaQ[Gt?lѓ&#?:eee|zZ;;duuܜढ҇`䨫 55m3?K<ƶ kii?"dFe"oߦ/26657^8iffb.ŵJg") ۷0`BQQqjʛ/mٴc˜2hHK?|x_RRB{?S n339׮8=-[7oΠH7!\:svffF}}}_uܬLm-a޹kw`ડCeeez dvXϩ۴߿hedbZ\>cϟ޸q}I|78t*kK ' \w $OU?ߥ'Nõtna3V..n\4`q6|1I8t܇ӷw>O,7vn,#sɝT^^6o==#GsW▕ͯ.y=Mɱdׯs2?'Naں] rx9;$'s8H+hMa={$\MM &ܼy7761l;zk9NZ>\B\ClmJ߸qqǏKbzq{~=e]B :t;NΠ߲e] rx9=z'7o9,{Fa)(Q--leݺuSVRRM;Cc(H{NWYL` v.<, ]gϟz%%8,lٲw442,-bJ0l0ߥK-+OO)M@]: +|LIfZ*`Ϟ(XsR}fSŜ9uPToS9)))9:zӤ IDAT͛dffIHH| $$(]ܗDžӦ,5ȒHHJ zQi))D}#<5%'$\O/%%h7Qkn~uqlc虙+ffc0GHI͒Uڲe/4`LLӤM6,g~/_?^J7nnv4A *++ رcKK (ZZ_tuqnV+ߵFaEˏ;6Kҥ6mLQk05}=۷Ϛ5k0w_zԩㆆFзG]lâwq 7<}kbi9[>װaG5j-wb_[n{577W:nl|nŋSLwɾ: }Ѣ R룶.44>6P F{*wq:<7uI6@QCCu;JJ+?_+PRزNU76n F662( Kh-+/{.b}eeeydaiiy*bs֔WPx=$[{QϟpWɵׯ^fjf N֯OsAvOxzT3Vءs4 III >}TU߼yb媕+W?yٳg7>$4={vsgɩS=NSWTco*,,1}:KNӈD"033vmPpJ*Çp.tw‚o߾uQMHH-,,WO~P雀t}䓓%g̀o+X.\P77a~9$$kwԌ37wބ |C%ޓ2slf ayy ,y,֭ 335l>nMK_HѸ6lHKO37])AAOJ $iGTxNkIehhpii)훣kn.)}{X7 eM uUU_?2ugcc&})-Ezԭ[78w[55}ǻw/^=ޯTlۺΝeee:::7o`/bʕmNNKhkk߾}',,ŋf1̛\Auq7 xO N;>cvkg,9:xVFqۦN f|]r#G֭[BlSS܆ #;wLiiy ܽo_6wO)D8J'G$DFFT76} ++HMEaɵ˗Ňg?Ihl, **SfsHsT7iMMϝ=%:0gN<F^^\\9p`ODG#(**._{y.88|^̹ZISshx"{ X{g3ZGMcb|[OoLL`0Jk2꒒*'$M/]zʥP9FTUU0΁NgNV{iJvv!^zα@ o 3ϟ7Cȵ$jj}>[\!}iS;I_ Ξ''PUӧa"x ?EIPP1c`=COi()她?wH ]  @ H'48 >هA?v:j:ׯ_:\aQNҽ{=Iw A_ſYXX IR]]PT[eEEϒZot2b;r5aL|r۶t 77Ȉ"0DYXXXzEQw]˾(,,|gggH$ IL$"@   D-`7If'}'%?~$/@ t  C$QNNNYqc]uuͻooSH8BÇY'Yhik m;%({Ҩ DFD@ Ҵ>?h~y388tvfQMUB%2**$$x ++ۻ2&/++_VvDlǖK5gnƻw8( z4WGG,!C?}|ys0I? O>bzFiiin޴ĸu:0;vDGq/!ky? ўPiTN%Q%PE Q,8u@1]588 ߿vUoS|NXޓiהv-mnny]2IIKwM^~)##7EEEC?NNN#F ۉQQ;,͖UVVM2I, SS)ɓ_~T B _|6X^^>8x^ӻb!HNSRVޭ;B!$U*Wqp;F3+RRRUr OOHvjjK,7%V?f?Ozj@@`/)d.oٲJԴ`iO@vvvLt4 d3"P A2 D [8iwivwqA6ϟ=eIQ`0ҨNa21ϙ;l?&L142?~bJj&|i&SR:99ocaa5b!k ^\\VSօ*RZ0يo 4+,544BB566]m(Bh;pͥ&nweevo+_lrs`ٲ )KwVjzB8u7/^EQWWz%K445U6ժϷ4׋8 @HJ-6A$Ν;,+++߽B!D/8t{{ϟ߻3|=zۗflڴ~u͠@;[q{u WVpDHqѪ"2`I/꣦kQ£GTY993#+j쌋OP40 :|Ƨ>~}'O9;;GDD @IXI*} fgg{_o޼Z vr[%%;"wtE mŜ?# CRM7 ܳ+!>ࡣW._ݳgWCEk?j-nO8_V_pLݟV9rsss 7'[ϯ. M܇Bfv||wp_|/NNpþ~̟^񢬬g|;[}waBYi9L̝_VZܽ{„IM'UСÍ͎=?~9|.kL"vD޼u+,,&2N'#{BKzZ lqq7n\KMy}3hƍJJn^W+|FL',l7_DG'ܜ,lc?.]ػoK'dm':ZZ[C}]oeEOOOyS t2"]?Hv@H$LRPPP(= !o/555ŋa z;vD=SWOWG3~¤뛛z{{=zXxScc`РAz)sLhi={\?ޱc'`,dqӖ>_` 4k>sU+Wdj "IV~e\dY YYY66>$; Y[~QSSÄ4:-4l.NLmٲznntj5?M|x!|}]YYْRRv<+V>,mA!24 ' Y6>K50##Ava`kPut,\ONwqp:ke)τֿPh28'9P2M]])=T+N@ JJҩ 5çŸ_V՟_EqO7Qlƚo -j2?pg?Ϭ?ƻZte.~1ĻX_`@INNX<TzzZjYC뿋'@NN&&.O~+Ξ:;_chWsg$kڛؙ2'Yz͙h=o˜&yZUY2hŲܜpѬv3&yp|-5-5;;mٚߪJҫ-hc7y0,XbMR fNu73Y8gMQ/=Ryf9h֔ )_a¸MderdLjƥg[Uzs2YhnEy9JqT |,m% q,[n(R߼u*vTUVt:6}ۦ7^|6QnQkj>زKپ5?ܽu؁}ʊ򺺺[B:Bn)o^Skj{fM۟?4S$$ zۖ{{1/ib[7vRQ^Ah4Zzjʆ[2ޞ<yS)K ϶ؼq|IB0yb;P֯Y NZyyv~0ul@3 m`FKcO&rbg>^o`3Ժ>'!ہ#ˉ#*_ɮhlz$::0u( ph+;X2`i\RNwj@s| (pO>0DRVLܞsNMWy75`M@-m2[E{6W >j| Ի55u0BXPz**UlaU!넬, }{))M=[FPS]zh7'#+ݠg@&S+YX%QV6226v!R=i@U. pRUj۞oº Ru*^[~AY?;AH>?߄ͯ x[ mE{[6Gل܇ 2p~]\[n{o+V|6~qp: oNEt x|F6l$F }Ҫ*N' @'>BC 7( ~ƾUxR?AVr[nrgAgx̗lRN|(>.~I>ۯ_?QRw~'Ϗ6Ϝ9cGʊ P/]T,3$IAA~Qvڼe3 IDAT&XcnawyVV6&wIS 'LIj"# 100>pEzÆM-=L>is k K+V}僦`‚B(.>K, z)*JHPzmm=e>|c'γyO 888?.Zf̘߸qPwo߹wለm!|cLLn'>#"sFEn744(--Äqqϟ=7)o^oڴym[0w8 O9cZl\`~.ضfZq:dbIIIϜ9YYuO@<++@g;N*Kr u4iJL"IDH@D*٣'R&!SRvW$]iJxi_']U /FEEϘ1}&q# @M+<(Qϴ쌋fΘvދs;7?\j]6/_}[Fi5++* B2>UDtO>cdlt^z);]vϟb}7oα'{/$ ` l@ 42VVYYٯ$ggǦGuMКD7o޺wߟO&IQF=L"sݿoΖeN@,γy\"888WOUCATUTY[]pN]]KZ6h//߿}, }_cM*L-hM`ddiZZW\\ srs-,̷na+8hMH2#csfW4qɓ&N/%=\ ~~<-[Fh#$t P }d<Z>0r,[򺮮nٙZ ժg)UGe=>HJJ>o>0?Ӂjb%uP/W/ 8~xgoGqppp~M3h4ڤC՞q8g|Gq~+$ƺ eA^ަ`Ey9ڳ[79woݴ?Do<`DIO߶8-pegeEpBEyyL⢧V/~/IKKs^t)zy_vݺ3ks ? iРC{w$1zӥsgXsCsC{3pL-Muh:ZM0BUe*%nvA+ggaS& 739y\:[@ 8cP{330aEy׼LǺ"iL d2BQVQa'6xٓǰøf{N5џxVMW,]bc返US^?ƻZڙϘ༳='dc?yc<Ǎ3՟1gs{1gށ?cX};7߫^`zboc7iP1;QS.Xlm9ㆹ} *+F8aㇻhk+N+ Lde:cS[.dQ{9rc,I⫴Wi^c)g'{^:vSDG'}[\\or!f+`B+[~,.:~耕- s-+*,ط3z,9F%9YV־߽M$NJKIfiwyܜYwy7lمEQsZV*/3*b"oEi{׉ct:_Čs&zYUY^YQv$ =IJJe+99{+.&!!!9- No?"y-:Oi-OiAjef/ _Fe.MaehXUޑ3_9,Tf3/_'&rLrM-_׬:ni|r*NV@OEE[{G1k5540ɦk33j[?p[|Ut ӗ g^] 6rI-^Y_ P>SkBb"/Y?c|LZm6Lh֌>K`紋,_FV/޾$Z6]PP`s}`hll/K~YW_LjU(_IUn8;?~N#7ZUU}{ĉ-,[9v6>#׭[W^XX-RREBc]pYcY΅m5H2v g ٬Y3Eh_q 33R[91=d?%`R' m_ O﯊6tFѣOY3Z"Ѻ,AnnߧOl"ZU2Sиlz8j"пDE5<=g  >xM<ިao9B/Z@wq5jժ*Yw ^-j֬q4p?X.%? l|eKCfqT/Qzʪ)$?\*z^z& n "P]"G( )3G|4[O.\Xs]FjPЕa1n~ກ my+ܼysIvCcO4%===<<]+/ٷ5k7|V\s==wvqb#ǎ~cxh@ouQ6lܘtKs~/PDRhYr}];/.C\|shS=7uy=3&g? DDG*ŗDru!'7P(eܼ{@ e鯨/$GGDzCи78jFp]79:YgNVqaX>فV\x7lV]pUo6CV:!!aFy7rT֙vmK rQǤ근iJcۗs;ez7Q`G\]ӶVw?l!w S 8R.a[\ <9 8a*GJp"!ܴK'S*\~Z(=.,u)^ǾڷO}mЫ|c< l22.Z2w^œ3`}<0w=?qYz"@ ϲ?{r@B;׉cVՂxap<<1BcNb.[!KYE 7jw}Bw'3 1B8"b,XC⥂!6kYU}''|)ޟkw z?\҃CB"~AaoT⻮u(U>-2Ӊ(ȝP"8@fٮ\`MZ, nSBBq"uwP.ixk ͙"iTp&7p8cBϝ6QGN*ejLr"WO(R~kILV-SRR8q( L&~#.Ϻ̵ڍ*E;|і8(csfZnX 6\)(8?/8fw8x;cr1YtW;3߽wQ}%Wg! vd2D8g"PS/ v_l!u,/ )AG?ݫ)ӟz 9F5m.>#Kѽ[o sFJwb^!>W]W3J)B頂paXVг{PH~2^RgMgVU>j\EJՓjCʜ_pBΚG8Pq<H)@ ZTV*A"pYx EYEwxU;+?ow_Y\pmֳfz_h; 7ݺuc*־{)99YA}eѳ!1KI6AA )%0P DRhXVq Y)w=gӽ4nȝd1];yAJ`28anN,"K*g4p8+_mTtFA~K*%2X,ӧ޽[zuOvTa3OQ3œƵ7ڵj"t:W@ H͔D/To#JD`@Rd@Q5bJ!0!nm>|!g]K$t܁!bl!v]v#*.vABB8bڭQ.Nݒ5nU4P%U4-ߕo0xހ "$6BK㟌#?S˗-}.or;$4 ] )B(RR>.FWJ(DaV%<<̧7ΔqJ$ #q8 B[fs81slJpmsO%=V( IDATL5øзBK㱖 c̑Æ$mڠQ2Y7mm6ŋir|`0\@lurj@4#0Ĝ+|flDY8b #MeIQ'1Sd>("VhϷܶ@nݔ!A_7Z|w̄z`r3͛G;v2aQѱ=lfffȏM:z[k7 }[UǕ.8|;C\Lj^M2eڜ}?}Sty]yJrsrғֽ1ԏKʍj\Rjc8!@c`!bcq*MH-,J `J@  m{W8b3,??{֮\:ilBȲE k´Yw gAu~-444?ҬQq_^y9GHLqK:us{e[w6%v;d59W|ݱCP߯ |be&QM5r[|ԡ}>}zB;/Ò[s T=_ [ .B@ @M6aQ`6fX,an=(Y&z?@;|h͘ =ժU5k(~%k'_la¨1,jf-~D)%.Ϭ߰ec)oyg?;fd'_z;KM[OF^bZn|ӼY,;w!:ɚՉk?Bgw6[l&MZt DxGy9Q!*92&# NEBˆ-&H9GHAm`=aʺ-})zָf-B@i%Z!o ި{v]lwe%}C m^tpҕkׯh.]ӣ}wD>qibKn};7~g>{,1qoO>4a-fz~F5 ޭ[ZP\ө;G 1uK@\@ŧmڵc@|.V9}VxxH}ɳn߼m7jDZ"]^)IK𐟟WV'3kj ԗ>?5gvr:zEO>蚵YJtTӵ6=ݻw)m+U4[UےҥK6n͚7lؐjnϽȖb\ML\}5sܭ kToDG ]xmڽ4u2lĻ듖O`T~^~lf6n+1&N5;(BSH.{QX,ڸi mldy@7]b^ΘKP>ڥS5 rTTt"Ë/msoxx3|;)ikm/xs蛱7R -]lw~[ѭŗQ$ 33K~B;{vл6a͛v>0Q8,4$?/G2wyNۣW~ Zj" N~9֎8 &&jՊE0AͶܶ4j@L/6l[oAC\L&M_|ji,P&q=dggϛx;4wB?ۿuγgcnXo.)~͛s\LLI"J5.)8x8-x KOKbe;t{G:jRN 7]U{\r~AtZ]|HNCj׮5q̬tI? D ;Z4G&O/w1@%˧O~H{Ax%N69&&:f:l)[JRtJJjjJP^`Г߫C e#mpڡmrR;TP$ *Wsag޺*EWfv0=t߾u(g%zx622ׯ[d;&QMߌl]p]6OLu9|X۶ffd֯_"pb6MpPR ))mb㘘Rzy{ؿGQ9 ?gP*R.l,nE[V;~JAAAlLꕋ¼K_l4bKXլYEE6oslVrZCMi))JIXxז-MSVNvΫnCwlʞ9gɓޫ^|{3f4 n=ǰ7C |歌x٧OӰA[*?-民/]ޖƎY9 ?١c_nnU_2۶|&kzYD~$8O4v=UV|bZX cl՛#7ʿT3TPdݻ(jf= 0]1v=fu"`Bн{nݺ0*\z%P1!(5j՘0a\ƭԂ\eb=VS|G?ddd:=8tX qYR57oXiˎeIS'Oq] K5m8:);lX#_gWp9?_@*'3bXoڼ#qS'(3'.M޽ۇoL"WA%KWM2>&iz-KB{H[.Jӂ같CBë='҃R%H\(Yw  "JXP\%TJ DS@w1 .N.~3=HZpLp@l,2:u=}cv=g~=v|7R=powХK{{TT|ԓ#"@)yO0p Ͽ4XU#g9PЭ[`tT5k7'wZdMe{|Iߵdy^֬SJ@\rί͕.K9"D&R*ӟxO 9L+ٍ:YTiPLDNG/ H`Z#,ӯWvmfYv|yrS9cBb߷6lzC1VҲkLn@'=W![Z tú屮seif-pάݶ}77g@{R.`[%80+7x9"N51s%+-in)ϳtS~_"JAQ0K%˪y_Cmq.C /:'> %%~3ڷo%ㄉӺd4@odmձ}[չaau4yݺ¢'iiZ33oֵs~ջGvqͺͯ z%*IzȆk3xvm (/AO~7YNHHhxDhyo"jb%tEhټ|=1׉(.xJHiz<v:g?u]ev۝*QE2FMP.z#1rk_z5b;ÇΜ=al>is'6ߑS9éY\OԩÈX/0e#ykʵ&LesW\,H6n /4l8xY.tnS .k/)/lҤ)#%Ѣm6]m2zy%t (@Taw6=777?͛z@94L!v[aj6@M "b򻤟!Np8͞WPp[=VvAYJ!8$Tb4>jo#CStQTBZqA`҇@3?J3LĬ"D$8B1v`[]6y?΀o]9WI wʉοʿJ,<*s5:Iˮt za;[T8!}AAAXvnw}_6Ӫ=|uz_{~;b4?ٹ}ib PP|ԬQc'_tRrrr?"gYYG{.U`wMC  M`}Ǯ}a[,ci9gϙ~"4,d 2<1q!$dAv-k[\oTL ב %'@ `XlyyOW/]slllJ;Bu啻 7ifbrv-]?pMxAf0 pl 0y8#3Ƙ6s=Enwu.ֺxʝCApL& @ F6("9݄roqP6'2@Yveˎ.7 ^֪UKt ,HS֮]֮ݰ`AS~m2>56_Az׳.lvHػw3MΜ1N_7t֜6*HHX4wؘBŞ!j|Ξ=~Vzux398d2s Tyyo |˖6lR[5lPs찰0x'.^ڴS_ .zoA F*eݵ'66f?߿w!K7nDmڴ-,pJVf&B b@m8Z>xo?r{1meyEK|wL~~A׸{#g̚w+#e׭3=|]Q^T=Q_TzhA^h0`yIO̓yN9Pgԡ@bF~i*h]wWq%r D X-A a )$/)`[c|Nȯ^Gyc@$-=zZ*ch 2MF#*\>wA }wvD B{ڧfQn;nNzgx`@=Q[Ϥ]u3 1d3N{jԬf488X>]J,b3@ATBl6fvn1 @#L`c ;BQBrE$4zRH@Jd\?[a G d4_x )#+iM?2J]]E(GF5Jc (a1V+o00ѝ9Q~gUˡY]wWE]*ǘ}0G!`! 8]0H =&[XU@XCCÊ^d@#Gy,I2P~n>mcK| ڙ s@*ca1aI]"cCnrywٮwJARJ#B$H sR\I E1&IU|Jh=ab7#c6s aTugTVWw"iWK(?N;!855_1ħ'~i馴AAL8!BH9œ:ƶMA&,7^I~?}c&)(s`!0,IDAT0Dr8ɰew|e )E{U[Bo@@)ɝ1dU1s fjJ,Jma$s /G/ BNCJ}<󆦘`)'„r?^"XCsզcP)jtvg9CqxdWkT`J|lJZr#[.ㆳOSaBh]Si삕'*7F'r# Oh^,d QnPnI+:KSg'iCNe]3~<.jwEaG  + Hf0#g$NTI?.>:btR$_l"(FR#1P0']]99 dUt|gY?}WSAGN1xҕE٤Ιg[u%.??UX\) :{D @ ]t&x]=WGBO=EH94T_$s:G)8mjWqs<5NW Hmde&V $-sk=&Hiٖ@%WवMt@:jf,_ڍ+ $ֲ@cxKNU\}¡VV4JAx*)]G[LȢ.*+Eb(D)e2L/$pZQ7`|gjN*'*ݤmvtEʀ3E,g6ןrbW*ݍЭU%*!Ht0ǩQ|L w`7ɀ]нvTXwZ7ZLPcbRc!YWQZ~i$MKZgLNdc-MepbkI3E<0O@+ W@sܕIw*w!=k-C.!OAr"X 5>;BWfQIGKQFc'kNw wSwP|TsC^Awpet!x|(DyVEr py9g]9Vs='7X(a1vyِ4yp=@!KˀPe?S .tGw<Bi(@Hʺ>i4OznVʹEEw!P*WPqQu ʧ[{o)@wר]'$8$,"h4,D0_-}U]O gYTA74LzD6TE;5*.C'jҽSb#}s<`XyJj'E .oHnnެ9bU %Q$y\V;ee{)6JH)~HqșV;y91U*V5OZ];cφ܆JBޣ5k7^tqaC_v_|2%33 ؂ŭ} ͹ږ!:&0("QtyCvQ>$h f]ry\_KIKAg$B_w:o;8?H#hBtww8\ﯯ q#z=zh|9 'O<=cƜ'ov|>PC|yt[R5Hbbƌx붜K8Aat) dvw+[~s}v;M?\K-ŲSw-I+( I"r0efGzTB< *p}^7Ns7rݺWWUm';;{~?ީS!5&\rfVvF >ݵCtO_7n?bc6oZ< ӓ\yx%K~i_FFC9N]CbEOQ2Ta+Ńh+WFFF3]6ߛ1#aק_A-Z4cG 5waFFfq뒖~mAFl&Ƭ_Bn羰 gΙ.|]#G%< I5Q䡠 Sl\oU#43#ŃQIΤfPNhb WcɎt(.@1j$Ƕy_ Zk.{wժY#qպeKݼ#,<:f;gvls0ׯ^/oԨ}=W._'nn?q]JOTHl}nlSf51r39*xFQJ)o0֪0+#⹨Z;v:!!aF Az4ջ{2T ($Z pe7GwH*w?)S[7)ʏ=.AAAbK3>H߭=SgH?1wg&\i%TI{Qێkrۧ_ʇPüh_廭]!CNW#eu?:rh/P Wv]ߠ~++.? E j_u2ūJV~՘.b&p`v?Xav YBPPHL,T"a.7K@@Ce|kY 2>EgoKCe%\2/m@qj9Wb> UgEY8W9Y3x7ioe ^3ܼuh4jղu{^|\2KJ !P 77?5-=qYқoڹ ~6A9@8)$+4ȱ.^~Al.,^佪^^+ I궔\6r߸nPZ?x^}ef][ʿ^?Att_fs. x93|Pl0`G<1: UMzr`wQo2{xy;{ؼ_LX&JxAzo1CgggϞ rs,Z!+цu+`35lM'[s69& tS5m'jӺIcjԨ^{ڴIqcGhW -M%,d?1rP7+("S>2 GG0eV'V =?Z?VkUk2 ǟ7kR.^ѳWiS'׭?:y<,YpիW1sC2$؜ąΜ>96&*M|uEKV֪UsǶu7n]dF'}9/a捫˪~fSPϜ+W_p9:nS s90bKX;}';1#;4nBAZy/㿃 Cqy^ lwnS>Ƕ:M Ea߯V.nԨjmcW'S={ts"#֯hP/ z֗~s璗-[ 񝚷haŊ^Ϫ] [ Fy8!cg5Ppw}@'3f|8q׎m`s1㿗Vĉ3fͿy+#,,Iq1ʜڵkUΉM?p-;g( l۽]e3ly_~[i|nPEǩ,Q]|Z}njsukU7:}v5]{7lfZs]9'^p]6 z?RdǷ߼壔M[>7RVZ3f^xfQ:e?U-sAC6os'-=jxnrxotÆ CBM'Ξ\ȑ cٴ)2i_x{[2hKs96.͡jUɼUFNoP@2uh0e Tܥ`Rϟh԰!K$@)P]칹7ozroWZ=ܨa7]|mV XNdVFJjڵk=|[ߣ|p?^zǎ x|+aR.o|\QK Չ.ҡ_-4[Ѐ@ *P-`D}6 *0C=j(W$v`^@?n$/+_^c rOSvUCH(+?<7MIENDB`rkward-0.6.4/rkward/0000775000175000017500000000000012633754452013672 5ustar thomasthomasrkward-0.6.4/rkward/Info.plist.in0000644000175000017500000000250112471622105016230 0ustar thomasthomas LSUIElement 1 CFBundleDevelopmentRegion English CFBundleExecutable rkward CFBundleGetInfoString KDE frontend to the R statistics language CFBundleIconFile rkward.icns CFBundleIdentifier CFBundleInfoDictionaryVersion 6.0 CFBundleLongVersionString @RKVERSION_NUMBER@ CFBundleName RKWard CFBundlePackageType APPL CFBundleShortVersionString @RKVERSION_NUMBER@ CFBundleSignature ???? CFBundleVersion CSResourcesFileMapped LSRequiresCarbon NSHumanReadableCopyright rkward-0.6.4/rkward/agents/0000755000175000017500000000000012633754363015152 5ustar thomasthomasrkward-0.6.4/rkward/agents/rkdebughandler.h0000664000175000017500000000461512633754363020314 0ustar thomasthomas/*************************************************************************** rkdebughandler - description ------------------- begin : Wed Oct 19 2011 copyright : (C) 2011 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKDEBUGHANDLER_H #define RKDEBUGHANDLER_H #include #include "../rbackend/rcommand.h" class RBackendRequest; /** A central handler, responsible for keeping all debug related widgets up-to-date */ class RKDebugHandler : public QObject { Q_OBJECT public: explicit RKDebugHandler (QObject *parent); ~RKDebugHandler (); static RKDebugHandler *instance () { return _instance; }; void debugCall (RBackendRequest *request, RCommand *command); void submitDebugString (const QString &command); void sendCancel (); void endDebug (); enum DebugState { NotInDebugger, InDebugPrompt, InDebugRun }; DebugState state () const { return _state; }; QString outputContext () const { return _output_context; }; QStringList calls () const { return _calls; }; QStringList functions () const { return _functions; }; QStringList environments () const { return _environments; }; QStringList locals () const { return _locals; }; QList relativeSourceLines () const { return _rel_src_lines; }; QString debugPrompt () const { return _prompt; }; RCommand *command () const { return _command; }; signals: void newDebugState (); private: RCommand *_command; QStringList _calls, _functions, _environments, _locals; QList _rel_src_lines; QString _prompt, _output_context; DebugState _state; RBackendRequest *_request; static RKDebugHandler *_instance; }; #endif rkward-0.6.4/rkward/agents/rkeditobjectagent.h0000664000175000017500000000331712633754363021021 0ustar thomasthomas/*************************************************************************** rkeditobjectagent - description ------------------- begin : Fri Feb 16 2007 copyright : (C) 2007 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKEDITOBJECTAGENT_H #define RKEDITOBJECTAGENT_H #include #include "../rbackend/rcommandreceiver.h" #include #include /** This agent gets called, when an rk.edit() command was run in the backend. The purpose is to first update the structure information for the object(s), and then try to open it/them. @author Thomas Friedrichsmeier */ class RKEditObjectAgent : public QObject, public RCommandReceiver { Q_OBJECT public: RKEditObjectAgent (const QStringList &object_names, RCommandChain *chain); ~RKEditObjectAgent (); protected: void rCommandDone (RCommand *command); private: QStringList object_names; int done_command_id; }; #endif rkward-0.6.4/rkward/agents/rkquitagent.cpp0000664000175000017500000000537512633754363020230 0ustar thomasthomas/*************************************************************************** rkquitagent - description ------------------- begin : Thu Jan 18 2007 copyright : (C) 2007 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkquitagent.h" #include #include #include "../rkglobals.h" #include "../rbackend/rinterface.h" #include "../rkward.h" #include "../misc/rkprogresscontrol.h" #include "../debug.h" //static bool RKQuitAgent::quitting = false; RKQuitAgent::RKQuitAgent (QObject *parent) : QObject (parent) { RK_TRACE (APP); quitting = true; RCommand *command = new RCommand (QString (), RCommand::EmptyCommand | RCommand::QuitCommand, QString (), this); RKWardMainWindow::getMain ()->hide (); cancel_dialog = new RKProgressControl (this, i18n ("Waiting for remaining R commands to finish. To quit immediately, press Cancel (WARNING: This may result in loss of data)"), i18n ("Waiting for R to finish"), RKProgressControl::AllowCancel | RKProgressControl::ShowAtOnce); cancel_dialog->addRCommand (command, true); connect (cancel_dialog, SIGNAL (cancelled()), this, SLOT (doQuitNow())); if (RKGlobals::rInterface ()->backendIsDead ()) { // nothing to loose QTimer::singleShot (0, this, SLOT (doQuitNow())); return; } else if (RKGlobals::rInterface ()->backendIsIdle ()) { // there should be no problem while quitting. If there is, show the dialog after 300 msec QTimer::singleShot (300, this, SLOT (showWaitDialog())); } else { showWaitDialog (); } RKGlobals::rInterface ()->issueCommand (command); } RKQuitAgent::~RKQuitAgent () { RK_TRACE (APP); } void RKQuitAgent::showWaitDialog () { RK_TRACE (APP); cancel_dialog->doNonModal (true); } void RKQuitAgent::doQuitNow () { RK_TRACE (APP); RKWardMainWindow::getMain ()->close (); // this will kill the agent as well. } void RKQuitAgent::rCommandDone (RCommand *) { RK_TRACE (APP); QTimer::singleShot (0, this, SLOT (doQuitNow())); } #include "rkquitagent.moc" rkward-0.6.4/rkward/agents/rkdebughandler.cpp0000664000175000017500000000523612633754363020647 0ustar thomasthomas/*************************************************************************** rkdebughandler - description ------------------- begin : Wed Oct 19 2011 copyright : (C) 2011 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkdebughandler.h" #include "../rbackend/rkrbackendprotocol_frontend.h" #include "../debug.h" RKDebugHandler* RKDebugHandler::_instance = 0; RKDebugHandler::RKDebugHandler (QObject *parent) : QObject (parent) { RK_TRACE (APP); _state = NotInDebugger; _request = 0; _command = 0; _instance = this; } RKDebugHandler::~RKDebugHandler () { RK_TRACE (APP); } void RKDebugHandler::debugCall (RBackendRequest *request, RCommand *command) { RK_TRACE (APP); _command = command; _request = request; if (command) _output_context = command->fullOutput (); else _output_context.clear (); _calls = request->params["calls"].toStringList (); _functions = request->params["funs"].toStringList (); _environments = request->params["envs"].toStringList (); _locals = request->params["locals"].toStringList (); _prompt = request->params["prompt"].toString (); QStringList dummy = request->params["relsrclines"].toStringList (); _rel_src_lines.clear (); for (int i = 0; i < dummy.size (); ++i) _rel_src_lines.append (dummy.at (i).toInt ()); _state = InDebugPrompt; newDebugState (); } void RKDebugHandler::sendCancel () { RK_TRACE (APP); RK_ASSERT (_request); submitDebugString ("Q\n"); } void RKDebugHandler::submitDebugString (const QString &command) { RK_TRACE (APP); if (!_request) { RK_ASSERT (false); return; } _request->params["result"] = command; RKRBackendProtocolFrontend::setRequestCompleted (_request); _command = 0; _state = InDebugRun; newDebugState (); } void RKDebugHandler::endDebug () { RK_TRACE (APP); _command = 0; _request = 0; _state = NotInDebugger; newDebugState (); } #include "rkdebughandler.moc" rkward-0.6.4/rkward/agents/rkprintagent.cpp0000664000175000017500000000703512633754363020375 0ustar thomasthomas/*************************************************************************** rkprintagent - description ------------------- begin : Mon Aug 01 2011 copyright : (C) 2011 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkprintagent.h" #include #include #include #include #include #include #include #include "../rkward.h" #include "../debug.h" RKPrintAgent::RKPrintAgent () : QObject () { RK_TRACE (APP) } RKPrintAgent::~RKPrintAgent () { RK_TRACE (APP) if (delete_file) QFile (file).remove (); if (provider) provider->deleteLater (); } void RKPrintAgent::printPostscript (const QString &file, bool delete_file) { RK_TRACE (APP) KService::Ptr service = KService::serviceByDesktopPath ("okular_part.desktop"); if (!service) service = KService::serviceByDesktopPath ("kpdf_part.desktop"); KParts::ReadOnlyPart *provider = 0; if (service) provider = service->createInstance (0); else RK_DEBUG (APP, DL_WARNING, "No KDE service found for postscript printing"); QAction *printaction = 0; if (provider) { printaction = provider->action ("print"); if (!printaction) printaction = provider->action ("file_print"); if (!printaction) { QAction *a = new QAction (provider); bool ok = connect (a, SIGNAL (triggered()), provider, SLOT (slotPrint())); if (ok) printaction = a; } if (!(printaction && provider->openUrl (KUrl::fromLocalFile (file)))) { RK_DEBUG (APP, DL_WARNING, "No print action in postscript provider"); delete provider; provider = 0; } } if (!provider) { RK_DEBUG (APP, DL_WARNING, "No valid postscript postscript provider was found"); KMessageBox::sorry (RKWardMainWindow::getMain (), i18n ("No service was found to provide a KDE print dialog for PostScript files. We will try to open a generic PostScript viewer (if any), instead.

Consider installing 'okular', or configure RKWard not to attempt to print using a KDE print dialog."), i18n ("Unable to open KDE print dialog")); // fallback: If we can't find a proper part, try to invoke a standalone PS reader, instead KRun::runUrl (KUrl::fromLocalFile (file), "appication/postscript", RKWardMainWindow::getMain ()); return; } RKPrintAgent *agent = new RKPrintAgent (); agent->file = file; agent->delete_file = delete_file; agent->provider = provider; // very hacky heuristic to try to find out, whether the print action is synchronous or asnchronous. If the latter, delete after half an hour. If the former delete after printing. QTime ts; ts.start (); printaction->trigger (); if (ts.elapsed () < 5000) { QTimer::singleShot (1800000, agent, SLOT (deleteLater())); } else { agent->deleteLater (); } } #include "rkprintagent.moc" rkward-0.6.4/rkward/agents/rksaveagent.h0000664000175000017500000000441512633754363017643 0ustar thomasthomas/*************************************************************************** rksaveagent - description ------------------- begin : Sun Aug 29 2004 copyright : (C) 2004, 2009, 2011 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKSAVEAGENT_H #define RKSAVEAGENT_H #include "../rbackend/rcommandreceiver.h" #include #include class RCommandChain; /** This class basically provides a mechanism to let the user save a workspace, find out whether saving was successful and - if it was not - to ask for a new filename or the like. @author Thomas Friedrichsmeier */ class RKSaveAgent : public RCommandReceiver, public QObject { public: enum DoneAction { DoNothing=0, Load=1 }; /** creates a new RKSaveAgent. If when_done == Quit, the RKSaveAgent will quit the application as soon as saving was successful (or it asked to by the user). Similarly, if when_done==Load, it will load a new workspace after saving (specify the url in load_url). If url is given (not empty), and not save_file_as, the agent will try to save to the given url, else it will ask the user to specify a url. RKSaveAgent will self destruct when done. */ explicit RKSaveAgent (KUrl url, bool save_file_as=false, DoneAction when_done=DoNothing, KUrl load_url=KUrl()); ~RKSaveAgent (); protected: void rCommandDone (RCommand *command); private: bool askURL (); void done (); RCommandChain *save_chain; KUrl save_url; KUrl load_url; KUrl previous_url; DoneAction when_done; }; #endif rkward-0.6.4/rkward/agents/rksaveagent.cpp0000664000175000017500000000761112633754363020177 0ustar thomasthomas/*************************************************************************** rksaveagent - description ------------------- begin : Sun Aug 29 2004 copyright : (C) 2004, 2009, 2010, 2011, 2012 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rksaveagent.h" #include #include #include #include #include "../rbackend/rinterface.h" #include "../core/robjectlist.h" #include "../rkglobals.h" #include "../rkward.h" #include "../settings/rksettingsmodulegeneral.h" #include "../windows/rkworkplace.h" #include "../debug.h" RKSaveAgent::RKSaveAgent (KUrl url, bool save_file_as, DoneAction when_done, KUrl load_url) : QObject () { RK_TRACE (APP); save_url = url; RKSaveAgent::when_done = when_done; RKSaveAgent::load_url = load_url; previous_url = RKWorkplace::mainWorkplace ()->workspaceURL (); save_chain = 0; if (save_url.isEmpty () || save_file_as) { if (!askURL ()) { deleteLater(); return; } } RKWorkplace::mainWorkplace ()->flushAllData (); save_chain = RKGlobals::rInterface ()->startChain (0); RKWorkplace::mainWorkplace ()->setWorkspaceURL (save_url, true); RKWorkplace::mainWorkplace ()->saveWorkplace (save_chain); RKGlobals::rInterface ()->issueCommand (new RCommand ("save.image (\"" + save_url.toLocalFile () + "\")", RCommand::App, QString (), this), save_chain); } RKSaveAgent::~RKSaveAgent () { RK_TRACE (APP); } bool RKSaveAgent::askURL () { RK_TRACE (APP); save_url = KFileDialog::getSaveFileName (save_url, i18n ("%1|R Workspace Files (%1)\n*|All files", RKSettingsModuleGeneral::workspaceFilenameFilter ())); if (save_url.isEmpty ()) { if (when_done != DoNothing) { if (KMessageBox::warningYesNo (0, i18n ("No filename given. Your data was NOT saved. Do you still want to proceed?")) != KMessageBox::Yes) when_done = DoNothing; } return false; } return true; } void RKSaveAgent::rCommandDone (RCommand *command) { RK_TRACE (APP); if (command->hasError ()) { RKWorkplace::mainWorkplace ()->setWorkspaceURL (previous_url); int res; if (when_done != DoNothing) { res = KMessageBox::warningYesNoCancel (0, i18n ("Saving to file '%1' failed. What do you want to do?", save_url.path ()), i18n ("Save failed"), KGuiItem (i18n ("Try saving with a different filename")), KGuiItem (i18n ("Saving failed"))); } else { res = KMessageBox::warningYesNo (0, i18n ("Saving to file '%1' failed. Do you want to try saving to a different filename?", save_url.path ())); } if (res == KMessageBox::Yes) { if (askURL ()) { RKGlobals::rInterface ()->issueCommand (new RCommand ("save.image (\"" + save_url.toLocalFile () + "\")", RCommand::App, QString (), this), save_chain); return; } } else if (res == KMessageBox::No) { done (); return; } // else when_done = DoNothing; } done (); } void RKSaveAgent::done () { RK_TRACE (APP); RKWardMainWindow::getMain ()->setWorkspaceMightBeModified (false); if (save_chain) { RKGlobals::rInterface ()->closeChain (save_chain); } if (when_done == Load) { RKWardMainWindow::getMain ()->askOpenWorkspace (load_url); } deleteLater (); } rkward-0.6.4/rkward/agents/showedittextfileagent.cpp0000664000175000017500000001065412633754363022300 0ustar thomasthomas/*************************************************************************** showedittextfileagent - description ------------------- begin : Tue Sep 13 2005 copyright : (C) 2005, 2009, 2010, 2011 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "showedittextfileagent.h" #include #include #include #include #include #include #include #include "../windows/rkcommandeditorwindow.h" #include "../rbackend/rinterface.h" #include "../rbackend/rkrbackendprotocol_frontend.h" #include "../windows/rkworkplace.h" #include "../rkglobals.h" #include "../rkward.h" #include "../debug.h" ShowEditTextFileAgent::ShowEditTextFileAgent (RBackendRequest *request, const QString &text, const QString &caption) : QObject (RKWardMainWindow::getMain ()) { RK_TRACE (APP); ShowEditTextFileAgent::request = request; dialog = new KDialog (0); // dialog setup dialog->setCaption (caption); dialog->setButtons (KDialog::Ok); dialog->setModal (false); QWidget *page = new QWidget (dialog); dialog->setMainWidget (page); QVBoxLayout *layout = new QVBoxLayout (page); layout->setContentsMargins (0, 0, 0, 0); QLabel *label = new QLabel (text, page); label->setWordWrap (true); layout->addWidget (label); dialog->setButtonText (KDialog::Ok, i18n ("Done")); connect (dialog, SIGNAL (finished()), this, SLOT (deleteLater())); // do it dialog->show (); } ShowEditTextFileAgent::~ShowEditTextFileAgent () { RK_TRACE (APP); RKRBackendProtocolFrontend::setRequestCompleted (request); dialog->deleteLater (); } // static void ShowEditTextFileAgent::showEditFiles (RBackendRequest *request) { RK_TRACE (APP); if (!request) return; QStringList files = request->params["files"].toStringList (); QStringList titles = request->params["titles"].toStringList (); QString wtitle = request->params["wtitle"].toString (); bool prompt = request->params["prompt"].toBool (); int count = files.count (); RK_ASSERT (titles.count () == count); QStringList display_titles; for (int n = 0; n < count; ++n) { QString title; if (!titles[n].isEmpty ()) title = titles[n]; else if (count > 1) title = files[n]; if (!wtitle.isEmpty ()) { if (!title.isEmpty ()) title.prepend (": "); title.prepend (wtitle); } display_titles.append (title); } bool r_highlighting = false; bool read_only = true; bool delete_files = false; if (request->type == RBackendRequest::ShowFiles) { RK_ASSERT (!request->synchronous); if (prompt) KMessageBox::informationList (RKWardMainWindow::getMain (), i18n ("A command running in the R-engine wants you to see the following file(s):\n"), display_titles, i18n ("Showing file(s)"), "show_files"); delete_files = request->params["delete"].toBool (); RKRBackendProtocolFrontend::setRequestCompleted (request); } else if (request->type == RBackendRequest::EditFiles) { if (prompt) { new ShowEditTextFileAgent (request, i18n ("A command running in the R-engine wants you to edit one or more file(s). Please look at these files, edit them as appropriate, and save them. When done, press the \"Done\"-button, or close this dialog to resume.\n\n") + display_titles.join ("\n"), i18n ("Edit file(s)")); } else { RKRBackendProtocolFrontend::setRequestCompleted (request); } r_highlighting = true; read_only = false; } else { RK_ASSERT (false); } // do this last, as it may produce error messages, if some of the files could not be opened. for (int n = 0; n < count; ++n) { RKWorkplace::mainWorkplace ()->openScriptEditor (KUrl::fromLocalFile (files[n]), QString (), r_highlighting, read_only, display_titles[n], delete_files); } } rkward-0.6.4/rkward/agents/showedittextfileagent.h0000664000175000017500000000642112633754363021742 0ustar thomasthomas/*************************************************************************** showedittextfileagent - description ------------------- begin : Tue Sep 13 2005 copyright : (C) 2005, 2010 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef SHOWEDITTEXTFILEAGENT_H #define SHOWEDITTEXTFILEAGENT_H #include class RBackendRequest; class KDialog; /** The purpose of this agent is to display text files for showing and/or editing on request of the R backend. Technically speaking, it gets invoked, whenever the standard R callbacks (ptr_)R_ShowFiles, (ptr_)R_EditFiles, or (ptr_)R_EditFile are called. While the task of simply opening such files for display/editing is rather simple, there is one slightly more difficult issue involved (and hence this class to handle it): In standard R, all these calls are blocking further processing, until the user has closed the shown/edited files. In some cases this may be necessary (for instance if R wants to use with the edited files immediately), in some cases this is an unnecessary nuisance (such as when R simply wants to display a help page or some other purely informational text). The solution to this, used in this agent, is to display a non-modal dialog with a "Done"-button, and thereby leave the decision to the user. Whenever the user thinks, it's safe to resume processing, the "Done"-button can be pressed, even if the files are still open. Until that time, processing is halted. You probably don't want to create an instance of this agent directly. Rather use the static showEditFiles (). @author Thomas Friedrichsmeier */ class ShowEditTextFileAgent : public QObject { public: /** constructor. Do not use directly. Use the static showEditFiles () instead. @param args The corresponding RCallbackArgs-record @param text Text to display in the dialog. */ ShowEditTextFileAgent (RBackendRequest *request, const QString &text, const QString &caption); /** destructor. Called when the user presses the done button (or closes the notification dialog). Resumes processing in the backend, deletes the agent */ ~ShowEditTextFileAgent (); /** This gets called by RInterface, in order to show/edit the files in question. The RCallbackArgs-struct is passed in raw form, and only this function sorts out, what exactly needs to be done. Note that this is a static member. It will take care of creating/deleting an agent on its own. */ static void showEditFiles (RBackendRequest *request); private: RBackendRequest *request; KDialog *dialog; }; #endif rkward-0.6.4/rkward/agents/rkquitagent.h0000664000175000017500000000372212633754363017667 0ustar thomasthomas/*************************************************************************** rkquitagent - description ------------------- begin : Thu Jan 18 2007 copyright : (C) 2007 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKQUITAGENT_H #define RKQUITAGENT_H #include #include "../rbackend/rcommandreceiver.h" class RKProgressControl; /** The purpose of RKQuitAgent is to delay the actual destruction of the app until all commands have finished in the backend. The quit agent can NOT handle queries for saving some more data, or similar things. Do not call before you really want to quit the application. @author Thomas Friedrichsmeier */ class RKQuitAgent : public QObject, public RCommandReceiver { Q_OBJECT public: /** Constructor. As soon as you contruct an object of this type, the RKWard application *will* quit (but maybe with a short delay)! */ explicit RKQuitAgent (QObject *parent); ~RKQuitAgent (); static bool quittingInProgress () { return quitting; }; public slots: void doQuitNow (); void showWaitDialog (); protected: void rCommandDone (RCommand *command); private: RKProgressControl *cancel_dialog; static bool quitting; }; #endif rkward-0.6.4/rkward/agents/rkeditobjectagent.cpp0000664000175000017500000000525512633754363021357 0ustar thomasthomas/*************************************************************************** rkeditobjectagent - description ------------------- begin : Fri Feb 16 2007 copyright : (C) 2007 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkeditobjectagent.h" #include #include #include "../rkglobals.h" #include "../core/robjectlist.h" #include "../rbackend/rinterface.h" #include "../rkward.h" #include "../windows/rkworkplace.h" #include "../debug.h" RKEditObjectAgent::RKEditObjectAgent (const QStringList &object_names, RCommandChain *chain) { RK_TRACE (APP); RKEditObjectAgent::object_names = object_names; // first issue an empty command to trigger an update of the object list RKGlobals::rInterface ()->issueCommand (new RCommand (QString (), RCommand::EmptyCommand | RCommand::ObjectListUpdate, QString (), this), chain); // now add another empty command to find out, when the update is complete RCommand *command = new RCommand (QString (), RCommand::EmptyCommand, QString (), this); done_command_id = command->id (); RKGlobals::rInterface ()->issueCommand (command, chain); } RKEditObjectAgent::~RKEditObjectAgent () { RK_TRACE (APP); } void RKEditObjectAgent::rCommandDone (RCommand *command) { RK_TRACE (APP); if (command->id () == done_command_id) { for (QStringList::const_iterator it = object_names.constBegin (); it != object_names.constEnd (); ++it) { QString object_name = *it; RObject *obj = RObjectList::getObjectList ()->findObject (object_name); if (!(obj && RKWorkplace::mainWorkplace()->editObject (obj))) { KMessageBox::information (0, i18n ("The object '%1', could not be opened for editing. Either it does not exist, or RKWard does not support editing this type of object, yet.", object_name), i18n ("Cannot edit '%1'", object_name)); } } // we're done deleteLater (); } } #include "rkeditobjectagent.moc" rkward-0.6.4/rkward/agents/CMakeLists.txt0000644000175000017500000000064312455741220017703 0ustar thomasthomasINCLUDE_DIRECTORIES( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${KDE4_INCLUDE_DIR} ${QT_INCLUDES} ) ########### next target ############### SET(agents_STAT_SRCS rkdebughandler.cpp rkeditobjectagent.cpp rkloadagent.cpp rkprintagent.cpp rkquitagent.cpp rksaveagent.cpp showedittextfileagent.cpp ) QT4_AUTOMOC(${agents_STAT_SRCS}) ADD_LIBRARY(agents STATIC ${agents_STAT_SRCS}) rkward-0.6.4/rkward/agents/rkprintagent.h0000664000175000017500000000324012633754363020034 0ustar thomasthomas/*************************************************************************** rkprintagent - description ------------------- begin : Mon Aug 01 2011 copyright : (C) 2011 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKPRINTAGENT_H #define RKPRINTAGENT_H #include #include /** The main purpose of this class is to cope with the lack of kprinter in KDE 4. Tries * to offer a KDE print dialog for an existing postscript file. */ class RKPrintAgent : public QObject { Q_OBJECT public: /** print the given postscript file. * @param delete_file : Try to delete the file after printing. Note: This is not guaranteed to work. */ static void printPostscript (const QString &file, bool delete_file=false); protected: RKPrintAgent (); ~RKPrintAgent (); QString file; KParts::ReadOnlyPart *provider; bool delete_file; }; #endif rkward-0.6.4/rkward/agents/rkloadagent.h0000664000175000017500000000331112633754363017616 0ustar thomasthomas/*************************************************************************** rkloadagent - description ------------------- begin : Sun Sep 5 2004 copyright : (C) 2004 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKLOADAGENT_H #define RKLOADAGENT_H #include #include "../rbackend/rcommandreceiver.h" #include #include /** The RKLoadAgent is really a rather simple agent. All it needs to do is display an error message, if loading fails. No further action is required. Like all agents, the RKLoadAgent self-destructs when done. @author Thomas Friedrichsmeier */ class RKLoadAgent : public QObject, public RCommandReceiver { Q_OBJECT public: explicit RKLoadAgent (const KUrl &url, bool merge=false); ~RKLoadAgent (); protected: void rCommandDone (RCommand *command); private: /// needed if file to be loaded is remote QString tmpfile; bool _merge; }; #endif rkward-0.6.4/rkward/agents/rkloadagent.cpp0000664000175000017500000000736012633754363020161 0ustar thomasthomas/*************************************************************************** rkloadagent - description ------------------- begin : Sun Sep 5 2004 copyright : (C) 2004, 2007, 2009, 2011, 2012, 2014 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkloadagent.h" #include #include #include #include #include #include "../rkglobals.h" #include "../core/robjectlist.h" #include "../rbackend/rinterface.h" #include "../rkward.h" #include "../windows/rkworkplace.h" #include "../settings/rksettingsmodulegeneral.h" #include "../debug.h" #define WORKSPACE_LOAD_COMMAND 1 #define WORKSPACE_LOAD_COMPLETE_COMMAND 2 RKLoadAgent::RKLoadAgent (const KUrl &url, bool merge) { RK_TRACE (APP); RKWardMainWindow::getMain ()->slotSetStatusBarText (i18n ("Loading Workspace...")); _merge = merge; QString filename; if (!url.isLocalFile ()) { KIO::NetAccess::download (url, tmpfile, RKWardMainWindow::getMain ()); filename = tmpfile; } else { filename = url.toLocalFile (); } RCommand *command; if (!merge) { RKWardMainWindow::getMain ()->slotCloseAllWindows (); command = new RCommand ("remove (list=ls (all.names=TRUE))", RCommand::App | RCommand::ObjectListUpdate); RKGlobals::rInterface ()->issueCommand (command); } command = new RCommand ("load (\"" + filename + "\")", RCommand::App | RCommand::ObjectListUpdate, QString (), this, WORKSPACE_LOAD_COMMAND); RKGlobals::rInterface ()->issueCommand (command); RKWorkplace::mainWorkplace ()->setWorkspaceURL (url); } RKLoadAgent::~RKLoadAgent () { RK_TRACE (APP); } void RKLoadAgent::rCommandDone (RCommand *command) { RK_TRACE (APP); if (command->getFlags () == WORKSPACE_LOAD_COMMAND) { if (!tmpfile.isEmpty ()) KIO::NetAccess::removeTempFile (tmpfile); if (command->failed ()) { KMessageBox::error (0, i18n ("There has been an error opening file '%1':\n%2", RKWorkplace::mainWorkplace ()->workspaceURL ().path (), command->error ()), i18n ("Error loading workspace")); RKWorkplace::mainWorkplace ()->setWorkspaceURL (KUrl()); } else { RKWorkplace::mainWorkplace ()->restoreWorkplace (0, _merge); if (RKSettingsModuleGeneral::cdToWorkspaceOnLoad ()) { if (RKWorkplace::mainWorkplace ()->workspaceURL ().isLocalFile ()) { RKGlobals::rInterface ()->issueCommand ("setwd (" + RObject::rQuote (RKWorkplace::mainWorkplace ()->workspaceURL ().directory ()) + ')', RCommand::App); } } RKGlobals::rInterface ()->issueCommand (QString (), RCommand::EmptyCommand | RCommand::App, QString (), this, WORKSPACE_LOAD_COMPLETE_COMMAND); } RKWardMainWindow::getMain ()->setCaption (QString ()); // trigger update of caption } else if (command->getFlags () == WORKSPACE_LOAD_COMPLETE_COMMAND) { RKWardMainWindow::getMain ()->slotSetStatusReady (); RKWardMainWindow::getMain ()->setWorkspaceMightBeModified (false); delete this; return; } else { RK_ASSERT (false); } } #include "rkloadagent.moc" rkward-0.6.4/rkward/rbackend/0000755000175000017500000000000012633754364015443 5ustar thomasthomasrkward-0.6.4/rkward/rbackend/rcommandstack.cpp0000664000175000017500000002612612633754364021006 0ustar thomasthomas/*************************************************************************** rcommandstack - description ------------------- begin : Mon Sep 6 2004 copyright : (C) 2004-2013 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rcommandstack.h" #include #include #include #include "rinterface.h" #include "../debug.h" //static RCommandStack *RCommandStack::regular_stack; RCommandStack::RCommandStack () : RCommandChain () { RK_TRACE (RBACKEND); closed = false; } RCommandStack::~RCommandStack () { RK_TRACE (RBACKEND); } void RCommandStack::issueCommand (RCommand *command, RCommandChain *chain) { RK_TRACE (RBACKEND); if (!chain) chain = regular_stack; issueCommandInternal (command, chain); } void RCommandStack::issueCommandInternal (RCommandChain* child, RCommandChain* parent) { RK_TRACE (RBACKEND); RK_ASSERT (parent); int pos = parent->sub_commands.size (); if (child->is_command && (child->toCommand ()->type () & RCommand::PriorityCommand)) { // Add priority commands before any regular command, after other priority commands. Always in main chain. RK_ASSERT (parent == regular_stack); for (pos = 0; pos < parent->sub_commands.size (); ++pos) { RCommand *com = parent->sub_commands[pos]->toCommand (); if (!(com && (com->type () & RCommand::PriorityCommand))) break; } } RCommandStackModel::getModel ()->aboutToAdd (parent, pos); parent->sub_commands.insert (pos, child); child->parent = parent; RCommandStackModel::getModel ()->addComplete (); } bool RCommandStack::removeFromParent (RCommandChain* child) { RK_TRACE (RBACKEND); RCommandChain* parent = child->parent; RK_ASSERT (parent); int index = parent->sub_commands.indexOf (child); if (index < 0) return false; RCommandStackModel::getModel ()->aboutToPop (parent, index); parent->sub_commands.removeAt (index); if (!child->is_command) delete child; // else child->parent = 0; // There is at least on point in the code, where we access the parent (chain) after the command was popped. This is rather unsafe in the first place, but... RCommandStackModel::getModel ()->popComplete (); return true; } RCommandChain *RCommandStack::startChain (RCommandChain *parent) { RK_TRACE (RBACKEND); if (!parent) parent = regular_stack; RCommandChain *chain = new RCommandChain (); issueCommandInternal (chain, parent); return chain; } void RCommandStack::closeChain (RCommandChain *chain) { RK_TRACE (RBACKEND); if (!chain) return; chain->closed = true; RCommandStackModel::getModel ()->itemChange (chain); popIfCompleted (chain); } RCommand* RCommandStack::currentCommand () { RK_TRACE (RBACKEND); RCommandChain *dummy; do { // first pop any empty things in the way dummy = activeSubItemOf (regular_stack); } while (popIfCompleted (dummy)); RCommandChain *ret = activeSubItemOf (regular_stack); if (ret) return ret->toCommand (); // might still be 0, if it is not a command return 0; } RCommandChain* RCommandStack::activeSubItemOf (RCommandChain* item) { RK_TRACE (RBACKEND); if (item->sub_commands.isEmpty () && item->is_command && item->isClosed ()) return item; if (item->sub_commands.isEmpty ()) return item; return activeSubItemOf (item->sub_commands.first ()); } void RCommandStack::listCommandsRecursive (QList *list, const RCommandChain *chain) { RK_TRACE (RBACKEND); if (chain->is_command) list->append (const_cast(chain)->toCommand ()); foreach (const RCommandChain* coc, chain->sub_commands) { listCommandsRecursive (list, coc); } } QList RCommandStack::allCommands () { RK_TRACE (RBACKEND); QList ret; listCommandsRecursive (&ret, regular_stack); return ret; } void RCommandStack::pop (RCommandChain *item) { RK_TRACE (RBACKEND); RCommandChain *parent = item->parent; removeFromParent (item); popIfCompleted (parent); } bool RCommandStack::popIfCompleted (RCommandChain* item) { RK_TRACE (RBACKEND); if (item->isClosed () && item->sub_commands.isEmpty () && item->parent && (!item->is_command)) { // if the item has no parent, it is the main stack. If it is a command, it will be popped from the RInterface. pop (item); return true; } return false; } /////////////////////// RCommandStackModel //////////////////// #define MAIN_COL 0 #define STATUS_COL 1 #define FLAGS_COL 2 #define DESC_COL 3 #define NUM_COLS 4 // static RCommandStackModel* RCommandStackModel::static_model = 0; RCommandStackModel::RCommandStackModel (QObject *parent) : QAbstractItemModel (parent) { RK_TRACE (RBACKEND); RK_ASSERT (static_model == 0); // only one instance should be created static_model = this; listeners = 0; } RCommandStackModel::~RCommandStackModel () { RK_TRACE (RBACKEND); static_model = 0; RK_ASSERT (!listeners); } void RCommandStackModel::addListener () { RK_TRACE (RBACKEND); ++listeners; } void RCommandStackModel::removeListener () { RK_TRACE (RBACKEND); --listeners; RK_ASSERT (listeners >= 0); } QModelIndex RCommandStackModel::index (int row, int column, const QModelIndex& parent) const { RK_ASSERT (listeners > 0); RK_TRACE (RBACKEND); RCommandChain* index_data = 0; if (!parent.isValid ()) { index_data = RCommandStack::regular_stack; } else { RCommandChain* parent_index = static_cast (parent.internalPointer ()); RK_ASSERT (parent_index); if (parent_index->sub_commands.size () <= row) { RK_ASSERT (false); return QModelIndex (); } index_data = parent_index->sub_commands[row]; } return (createIndex (row, column, index_data)); } QModelIndex RCommandStackModel::parent (const QModelIndex& child) const { RK_ASSERT (listeners); RK_TRACE (RBACKEND); if (child.isValid ()) { RCommandChain* child_index = static_cast (child.internalPointer ()); RK_ASSERT (child_index); RCommandChain* index_data = child_index->parent; if (index_data) return (createIndex (0, 0, index_data)); } return QModelIndex (); } int RCommandStackModel::rowCount (const QModelIndex& parent) const { RK_ASSERT (listeners); RK_TRACE (RBACKEND); if (!parent.isValid ()) return 1; RCommandChain* index_data = static_cast (parent.internalPointer ()); RK_ASSERT (index_data); return (index_data->sub_commands.size ()); } int RCommandStackModel::columnCount (const QModelIndex&) const { RK_ASSERT (listeners); RK_TRACE (RBACKEND); return NUM_COLS; } QVariant RCommandStackModel::data (const QModelIndex& index, int role) const { RK_ASSERT (listeners); RK_TRACE (RBACKEND); if (!index.isValid ()) return QVariant (); RK_ASSERT (index.model () == this); RCommandChain* index_data = static_cast (index.internalPointer ()); if (index_data->is_command) { RCommand *command = index_data->toCommand (); if ((index.column () == MAIN_COL) && (role == Qt::DisplayRole)) return (command->command ()); if ((index.column () == FLAGS_COL) && (role == Qt::DisplayRole)) { QString ret; if (command->type () & RCommand::User) ret += 'U'; if (command->type () & RCommand::Plugin) ret += 'P'; if (command->type () & RCommand::App) ret += 'A'; if (command->type () & RCommand::Sync) ret += 'S'; if (command->type () & RCommand::EmptyCommand) ret += 'E'; if (command->type () & (RCommand::GetIntVector | RCommand::GetRealVector | RCommand::GetStringVector | RCommand::GetStructuredData)) ret += 'D'; if (command->type () & RCommand::CCOutput) ret += 'O'; return (ret); } if ((index.column () == STATUS_COL) && (role == Qt::DisplayRole)) { QString ret; if (command->status & RCommand::Running) ret += i18n ("Running"); if (command->status & RCommand::Canceled) { if (!ret.isEmpty ()) ret += ", "; ret += i18n ("Canceled"); } return (ret); } if ((index.column () == DESC_COL) && (role == Qt::DisplayRole)) { return (command->rkEquivalent ()); } } else { if (index_data->parent) { if ((index.column () == MAIN_COL) && (role == Qt::DisplayRole)) return (i18n ("Command Chain")); if ((index.column () == STATUS_COL) && (role == Qt::DisplayRole)) { if (index_data->closed) return (i18n ("Closed")); return (i18n ("Waiting")); } } else { if ((index.column () == MAIN_COL) && (role == Qt::DisplayRole)) return (i18n ("Command Stack")); } } return (QVariant ()); } Qt::ItemFlags RCommandStackModel::flags (const QModelIndex& index) const { RK_ASSERT (listeners); RK_TRACE (RBACKEND); if (!index.isValid ()) return 0; RK_ASSERT (index.model () == this); RCommandChain* index_data = static_cast (index.internalPointer ()); RK_ASSERT (index_data); if (index_data->is_command) return (Qt::ItemIsSelectable | Qt::ItemIsEnabled); return Qt::ItemIsEnabled; } QVariant RCommandStackModel::headerData (int section, Qt::Orientation orientation, int role) const { RK_ASSERT (listeners); RK_TRACE (RBACKEND); if ((orientation == Qt::Horizontal) && (role == Qt::DisplayRole)) { if (section == MAIN_COL) return (i18n ("Command")); if (section == FLAGS_COL) return (i18n ("Type")); if (section == STATUS_COL) return (i18n ("Status")); if (section == DESC_COL) return (i18n ("Description")); } return QVariant (); } QModelIndex RCommandStackModel::indexFor (RCommandChain *item) { RK_ASSERT (listeners); RK_TRACE (RBACKEND); if (!item) return (QModelIndex ()); if (!item->parent) { // stack is always the first (and only) child return (createIndex (0, 0, item)); } int row = item->parent->sub_commands.indexOf (item); if (row < 0) { RK_ASSERT (false); return (QModelIndex ()); } return (createIndex (row, 0, item)); } void RCommandStackModel::aboutToPop (RCommandChain* parent, int index) { if (!listeners) return; RK_TRACE (RBACKEND); QModelIndex parent_index = indexFor (parent); beginRemoveRows (parent_index, index, index); } void RCommandStackModel::popComplete () { if (!listeners) return; RK_TRACE (RBACKEND); endRemoveRows (); } void RCommandStackModel::aboutToAdd (RCommandChain* parent, int index) { if (!listeners) return; RK_TRACE (RBACKEND); QModelIndex parent_index = indexFor (parent); beginInsertRows (parent_index, index, index); } void RCommandStackModel::addComplete () { if (!listeners) return; RK_TRACE (RBACKEND); endInsertRows (); } void RCommandStackModel::itemChange (RCommandChain* item) { if (!listeners) return; RK_TRACE (RBACKEND); QModelIndex item_index = indexFor (item); emit (dataChanged (item_index, item_index)); } #include "rcommandstack.moc" rkward-0.6.4/rkward/rbackend/rkbackendtransmitter.h0000664000175000017500000000376212633754364022047 0ustar thomasthomas/*************************************************************************** rkbackendtransmitter - description ------------------- begin : Thu Nov 18 2010 copyright : (C) 2010 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKBACKENDTRANSMITTER_H #define RKBACKENDTRANSMITTER_H #include "rktransmitter.h" /** Private class used by the RKRBackendProtocol, in case of the backend running in a split process. This will be used as the secondary thread, and takes care of serializing, sending, receiving, and unserializing requests. */ class RKRBackendTransmitter : public RKAbstractTransmitter { Q_OBJECT public: RKRBackendTransmitter (const QString &servername, const QString &token); ~RKRBackendTransmitter (); void publicmsleep (int delay) { msleep (delay); }; void run (); void writeRequest (RBackendRequest *request); void requestReceived (RBackendRequest *request); void handleTransmissionError (const QString &message); private: void timerEvent (QTimerEvent *event); void flushOutput (bool force); QList current_sync_requests; // pointers to the request that we expect a reply for. Yes, internally, this can be several requests. QString servername; }; #endif rkward-0.6.4/rkward/rbackend/rksignalsupport.h0000664000175000017500000000243112633754364021065 0ustar thomasthomas/*************************************************************************** rksignalsupport - description ------------------- begin : Thu Nov 22 2007 copyright : (C) 2007, 2010 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKSIGNALSUPPORT_H #define RKSIGNALSUPPORT_H namespace RKSignalSupport { void saveDefaultSignalHandlers (); void installSignalProxies (); void installSigIntAndUsrHandlers (void (*handler) (void)); void callOldSigIntHandler (); }; #endif rkward-0.6.4/rkward/rbackend/rklocalesupport.h0000664000175000017500000000236412633754364021054 0ustar thomasthomas/*************************************************************************** rklocalesupport - description ------------------- begin : Sun Mar 11 2007 copyright : (C) 2007 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKLOCALESUPPORT_H #define RKLOCALESUPPORT_H class QTextCodec; /** Helper function to determine the QTextCodec best suited to recode the current CTYPE to UTF-8 */ QTextCodec *RKGetCurrentLocaleCodec (); #endif rkward-0.6.4/rkward/rbackend/rkrbackendprotocol_shared.cpp0000664000175000017500000001074512633754364023376 0ustar thomasthomas/*************************************************************************** rkrbackendprotocol - description ------------------- begin : Thu Nov 04 2010 copyright : (C) 2010, 2011, 2013 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkrbackendprotocol_shared.h" #include "../debug.h" RCommandProxy::RCommandProxy (const QString &command, int type) { RK_TRACE (RBACKEND); RCommandProxy::command = command; RCommandProxy::type = type; id = -1; status = 0; } RCommandProxy::~RCommandProxy () { RK_TRACE (RBACKEND); RK_ASSERT ((type & RCommand::Internal) || (getDataType () == RData::NoData)); } int RBackendRequest::_id = 0; RBackendRequest::RBackendRequest (bool synchronous, RCallbackType type) { RK_TRACE (RBACKEND); RBackendRequest::synchronous = synchronous; RBackendRequest::type = type; id = ++_id; done = false; command = 0; output = 0; } RBackendRequest::~RBackendRequest () { RK_TRACE (RBACKEND); delete command; delete output; }; void RBackendRequest::mergeReply (RBackendRequest *reply) { RK_TRACE (RBACKEND); RK_ASSERT (reply->id == id); command = reply->command; params = reply->params; output = reply->output; reply->command = 0; reply->output = 0; } RBackendRequest* RBackendRequest::duplicate () { RK_TRACE (RBACKEND); RBackendRequest* ret = new RBackendRequest (synchronous, type); --_id; // for pretty, consecutive numbering ret->id = id; ret->done = done; ret->command = command; ret->params = params; ret->output = output; // prevent double deletion issues command = 0; output = 0; return ret; } #define MAX_BUF_LENGTH 16000 #define OUTPUT_STRING_RESERVE 1000 RKROutputBuffer::RKROutputBuffer () { RK_TRACE (RBACKEND); out_buf_len = 0; } RKROutputBuffer::~RKROutputBuffer () { RK_TRACE (RBACKEND); } bool RKROutputBuffer::handleOutput (const QString &output, int buf_length, ROutput::ROutputType output_type, bool allow_blocking) { if (!buf_length) return false; RK_TRACE (RBACKEND); RK_DEBUG (RBACKEND, DL_DEBUG, "Output type %d: %s", output_type, qPrintable (output)); // wait while the output buffer is exceeded to give downstream threads a chance to catch up while ((out_buf_len > MAX_BUF_LENGTH) && allow_blocking) { if (!doMSleep (10)) break; } output_buffer_mutex.lock (); bool previously_empty = (out_buf_len <= 0); ROutput *current_output = 0; if (!output_buffer.isEmpty ()) { // Merge with previous output fragment, if of the same type current_output = output_buffer.last (); if (current_output->type != output_type) current_output = 0; } if (!current_output) { current_output = new ROutput; current_output->type = output_type; current_output->output.reserve (OUTPUT_STRING_RESERVE); output_buffer.append (current_output); } current_output->output.append (output); out_buf_len += buf_length; output_buffer_mutex.unlock (); return previously_empty; } ROutputList RKROutputBuffer::flushOutput (bool forcibly) { ROutputList ret; if (out_buf_len == 0) return ret; // if there is absolutely no output, just skip. RK_TRACE (RBACKEND); if (!forcibly) { if (!output_buffer_mutex.tryLock ()) return ret; } else { output_buffer_mutex.lock (); } RK_ASSERT (!output_buffer.isEmpty ()); // see check for out_buf_len, above ret = output_buffer; output_buffer.clear (); out_buf_len = 0; output_buffer_mutex.unlock (); return ret; } QString RKRSharedFunctionality::quote (const QString &string) { QString ret; int s = string.size (); ret.reserve (s + 2); // typical case: Only quotes added, no escapes needed. ret.append ('\"'); for (int i = 0; i < s; ++i) { const QChar c = string[i]; if ((c == '\\') || (c == '\"')) ret.append ('\\'); ret.append (c); } ret.append ('\"'); return ret; } rkward-0.6.4/rkward/rbackend/rktransmitter.h0000664000175000017500000000615412633754364020535 0ustar thomasthomas/*************************************************************************** rktransmitter - description ------------------- begin : Thu Nov 18 2010 copyright : (C) 2010, 2013 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKTRANSMITTER_H #define RKTRANSMITTER_H #include "rkrbackendprotocol_shared.h" #include "rkasyncdatastreamhelper.h" #include #include /** functions for serialization / unserialization of communication between backend and frontend. NOTE: This could really be a namespace, instead of a class, but "friending" a class is simply easier... */ class RKRBackendSerializer { public: static void serialize (const RBackendRequest &request, QDataStream &buffer); static RBackendRequest *unserialize (QDataStream &buffer); private: static void serializeOutput (const ROutputList &list, QDataStream &stream); static void serializeData (const RData &data, QDataStream &stream); static void serializeProxy (const RCommandProxy &proxy, QDataStream &stream); static ROutputList* unserializeOutput (QDataStream &stream); static RData* unserializeData (QDataStream &stream); static RCommandProxy* unserializeProxy (QDataStream &stream); }; class QLocalSocket; /** The base class for the frontend- and backend transmitters */ class RKAbstractTransmitter : public QThread { Q_OBJECT public: static RKAbstractTransmitter* instance () { return _instance; }; virtual ~RKAbstractTransmitter (); /** returns the magic token negotiated between frontend and backend (for validating incoming connections) */ QString connectionToken () { return token; }; protected: RKAbstractTransmitter (); virtual void writeRequest (RBackendRequest *request) = 0; virtual void requestReceived (RBackendRequest *request) = 0; virtual void handleTransmissionError (const QString &message) = 0; void transmitRequest (RBackendRequest *request); void customEvent (QEvent *e); void setConnection (QLocalSocket *connection); QLocalSocket *connection; QString token; private slots: /** Note: this blocks until a compelete request has been received. Connected to the "readyRead"-signal of the connection. Calls requestReceived() once the request has been read. */ void fetchTransmission (); void disconnected (); private: static RKAbstractTransmitter* _instance; RKAsyncDataStreamHelper streamer; }; #endif rkward-0.6.4/rkward/rbackend/rcommand.cpp0000664000175000017500000001400412633754364017750 0ustar thomasthomas/*************************************************************************** rcommand.cpp - description ------------------- begin : Mon Nov 11 2002 copyright : (C) 2002, 2006, 2007 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rcommand.h" #include "rcommandreceiver.h" #include "rinterface.h" #include "../windows/rkcommandlog.h" #include "rkrbackendprotocol_shared.h" #include "../core/robject.h" #include "../debug.h" #include "../rkglobals.h" RCommand* RCommandChain::toCommand() { return (is_command ? static_cast (this) : 0); } RCommandNotifier::RCommandNotifier () : QObject () { RK_TRACE (RBACKEND); } RCommandNotifier::~RCommandNotifier () { RK_TRACE (RBACKEND); } int RCommand::next_id = 0; RCommand::RCommand(const QString &command, int type, const QString &rk_equiv, RCommandReceiver *receiver, int flags) : RData (), RCommandChain (false) { RK_TRACE (RBACKEND); _id = next_id++; // if we ever submit enough commands to get a buffer overflow, use only positive numbers. if (next_id < 0) { next_id = 0; } _type = type; _flags = flags; if (type & Plugin) _command = command.trimmed (); else _command = command; if (_command.isEmpty ()) _type |= EmptyCommand; status = 0; has_been_run_up_to = 0; _rk_equiv = rk_equiv; _notifier = 0; for (int i = 0; i < MAX_RECEIVERS_PER_RCOMMAND; ++i) receivers[i] = 0; if (!(type & Internal)) { addReceiver (receiver); addReceiver (RKCommandLog::getLog ()); } } RCommand::~RCommand(){ RK_TRACE (RBACKEND); for (QList::const_iterator it = output_list.constBegin (); it != output_list.constEnd (); ++it) { delete (*it); } // The output_list itself is cleared automatically if (_notifier) delete _notifier; } RCommandNotifier* RCommand::notifier () { if (!_notifier) { RK_TRACE (RBACKEND); _notifier = new RCommandNotifier (); RK_ASSERT (_notifier); } return _notifier; } void RCommand::addReceiver (RCommandReceiver *receiver) { RK_TRACE (RBACKEND); if (!receiver) return; for (int i = 0; i < MAX_RECEIVERS_PER_RCOMMAND; ++i) { if (receivers[i] == 0) { receivers[i] = receiver; receiver->addCommand (this); return; } } RK_DEBUG (RBACKEND, DL_ERROR, "Too many receivers for command"); } void RCommand::removeReceiver (RCommandReceiver *receiver) { RK_TRACE (RBACKEND); if (!receiver) return; for (int i = 0; i < MAX_RECEIVERS_PER_RCOMMAND; ++i) { if (receivers[i] == receiver) { receivers[i] = 0; return; } } RK_DEBUG (RBACKEND, DL_WARNING, "Was not a receiver in RCommand::removeReceiver: %p", receiver); } void RCommand::finished () { RK_TRACE (RBACKEND); for (int i=0; i < MAX_RECEIVERS_PER_RCOMMAND; ++i) { if (receivers[i] == 0) continue; receivers[i]->delCommand (this); receivers[i]->rCommandDone (this); } if (_notifier) _notifier->emitFinished (this); } void RCommand::newOutput (ROutput *output) { RK_TRACE (RBACKEND); for (int i=0; i < MAX_RECEIVERS_PER_RCOMMAND; ++i) { if (receivers[i] == 0) continue; receivers[i]->newOutput (this, output); } } void RCommand::commandLineIn () { RK_TRACE (RBACKEND); RK_ASSERT (_type & User); for (int i=0; i < MAX_RECEIVERS_PER_RCOMMAND; ++i) { if (receivers[i] == 0) continue; receivers[i]->userCommandLineIn (this); } } QString RCommand::error () const { RK_TRACE (RBACKEND); QString ret; for (ROutputList::const_iterator it = output_list.begin (); it != output_list.end (); ++it) { if ((*it)->type == ROutput::Error) { ret.append ((*it)->output); } } return ret; } QString RCommand::output () const { RK_TRACE (RBACKEND); QString ret; for (ROutputList::const_iterator it = output_list.begin (); it != output_list.end (); ++it) { if ((*it)->type == ROutput::Output) { ret.append ((*it)->output); } } return ret; } QString RCommand::warnings () const { RK_TRACE (RBACKEND); QString ret; for (ROutputList::const_iterator it = output_list.begin (); it != output_list.end (); ++it) { if ((*it)->type == ROutput::Warning) { ret.append ((*it)->output); } } return ret; } QString RCommand::fullOutput () const { RK_TRACE (RBACKEND); QString ret; for (ROutputList::const_iterator it = output_list.begin (); it != output_list.end (); ++it) { ret.append ((*it)->output); } return ret; } QString RCommand::remainingCommand () const { RK_TRACE (RBACKEND); RK_ASSERT (_type & User); // not a grave problem, if it's not, but not useful, either return _command.mid (has_been_run_up_to); } void RCommand::mergeAndDeleteProxy (RCommandProxy *proxy) { RK_TRACE (RBACKEND); RK_ASSERT (proxy); RK_ASSERT (proxy->id == _id); RK_ASSERT (proxy->type == _type); status = proxy->status; has_been_run_up_to = proxy->has_been_run_up_to; swallowData (*proxy); delete proxy; } RCommandProxy* RCommand::makeProxy () const { RK_TRACE (RBACKEND); RK_ASSERT (status == 0); // Initialization from an already touched command is not a real problem, but certainly no expected usage RK_ASSERT (has_been_run_up_to == 0); RK_ASSERT (getDataType () == RData::NoData); RCommandProxy *ret = new RCommandProxy (_command, _type); ret->id = _id, ret->status = status; ret->has_been_run_up_to = has_been_run_up_to; return ret; } QString RCommand::rQuote (const QString "ed) { return RObject::rQuote (quoted); } #include "rcommand.moc" rkward-0.6.4/rkward/rbackend/rkrbackendprotocol_frontend.h0000664000175000017500000000373012633754364023410 0ustar thomasthomas/*************************************************************************** rkrbackendprotocol - description ------------------- begin : Thu Nov 04 2010 copyright : (C) 2010, 2011, 2013 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKRBACKENDPROTOCOL_FRONTEND_H #define RKRBACKENDPROTOCOL_FRONTEND_H #include "rkrbackendprotocol_shared.h" #include class RInterface; class QThread; class RCommandProxy; class RKRBackendProtocolFrontend : public QObject { public: explicit RKRBackendProtocolFrontend (RInterface* parent); ~RKRBackendProtocolFrontend (); static void setRequestCompleted (RBackendRequest *request); ROutputList flushOutput (bool force); static void interruptCommand (int command_id); static void sendPriorityCommand (RCommandProxy *proxy); void terminateBackend (); void setupBackend (); static RKRBackendProtocolFrontend* instance () { return _instance; }; protected: /** needed to handle the QEvents, the R thread is sending (notifications on what's happening in the backend thread) */ void customEvent (QEvent *e); QThread* main_thread; private: static RKRBackendProtocolFrontend* _instance; RInterface *frontend; }; #endif rkward-0.6.4/rkward/rbackend/rkbackendtransmitter.cpp0000664000175000017500000001220312633754364022370 0ustar thomasthomas/*************************************************************************** rkbackendtransmitter - description ------------------- begin : Thu Nov 18 2010 copyright : (C) 2010, 2013 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkbackendtransmitter.h" #include "rkrbackend.h" #include #include "../version.h" #include "../debug.h" RKRBackendTransmitter::RKRBackendTransmitter (const QString &servername, const QString &token) { RK_TRACE (RBACKEND); RKRBackendTransmitter::servername = servername; RKRBackendTransmitter::token = token; } RKRBackendTransmitter::~RKRBackendTransmitter () { RK_TRACE (RBACKEND); if (!current_sync_requests.isEmpty ()) { RK_DEBUG (RBACKEND, DL_WARNING, "%d pending requests while exiting RKRBackendTransmitter", current_sync_requests.size ()); } if (!connection) return; // To prevent closing the process before the frontend has had a chance to see the QuitCommand if (connection->bytesToWrite ()) connection->waitForBytesWritten (1000); msleep (1000); } void RKRBackendTransmitter::timerEvent (QTimerEvent *) { // do not trace flushOutput (false); } void RKRBackendTransmitter::run () { RK_TRACE (RBACKEND); QLocalSocket* con = new QLocalSocket (this); con->connectToServer (servername); setConnection (con); if (!connection->waitForConnected ()) handleTransmissionError ("Could not connect: " + connection->errorString ()); // handshake connection->write (token.toLocal8Bit ().data ()); connection->write ("\n"); connection->write (RKWARD_VERSION); connection->write ("\n"); connection->waitForBytesWritten (); startTimer (200); // calls flushOutput(false), periodically. See timerEvent() exec (); } void RKRBackendTransmitter::writeRequest (RBackendRequest *request) { RK_TRACE (RBACKEND); if (request->type != RBackendRequest::Output) flushOutput (true); transmitRequest (request); connection->flush (); if (request->synchronous) { current_sync_requests.append (request); RK_DEBUG (RBACKEND, DL_DEBUG, "Expecting replies for %d requests (added %p)", current_sync_requests.size (), request); } else { delete request; } } void RKRBackendTransmitter::requestReceived (RBackendRequest* request) { RK_TRACE (RBACKEND); // first check for requests which originated in the frontend if (request->type == RBackendRequest::Interrupt) { RKRBackend::this_pointer->interruptCommand (request->params.value ("commandid", -1).toInt ()); } else if (request->type == RBackendRequest::PriorityCommand) { RKRBackend::this_pointer->setPriorityCommand (request->takeCommand ()); } else { // requests which originated in the backend below this line if (current_sync_requests.isEmpty ()) { RK_ASSERT (false); return; } // "Synchronous" requests are not necessarily answered in the order they have been queued int id = request->id; RBackendRequest* current_sync_request = 0; for (int i = current_sync_requests.size () - 1; i >= 0; --i) { RBackendRequest *candidate = current_sync_requests[i]; if (id == candidate->id) { current_sync_request = current_sync_requests.takeAt (i); break; } } RK_ASSERT (current_sync_request); if (current_sync_request->type == RBackendRequest::Output) { delete current_sync_request; // this was just our internal request } else { current_sync_request->mergeReply (request); current_sync_request->done = true; } RK_DEBUG (RBACKEND, DL_DEBUG, "Expecting replies for %d requests (popped %p)", current_sync_requests.size (), current_sync_request); } delete request; } void RKRBackendTransmitter::flushOutput (bool force) { if (!current_sync_requests.isEmpty ()) return; RKRBackend::this_pointer->fetchStdoutStderr (force); ROutputList out = RKRBackend::this_pointer->flushOutput (force); if (out.isEmpty ()) return; RK_TRACE (RBACKEND); // output request would not strictly need to be synchronous. However, making them synchronous ensures that the frontend is keeping up with the output sent by the backend. RBackendRequest* request = new RBackendRequest (true, RBackendRequest::Output); request->output = new ROutputList (out); writeRequest (request); } void RKRBackendTransmitter::handleTransmissionError (const QString &message) { RK_TRACE (RBACKEND); RK_DEBUG (RBACKEND, DL_ERROR, "%s", qPrintable ("Transmission error " + message)); RKRBackend::tryToDoEmergencySave (); } #include "rkbackendtransmitter.moc" rkward-0.6.4/rkward/rbackend/rkstructuregetter.h0000664000175000017500000000507512633754364021435 0ustar thomasthomas/*************************************************************************** rkstructuregetter - description ------------------- begin : Wed Apr 11 2007 copyright : (C) 2007, 2009, 2010, 2011 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKSTRUCTUREGETTER_H #define RKSTRUCTUREGETTER_H #include #include class RData; /** Low level helper class for getting the structure of R objects (.rk.get.structure). */ class RKStructureGetter { public: explicit RKStructureGetter (bool keep_evalled_promises); ~RKStructureGetter (); RData *getStructure (SEXP toplevel, SEXP name, SEXP envlevel, SEXP namespacename); private: struct GetStructureWorkerArgs { SEXP toplevel; QString name; int add_type_flags; RData *storage; int nesting_depth; RKStructureGetter *getter; }; void getStructureWorker (SEXP value, const QString &name, int add_type_flags, RData *storage, int nesting_depth); /** needed to wrap things inside an R_ToplevelExec */ static void getStructureWrapper (GetStructureWorkerArgs *data); void getStructureSafe (SEXP value, const QString &name, int add_type_flags, RData *storage, int nesting_depth); SEXP resolvePromise (SEXP from); SEXP prefetch_fun (const char *name, bool from_base=true); bool with_namespace; SEXP namespace_envir; SEXP toplevel_value; SEXP class_fun; SEXP dims_fun; SEXP meta_attrib; SEXP get_meta_fun; SEXP is_matrix_fun; SEXP is_array_fun; SEXP is_list_fun; SEXP is_function_fun; SEXP is_environment_fun; SEXP as_environment_fun; SEXP is_factor_fun; SEXP is_numeric_fun; SEXP is_character_fun; SEXP is_logical_fun; SEXP names_fun; SEXP args_fun; SEXP double_brackets_fun; SEXP length_fun; SEXP rk_get_slots_fun; int num_prefetched_funs; bool keep_evalled_promises; }; #endif rkward-0.6.4/rkward/rbackend/rkfrontendtransmitter.h0000664000175000017500000000343212633754364022271 0ustar thomasthomas/*************************************************************************** rkfrontendtransmitter - description ------------------- begin : Thu Nov 04 2010 copyright : (C) 2010 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKFRONTENDTRANSMITTER_H #define RKFRONTENDTRANSMITTER_H #include "rktransmitter.h" class QProcess; class QLocalServer; class RKGraphicsDeviceFrontendTransmitter; class RKFrontendTransmitter : public RKAbstractTransmitter, public RKROutputBuffer { Q_OBJECT public: RKFrontendTransmitter (); ~RKFrontendTransmitter (); void run (); bool doMSleep (int delay) { msleep (delay); return true; }; void writeRequest (RBackendRequest *request); void requestReceived (RBackendRequest *request); private slots: void connectAndEnterLoop (); void backendExit (int exitcode); private: void handleTransmissionError (const QString &message); QProcess* backend; QLocalServer* server; RKGraphicsDeviceFrontendTransmitter* rkd_transmitter; }; #endif rkward-0.6.4/rkward/rbackend/rktransmitter.cpp0000664000175000017500000001730312633754364021066 0ustar thomasthomas/*************************************************************************** rktransmitter - description ------------------- begin : Thu Nov 18 2010 copyright : (C) 2010, 2013 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rktransmitter.h" #include "../debug.h" void RKRBackendSerializer::serialize (const RBackendRequest &request, QDataStream &stream) { RK_TRACE (RBACKEND); stream << (qint16) request.id; stream << (qint8) request.type; stream << request.synchronous; stream << request.done; // well, not really needed, but... if (request.command) { stream << true; serializeProxy (*(request.command), stream); } else { stream << false; } if (request.output) { RK_ASSERT (request.type == RBackendRequest::Output); stream << true; serializeOutput (*(request.output), stream); } else { stream << false; } stream << request.params; } RBackendRequest *RKRBackendSerializer::unserialize (QDataStream &stream) { RK_TRACE (RBACKEND); RBackendRequest *request = new RBackendRequest (false, RBackendRequest::OtherRequest); // will be overwritten RBackendRequest::_id--; bool dummyb; qint8 dummy8; qint16 dummy16; stream >> dummy16; request->id = dummy16; stream >> dummy8; request->type = (RBackendRequest::RCallbackType) dummy8; stream >> request->synchronous; stream >> dummyb; request->done = dummyb; stream >> dummyb; if (dummyb) request->command = unserializeProxy (stream); stream >> dummyb; if (dummyb) request->output = unserializeOutput (stream); stream >> request->params; return request; } void RKRBackendSerializer::serializeOutput (const ROutputList &list, QDataStream &stream) { RK_TRACE (RBACKEND); stream << (qint32) list.size (); for (qint32 i = 0; i < list.size (); ++i) { stream << (qint8) list[i]->type; stream << list[i]->output; } } ROutputList* RKRBackendSerializer::unserializeOutput (QDataStream &stream) { RK_TRACE (RBACKEND); ROutputList *ret = new ROutputList (); qint32 len; stream >> len; #if QT_VERSION >= 0x040700 ret->reserve (len); #endif for (qint32 i = 0; i < len; ++i) { ROutput* out = new ROutput; qint8 dummy8; stream >> dummy8; out->type = (ROutput::ROutputType) dummy8; stream >> out->output; ret->append (out); } return ret; } void RKRBackendSerializer::serializeData (const RData &data, QDataStream &stream) { RK_TRACE (RBACKEND); RData::RDataType type = data.getDataType (); stream << (qint8) type; if (type == RData::IntVector) stream << data.intVector (); else if (type == RData::StringVector) stream << data.stringVector (); else if (type == RData::RealVector) stream << data.realVector (); else if (type == RData::StructureVector) { RData::RDataStorage list = data.structureVector (); qint32 len = list.size (); stream << len; for (qint32 i = 0; i < list.size (); ++i) { serializeData (*(list[i]), stream); } } else { RK_ASSERT (type == RData::NoData); } } RData* RKRBackendSerializer::unserializeData (QDataStream &stream) { RK_TRACE (RBACKEND); RData* ret = new RData; RData::RDataType type; qint8 dummy8; stream >> dummy8; type = (RData::RDataType) dummy8; if (type == RData::IntVector) { RData::IntStorage data; stream >> data; ret->setData (data); } else if (type == RData::StringVector) { RData::StringStorage data; stream >> data; ret->setData (data); } else if (type == RData::RealVector) { RData::RealStorage data; stream >> data;; ret->setData (data); } else if (type == RData::StructureVector) { RData::RDataStorage data; qint32 len; stream >> len; #if QT_VERSION >= 0x040700 data.reserve (len); #endif for (qint32 i = 0; i < len; ++i) { data.append (unserializeData (stream)); } ret->setData (data); } else { RK_ASSERT (type == RData::NoData); } return ret; } void RKRBackendSerializer::serializeProxy (const RCommandProxy &proxy, QDataStream &stream) { RK_TRACE (RBACKEND); stream << proxy.command; stream << (qint32) proxy.type; stream << (qint32) proxy.id; stream << (qint32) proxy.status; stream << (qint32) proxy.has_been_run_up_to; serializeData (proxy, stream); } RCommandProxy* RKRBackendSerializer::unserializeProxy (QDataStream &stream) { RK_TRACE (RBACKEND); QString command; stream >> command; qint32 type; stream >> type; RCommandProxy* ret = new RCommandProxy (command, type); qint32 dummy32; stream >> dummy32; ret->id = dummy32; stream >> dummy32; ret->status = dummy32; stream >> dummy32; ret->has_been_run_up_to = dummy32; RData *data = unserializeData (stream); ret->swallowData (*data); delete (data); return ret; } #include #include RKAbstractTransmitter* RKAbstractTransmitter::_instance = 0; RKAbstractTransmitter::RKAbstractTransmitter () : QThread () { RK_TRACE (RBACKEND); RK_ASSERT (_instance == 0); // NOTE: Although there are two instances of an abstract transmitter in an RKWard session, these live in different processes. _instance = this; connection = 0; moveToThread (this); } RKAbstractTransmitter::~RKAbstractTransmitter () { RK_TRACE (RBACKEND); } void RKAbstractTransmitter::transmitRequest (RBackendRequest *request) { RK_TRACE (RBACKEND); RK_ASSERT (connection); if (!connection->isOpen ()) { handleTransmissionError ("Connection not open while trying to write request. Last error was: " + connection->errorString ()); return; } RKRBackendSerializer::serialize (*request, streamer.outstream); RK_DEBUG (RBACKEND, DL_DEBUG, "Transmitting request of length %d", streamer.outSize ()); streamer.writeOutBuffer (); } void RKAbstractTransmitter::customEvent (QEvent *e) { RK_TRACE (RBACKEND); if (((int) e->type ()) == ((int) RKRBackendEvent::RKWardEvent)) { RKRBackendEvent *ev = static_cast (e); writeRequest (ev->data ()); } else { RK_ASSERT (false); return; } } void RKAbstractTransmitter::fetchTransmission () { RK_TRACE (RBACKEND); while (connection->bytesAvailable ()) { if (!streamer.readInBuffer ()) break; requestReceived (RKRBackendSerializer::unserialize (streamer.instream)); RK_ASSERT (streamer.instream.atEnd ()); // full transmission should have been read } if (!connection->isOpen ()) { handleTransmissionError ("Connection closed unexepctedly. Last error was: " + connection->errorString ()); return; } } void RKAbstractTransmitter::setConnection (QLocalSocket *_connection) { RK_TRACE (RBACKEND); RK_ASSERT (!connection); connection = _connection; streamer.setIODevice (connection); RK_ASSERT (connection->isOpen ()); connect (connection, SIGNAL (readyRead()), this, SLOT (fetchTransmission())); connect (connection, SIGNAL (disconnected()), this, SLOT (disconnected())); // In case something is pending already. if (connection->bytesAvailable ()) QTimer::singleShot (0, this, SLOT (fetchTransmission())); } void RKAbstractTransmitter::disconnected () { RK_TRACE (RBACKEND); handleTransmissionError ("Connection closed unexpectedly. Last error was: " + connection->errorString ()); } #include "rktransmitter.moc" rkward-0.6.4/rkward/rbackend/rcommandreceiver.h0000664000175000017500000000760312633754364021151 0ustar thomasthomas/*************************************************************************** rcommandreceiver - description ------------------- begin : Thu Aug 19 2004 copyright : (C) 2004, 2006, 2007, 2010 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RCOMMANDRECEIVER_H #define RCOMMANDRECEIVER_H #include #include "rcommand.h" /** Use this class as a base for classes that need to handle RCommands. Most importantly it provides a virtual function (rCommandDone ()) for handling of RCommand-results. Reimplement this to interpret the command-results. For windows/dialogs which interpret RCommand results, the receiver provides a special mechanism. The problem with those windows/dialogs is, that the user may close them, while there are still RCommands due to come in, i.e. they can't be deleted, but rather need to wait for the remaining results to come in. This class will keep track of which RCommands are still out there (and expected to return to this receiver). When deleting the object, it will unregister from all outstanding commands, so there are no invalid pointer operations. TODO: this mechanism may be slightly costly, if there are *many* commands outstanding. Maybe for special receivers like RKWatch, which are never destroyed at run-time, the mechanism should be disabled. @Note that the (younger) RCommandNotifier class provides much easier handling based on QObject slots in most situations. Of course, you can also combine both, if you are interested in the cancelOutstandingCommands() functionality. @author Thomas Friedrichsmeier */ class RCommand; struct ROutput; typedef QList RCommandList; class RCommandReceiver { public: /** constructor. No args */ RCommandReceiver (); /** destructor */ virtual ~RCommandReceiver (); /** Causes the receiver to wait until all outstanding_commands (if any) are finished, then deletes itself */ void autoDeleteWhenDone (); protected: friend class RCommand; friend class RInterface; /** This function is called when a command for this receiver is finished (and before it is deleted). You have to implement it in your subclass to do the actual handling. @param command A pointer to the command. The pointer is still valid during this call, but the RCommand will be deleted shortly after! */ virtual void rCommandDone (RCommand *) {}; /** This function is called when there is new output for a command or this receiver. Default implementation does nothing. Reimplement if you want to get at a command's output immediately (i.e. before the command is fully completed). @param command A pointer to the command @param output The new output-fragment */ virtual void newOutput (RCommand *, ROutput *) {}; /** This function is called when a new line of the given command has been fed to the backend. Note: This is only ever called for commands of type RCommand::User. @param command A pointer to the command */ virtual void userCommandLineIn (RCommand*) {}; protected: RCommandList outstanding_commands; void cancelOutstandingCommands (); private: bool delete_when_done; void addCommand (RCommand *command); void delCommand (RCommand *command); }; #endif rkward-0.6.4/rkward/rbackend/rkasyncdatastreamhelper.h0000664000175000017500000000714012633754364022540 0ustar thomasthomas/*************************************************************************** rkasyncdatastreamhelper - description ------------------- begin : Mon Mar 18 20:06:08 CET 2013 copyright : (C) 2013 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKASYNCDATASTREAMHELPER_H #define RKASYNCDATASTREAMHELPER_H #include #include #include /** Using a QDataStream on an asynchronous connection is somewhat cumbersome due to the need to ensure that chunks of * data are complete, when we process them. This small class helps with that. Essentially: * * - write to outstream * - when a chunk is done, push it to the device using writeOutBuffer(). * * To read a chunk call * - readInBuffer() repeatedly, until it returns true * - read from instream * * Class is implemented as a template, so you can squeeze some bytes out of the protocol, if you know transmission chunks to be short * (e.g. never to exceed quint32). For maximum flexibility, use RKAsyncDataStreamHelper. */ template class RKAsyncDataStreamHelper { public: RKAsyncDataStreamHelper () : auxbuffer(), inbuffer(), outbuffer(), auxstream (&auxbuffer, QIODevice::ReadWrite), instream (&inbuffer, QIODevice::ReadOnly), outstream (&outbuffer, QIODevice::WriteOnly) { device = 0; expected_read_size = 0; } ~RKAsyncDataStreamHelper () {}; void setIODevice (QIODevice *_device) { device = _device; } void writeOutBuffer () { auxstream.device ()->seek (0); auxbuffer.resize (0); auxstream << (LENGTH_TYPE) outbuffer.size (); device->write (auxbuffer); device->write (outbuffer); outstream.device ()->seek (0); outbuffer.resize (0); } /** @returns false if no complete chunk of data is available, yet. true, if the next chunk of data is available for * processing from instream. */ bool readInBuffer () { if (!expected_read_size) { if (device->bytesAvailable () < (unsigned int) sizeof (LENGTH_TYPE)) { return false; } else { auxbuffer = device->read (sizeof (LENGTH_TYPE)); auxstream.device ()->seek (0); auxstream >> expected_read_size; } } if ((LENGTH_TYPE) device->bytesAvailable () < expected_read_size) { return false; } inbuffer = device->read (expected_read_size); instream.device ()->seek (0); expected_read_size = 0; return true; } int inSize () const { return inbuffer.size (); } int outSize () const { return outbuffer.size (); } private: QIODevice *device; LENGTH_TYPE expected_read_size; // NOTE: Order of declaration of the buffers and streams is important, as these are initialized during construction, and depend on each other QByteArray auxbuffer; QByteArray inbuffer; QByteArray outbuffer; QDataStream auxstream; public: QDataStream instream; QDataStream outstream; }; #endif rkward-0.6.4/rkward/rbackend/rkrbackend.h0000664000175000017500000002376612633754364017742 0ustar thomasthomas/*************************************************************************** rkrbackend - description ------------------- begin : Sun Jul 25 2004 copyright : (C) 2004 - 2013 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKRBACKEND_H #define RKRBACKEND_H #include #include #include #include #include #include #include "rcommand.h" #include "rcommandstack.h" #include "rkrbackendprotocol_backend.h" #ifdef Q_WS_WIN extern "C" { void RK_scheduleIntr(); } #endif class QStringList; class QTextCodec; class RInterface; struct ROutput; /** This class represents the thread the R backend is running in. So to speak, this is where the "eventloop" of R is running. The main thing happening in this class, is that it enters R's REPL (Read-evaluate-parse-loop). Whenever there are commands to be executed, those get evaluated. When there are not, processing of X11-Events in R is triggered. The rest of the time the thread sleeps. Actually, there is also a custom sub-eventloop, which gets called when the R backend has requested something. See handleRequest(). In this case, we might have to run some further child commands in the backend, before we proceed with the commands in the main queque. Some thing like: - Run some RCommand s - R backend asks for some information / action - potentially some more RCommand s are needed to accomplish this request - (additional levels of substacks) - return the result - R backend request completed - Run some more RCommand s This subordinate/nested eventloop is done in handleRequest (). A closely related class is RInterface: RKRBackend communicates with RInterface by placing QCustomEvent s, when commands are done or when the backend needs information from the frontend. For historical reasons, the definitions of RKRBackend class-members are currently spread over different files. Only one RKRBackend-object can be used in an application. Don't use this class in RKWard directly. Unless you really want to modify the internal workings of the backend, you will want to look at RInterface and use the functions there. @see RInterface @author Thomas Friedrichsmeier */ class RKRBackend : public RKROutputBuffer { public: /** constructor. Only one RKRBackend should ever be created, and that happens in RInterface::RInterface (). */ RKRBackend (); /** destructor */ virtual ~RKRBackend (); /** interrupt processing of the current command. This is much like the user pressing Ctrl+C in a terminal with R. This is probably the only non-portable function in RKRBackend, but I can't see a good way around placing it here, or to make it portable. */ static void interruptProcessing (bool interrupt); /** Enum specifying types of errors that may occur while parsing/evaluating a command in R */ enum RKWardRError { NoError=0, /**< No error */ Incomplete=1, /**< The command is incomplete. Command was syntactically ok up to given point, but incomplete. It may or may not be semantically correct. */ SyntaxError=2, /**< Syntax error */ OtherError=3 /**< Other error, usually a semantic error, e.g. object not found */ }; /** initializes the R-backend. Emits an RCallbackType::Started-request (with any error messages) when done. Note that you should call initialize only once in a application */ void initialize (const char* locale_dir); void enterEventLoop (); protected: /** low-level initialization of R */ bool startR (); /** reimplemented from RKROutputBuffer */ bool doMSleep (int msecs); public: /** convenience low-level function for running a command, directly @param command command to be runCommand @returns true if command was run successfully, false in case of an error */ bool runDirectCommand (const QString &command); /** convenience low-level function for running a command, directly. Use this overload, if you want to handle a return value. @param command command to be runCommand @param datatype the data type that should be (attempted to be) returned @returns a pointer to the RCommandProxy-instance that was created and used, internally. You can query this pointer for status and data. Be sure to delete it, when done. */ RCommandProxy *runDirectCommand (const QString &command, RCommand::CommandTypes datatype); void handleRequest (RBackendRequest *request) { handleRequest (request, true); }; /** A relic of history. In contrast to handlePlainGenericRequest(), these requests support running sub-commands. However, the remaining requests which are currently handled this way should probably be converted to dedicated RKRBackendRequest's in the future. See also handlePlainGenericRequest(). */ void handleHistoricalSubstackRequest (const QStringList &list); /** Sends a request to the frontend and returns the result (an empty QStringList in case of asynchronous requests). Note that this function has considerable overlap with handleHistoricalSubstackRequest(). Exactly which requests get handled by which function is somewhat arbitrary, ATM. However, request that do not need sub-commands to be run, should generally be converted to use handlePlainGenericRequest(). (And probably all historicalSubstackRequests should be replaced!) */ QStringList handlePlainGenericRequest (const QStringList ¶meters, bool synchronous); RCommandProxy* fetchNextCommand (); /** The command currently being executed. */ RCommandProxy *current_command; QList all_current_commands; void runCommand (RCommandProxy *command); /** only one instance of this class may be around. This pointer keeps the reference to it, for interfacing to from C to C++ */ static RKRBackend *this_pointer; static void tryToDoEmergencySave (); bool r_running; /** Check whether the runtime version of R is at least the given version. Valid only *after* startR() has been called! */ bool RRuntimeIsVersion (int major, int minor, int revision) { return (r_version >= (1000 * major + 10 * minor + revision)); } /** backend is killed. Should exit as soon as possible. @see kill */ enum KillType { NotKilled = 0, ExitNow = 1, EmergencySaveThenExit = 2, AlreadyDead = 3 } killed; /** "Kills" the backend. Actually this just tells the thread that is is about to be terminated. Allows the thread to terminate gracefully */ void kill () { killed = ExitNow; }; bool isKilled () { return (killed != NotKilled); }; QTextCodec *current_locale_codec; struct RKReplStatus { QByteArray user_command_buffer; int user_command_transmitted_up_to; bool user_command_completely_transmitted; int user_command_parsed_up_to; int user_command_successful_up_to; enum { NoUserCommand, UserCommandTransmitted, UserCommandSyntaxError, UserCommandRunning, UserCommandFailed, ReplIterationKilled } user_command_status; int eval_depth; // Number (depth) of non-user commands currently running. User commands can only run at depth 0 enum { NotInBrowserContext = 0, InBrowserContext, InBrowserContextPreventRecursion } browser_context; bool interrupted; }; static RKReplStatus repl_status; /** holds a copy of the default R_GlobalContext. Needed to find out, when a browser context has been left. */ static void *default_global_context; void commandFinished (bool check_object_updates_needed=true); /** A list of symbols that have been assigned new values during the current command */ QStringList changed_symbol_names; /** the main loop. See \ref RKRBackend for a more detailed description */ void run (const QString &locale_dir); static void scheduleInterrupt (); void startOutputCapture (); void printAndClearCapturedMessages (bool with_header); void printCommand (const QString &command); void catToOutputFile (const QString &out); QMutex all_current_commands_mutex; QList current_commands_to_cancel; bool too_late_to_interrupt; void interruptCommand (int command_id); /** check stdout and stderr for new output (from sub-processes). Since this function is called from both threads, it is protected by a mutex. * @param forcibly: if false, and the other thread currently has a lock on the mutex, do nothing, and return false. * @returns: true, if output was actually fetched (or no output was available), false, if the function gave up on a locked mutex. */ bool fetchStdoutStderr (bool forcibly); /** public for technical reasons */ QMutex stdout_stderr_mutex; void setPriorityCommand (RCommandProxy *command); RCommandProxy *pending_priority_command; QMutex priority_command_mutex; private: void clearPendingInterrupt (); protected: RCommandProxy* handleRequest (RBackendRequest *request, bool mayHandleSubstack); private: int stdout_stderr_fd; /** set up R standard callbacks */ void setupCallbacks (); /** connect R standard callbacks */ void connectCallbacks (); int r_version; QString output_file; /** A copy of the names of the toplevel environments (as returned by "search ()"). */ QStringList toplevel_env_names; /** A copy of the names of the toplevel symbols in the .GlobalEnv. */ QStringList global_env_toplevel_names; /** check whether the object list / global environment / individual symbols have changed, and updates them, if needed */ void checkObjectUpdatesNeeded (bool check_list); /** The previously executed command. Only non-zero until a new command has been requested. */ RCommandProxy *previous_command; }; #endif rkward-0.6.4/rkward/rbackend/rksignalsupport.cpp0000664000175000017500000001421612633754364021424 0ustar thomasthomas/*************************************************************************** rksignalsupport - description ------------------- begin : Thu Nov 22 2007 copyright : (C) 2007, 2009, 2010 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rksignalsupport.h" #include #include "rkrbackend.h" #include "../debug.h" // On some platforms sighandler_t is defined, on others it is not, but it's required to be the same everywhere, anyway. // To avoid re-definition errors, we just use our own "type". typedef void (*rk_sighandler_t) (int); namespace RKSignalSupportPrivate { #ifdef Q_WS_WIN rk_sighandler_t r_sigsegv_handler = 0; rk_sighandler_t default_sigsegv_handler = 0; rk_sighandler_t r_sigill_handler = 0; rk_sighandler_t default_sigill_handler = 0; rk_sighandler_t r_sigabrt_handler = 0; rk_sighandler_t default_sigabrt_handler = 0; #else struct sigaction r_sigsegv_handler; struct sigaction default_sigsegv_handler; struct sigaction r_sigill_handler; struct sigaction default_sigill_handler; struct sigaction r_sigabrt_handler; struct sigaction default_sigabrt_handler; #endif rk_sighandler_t r_sigint_handler = 0; void (*new_sigint_handler) (void) = 0; void internal_sigint_handler (int num) { new_sigint_handler (); signal (num, internal_sigint_handler); } #ifdef Q_WS_WIN void signal_proxy (int signum) { rk_sighandler_t r_handler = r_sigsegv_handler; rk_sighandler_t default_handler = default_sigsegv_handler; #else void signal_proxy (int signum, siginfo_t *info, void *context) { struct sigaction r_handler = r_sigsegv_handler; struct sigaction default_handler = default_sigsegv_handler; #endif if (signum == SIGILL) { r_handler = r_sigill_handler; default_handler = default_sigill_handler; } else if (signum == SIGABRT) { r_handler = r_sigabrt_handler; default_handler = default_sigabrt_handler; } else { RK_ASSERT (signum == SIGSEGV); } RKRBackend::tryToDoEmergencySave (); // if we are not in the R thread, handling the signal in R does more harm than good. if (RKRBackendProtocolBackend::inRThread ()) { #ifdef Q_WS_WIN if (r_handler) { r_handler (signum); return; } #else if (r_handler.sa_sigaction) { r_handler.sa_sigaction (signum, info, context); return; } else if (r_handler.sa_handler) { r_handler.sa_handler (signum); return; } #endif } #ifdef Q_WS_WIN if (default_handler) { default_handler (signum); return; } #else // this might be a Qt/KDE override or default handling if (default_handler.sa_sigaction) { default_handler.sa_sigaction (signum, info, context); return; } else if (default_handler.sa_handler) { default_handler.sa_handler (signum); return; } #endif RK_ASSERT (false); // had not handler? Could conceivably happen, but should not. signal (signum, SIG_DFL); raise (signum); } } void RKSignalSupport::saveDefaultSignalHandlers () { RK_TRACE (RBACKEND); #ifdef Q_WS_WIN RKSignalSupportPrivate::default_sigsegv_handler = signal (SIGSEGV, SIG_DFL); RKSignalSupportPrivate::default_sigill_handler = signal (SIGILL, SIG_DFL); RKSignalSupportPrivate::default_sigabrt_handler = signal (SIGABRT, SIG_DFL); #else sigaction (SIGSEGV, 0, &RKSignalSupportPrivate::default_sigsegv_handler); sigaction (SIGILL, 0, &RKSignalSupportPrivate::default_sigill_handler); sigaction (SIGABRT, 0, &RKSignalSupportPrivate::default_sigabrt_handler); #endif } void RKSignalSupport::installSignalProxies () { RK_TRACE (RBACKEND); #ifdef Q_WS_WIN RKSignalSupportPrivate::r_sigsegv_handler = signal (SIGSEGV, &RKSignalSupportPrivate::signal_proxy); RKSignalSupportPrivate::r_sigill_handler = signal (SIGILL, &RKSignalSupportPrivate::signal_proxy); RKSignalSupportPrivate::r_sigabrt_handler = signal (SIGABRT, &RKSignalSupportPrivate::signal_proxy); #else // retrieve R signal handler sigaction (SIGSEGV, 0, &RKSignalSupportPrivate::r_sigsegv_handler); sigaction (SIGILL, 0, &RKSignalSupportPrivate::r_sigill_handler); sigaction (SIGABRT, 0, &RKSignalSupportPrivate::r_sigabrt_handler); // set new proxy handlers struct sigaction proxy_action; proxy_action = RKSignalSupportPrivate::r_sigsegv_handler; proxy_action.sa_flags |= SA_SIGINFO; proxy_action.sa_sigaction = &RKSignalSupportPrivate::signal_proxy; sigaction (SIGSEGV, &proxy_action, 0); proxy_action = RKSignalSupportPrivate::r_sigill_handler; proxy_action.sa_flags |= SA_SIGINFO; proxy_action.sa_sigaction = &RKSignalSupportPrivate::signal_proxy; sigaction (SIGILL, &proxy_action, 0); proxy_action = RKSignalSupportPrivate::default_sigabrt_handler; proxy_action.sa_flags |= SA_SIGINFO; proxy_action.sa_sigaction = &RKSignalSupportPrivate::signal_proxy; sigaction (SIGABRT, &proxy_action, 0); #endif } void RKSignalSupport::installSigIntAndUsrHandlers (void (*handler) (void)) { RK_TRACE (RBACKEND); RK_ASSERT (!RKSignalSupportPrivate::r_sigint_handler); RKSignalSupportPrivate::new_sigint_handler = handler; RKSignalSupportPrivate::r_sigint_handler = signal (SIGINT, &RKSignalSupportPrivate::internal_sigint_handler); #ifndef Q_WS_WIN // default action in R: save and quit. We use these as a proxy for SIGINT, instead. signal (SIGUSR1, &RKSignalSupportPrivate::internal_sigint_handler); signal (SIGUSR2, &RKSignalSupportPrivate::internal_sigint_handler); #endif } void RKSignalSupport::callOldSigIntHandler () { RK_TRACE (RBACKEND); RKSignalSupportPrivate::r_sigint_handler (SIGINT); } rkward-0.6.4/rkward/rbackend/rksessionvars.h0000664000175000017500000000555512633754364020544 0ustar thomasthomas/*************************************************************************** rksessionvars - description ------------------- begin : Thu Sep 08 2011 copyright : (C) 2011, 2013 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKSESSIONVARS_H #define RKSESSIONVARS_H #include #include class RInterface; /** Singleton for storing information about the running R session, and - for some of the info - notifying about changes. */ class RKSessionVars : public QObject { Q_OBJECT public: static RKSessionVars* instance () { return _instance; }; QStringList installedPackages () const { return installed_packages; }; void setInstalledPackages (const QStringList &new_list); static void setRVersion (const QString &version_string); /** compare given version string against the running version of RKWard. Returns -1 for earlier than current, 0 for equal, 1 for later than current version */ static int compareRKWardVersion (const QString &version); /** compare given version string against the running version of R. Returns -1 for earlier than current, 0 for equal, 1 for later than current version. NOTE: The version of R is not known until the R backend has been started. In this case, 0 is always returned */ static int compareRVersion (const QString &version); /** Split "a.b.c.d.e-fghi" into up to four numeric portions (returned as four bytes in a single 32bit unsigned int). Anything else (everything after the fourth dot, or after the first character that is neither dot, nor digit) is returned as suffix (via the suffix pointer; if that is 0, an error is reported, instead). */ static quint32 parseVersionString (const QString &version, QString *suffix); static QStringList frontendSessionInfo (); signals: void installedPackagesChanged (); protected: friend class RInterface; RKSessionVars (RInterface *parent); ~RKSessionVars (); private: static RKSessionVars* _instance; QStringList installed_packages; static quint32 rkward_version; static QString rkward_version_suffix; static quint32 r_version; static QString r_version_string; }; #endif rkward-0.6.4/rkward/rbackend/rkrbackendprotocol_backend.h0000664000175000017500000000401412633754364023154 0ustar thomasthomas/*************************************************************************** rkrbackendprotocol - description ------------------- begin : Thu Nov 04 2010 copyright : (C) 2010, 2011, 2013 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKRBACKENDPROTOCOL_BACKEND_H #define RKRBACKENDPROTOCOL_BACKEND_H #include "rkrbackendprotocol_shared.h" class QThread; class RKRBackendProtocolBackend { public: static bool inRThread (); static QString dataDir () { return _instance->data_dir; }; static QString rkdServerName () { return _instance->rkd_server_name; }; static QString backendDebugFile (); RKRBackendProtocolBackend (const QString &data_dir, const QString &rkd_server_name); ~RKRBackendProtocolBackend (); protected: friend class RKRBackendProtocolFrontend; friend class RKRBackend; friend class RKRBackendThread; friend class RKRBackendTransmitter; void sendRequest (RBackendRequest *request); static void msleep (int delay); static RKRBackendProtocolBackend* instance () { return _instance; }; QString data_dir; private: QString rkd_server_name; static RKRBackendProtocolBackend* _instance; QThread *r_thread; #ifndef Q_WS_WIN friend void completeForkChild (); Qt::HANDLE r_thread_id; #endif }; #endif rkward-0.6.4/rkward/rbackend/rdata.h0000664000175000017500000001036712633754364016720 0ustar thomasthomas/*************************************************************************** rdata - description ------------------- begin : Sun Oct 01 2006 copyright : (C) 2006, 2010 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RDATA_H #define RDATA_H #include #include /** Class to represent data (other than output/erros) passed from the R backend to the main thread. Data is usually a vector of type int, double or QString, but can also contain a hierarchy of RData*s. RCommand is a subclass of this */ class RData { public: RData (); ~RData (); enum RDataType { StructureVector=0, IntVector=1, RealVector=2, StringVector=3, NoData=4 }; typedef QVector IntStorage; typedef QVector RealStorage; typedef QVector RDataStorage; typedef QStringList StringStorage; /** returns the type of data contained */ RDataType getDataType () const { return datatype; }; /** returns the length (size) of the data array. @see RCommand::GetStringVector @see RCommand::GetRealVector @see RCommand::GetIntVector @see RCommand:GetStructure */ unsigned int getDataLength () const; /** returns a vector of double, if that is the type of data contained (else, an assert is raised, and an empty vector is returned). Can be used safely on a null RData pointer (but raises an assert in this case). @see RCommand::GetRealVector @see RData::getDataType () */ const RealStorage realVector () const { if (this && (datatype == RealVector)) { return (*static_cast (data)); } doAssert (RealVector); return RealStorage (); } /** returns a vector of int, if that is the type of data contained (else, an assert is raised, and an empty vector is returned). Can be used safely on a null RData pointer (but raises an assert in this case). @see RCommand::GetIntVector @see RData::getDataType () */ const IntStorage intVector () const { if (this && (datatype == IntVector)) { return (*static_cast (data)); } doAssert (IntVector); return IntStorage (); } /** returns a QStringList, if that is the type of data contained (else, an assert is raised, and an empty vector is returned). Can be used safely on a null RData pointer (but raises an assert in this case). @see RCommand::GetStringVector @see RData::getDataType () */ const StringStorage stringVector () const { if (this && (datatype == StringVector)) { return (*static_cast (data)); } doAssert (StringVector); return StringStorage (); } /** returns a vector of RData*, if that is the type of data contained (else, an assert is raised, and an empty vector is returned). Can be used safely on a null RData pointer (but raises an assert in this case). @see RCommand::GetStructureVector @see RData::getDataType () */ const RDataStorage structureVector () const { if (this && (datatype == StructureVector)) { return (*static_cast (data)); } doAssert (StructureVector); return RDataStorage (); } void discardData (); /** purely for debugging! */ void printStructure (const QString &prefix); void setData (const RDataStorage &from); void setData (const IntStorage &from); void setData (const RealStorage &from); void setData (const StringStorage &from); /** public for technical reasons only. Do not use! Move data from the given RData to this RData. The source RData is emptied! */ void swallowData (RData &from); private: void doAssert (RDataType requested_type) const; RDataType datatype; void *data; }; #endif rkward-0.6.4/rkward/rbackend/rkreventloop.h0000664000175000017500000000507612633754364020360 0ustar thomasthomas/*************************************************************************** rkreventloop - description ------------------- begin : Tue Apr 23 2013 copyright : (C) 2013 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKREVENTLOOP_H #define RKREVENTLOOP_H #include // For Q_WS_WIN namespace RKREventLoop { /** This - somewhat misnamed - function takes care of processing R's events (not only X11). * It is like R_ProcessEvents (except for the scary warning in the header file, that R_ProcessEvents does not work on Unix), * but for safety, event processing is done inside a toplevel context. Thus, this function is guaranteed to return. * Call this periodically to make R's x11 windows process their events */ void processX11Events (); /** Register a function that will be called inside R's event loop. Implementation differs across platforms. The handler function * @em might get called unconditionally (hooked into R_PolledEvents()), periodically, or only when needed. To make sure, the * latter works, call wakeRKEventHandler() whenever there is(are) some new task(s) for the handler. * * Event on platforms where the event handler is called "on demand", there is no guarantee, that this happens exactly once per demand. * The registered event handling function should be prepared to handle zero, one, or several new events. * * Currently, only one handler may be registered. */ void setRKEventHandler (void (* handler) ()); /** Call this (potentially from a separate thread) to wake the handler set by setRKEventHandler() in the next iteration of * R's event loop. */ void wakeRKEventHandler (); #ifdef Q_WS_WIN /** On Windows, Rp->Callback has to be set to this function during startup. */ void winRKEventHandlerWrapper (void); #endif }; #endif rkward-0.6.4/rkward/rbackend/rkrbackendprotocol_frontend.cpp0000664000175000017500000000650412633754364023745 0ustar thomasthomas/*************************************************************************** rkrbackendprotocol - description ------------------- begin : Thu Nov 04 2010 copyright : (C) 2010, 2011 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkrbackendprotocol_frontend.h" #include "rinterface.h" #include #include "rkfrontendtransmitter.h" #include #include "../debug.h" RKRBackendProtocolFrontend* RKRBackendProtocolFrontend::_instance = 0; RKRBackendProtocolFrontend::RKRBackendProtocolFrontend (RInterface* parent) : QObject (parent) { RK_TRACE (RBACKEND); RK_ASSERT (!_instance); frontend = parent; _instance = this; } RKRBackendProtocolFrontend::~RKRBackendProtocolFrontend () { RK_TRACE (RBACKEND); terminateBackend (); RKFrontendTransmitter::instance ()->quit (); RKFrontendTransmitter::instance ()->wait (1000); delete RKFrontendTransmitter::instance (); } void RKRBackendProtocolFrontend::setupBackend () { RK_TRACE (RBACKEND); new RKFrontendTransmitter (); } void RKRBackendProtocolFrontend::setRequestCompleted (RBackendRequest *request) { RK_TRACE (RBACKEND); bool sync = request->synchronous; request->completed (); if (!sync) return; RKRBackendEvent* ev = new RKRBackendEvent (request); qApp->postEvent (RKFrontendTransmitter::instance (), ev); QThread::yieldCurrentThread (); } ROutputList RKRBackendProtocolFrontend::flushOutput (bool force) { return static_cast (RKFrontendTransmitter::instance ())->flushOutput (force); } void RKRBackendProtocolFrontend::interruptCommand (int command_id) { RK_TRACE (RBACKEND); RBackendRequest *req = new RBackendRequest (false, RBackendRequest::Interrupt); req->params.insert ("commandid", QVariant (command_id)); qApp->postEvent (RKFrontendTransmitter::instance (), new RKRBackendEvent (req)); } void RKRBackendProtocolFrontend::sendPriorityCommand (RCommandProxy* proxy) { RK_TRACE (RBACKEND); RBackendRequest *req = new RBackendRequest (false, RBackendRequest::PriorityCommand); req->command = proxy; qApp->postEvent (RKFrontendTransmitter::instance (), new RKRBackendEvent (req)); } void RKRBackendProtocolFrontend::terminateBackend () { RK_TRACE (RBACKEND); // Backend process will terminate automatically, when the transmitter dies } void RKRBackendProtocolFrontend::customEvent (QEvent *e) { if (((int) e->type ()) == ((int) RKRBackendEvent::RKWardEvent)) { RKRBackendEvent *ev = static_cast (e); frontend->handleRequest (ev->data ()); } else { RK_ASSERT (false); return; } } rkward-0.6.4/rkward/rbackend/rinterface.h0000664000175000017500000005361612633754364017753 0ustar thomasthomas/*************************************************************************** rinterface.h - description ------------------- begin : Fri Nov 1 2002 copyright : (C) 2002 - 2013 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RINTERFACE_H #define RINTERFACE_H #include #include #include "rcommand.h" #include "rcommandreceiver.h" class RCommand; class RKWardMainWindow; class RKRBackend; class RBackendRequest; /** This class provides the main interface to the R-processor. Note that since communication with R is asynchronous, there is no way to get R-output within the same function, the request is submitted. You have to provide an RCommandReceiver object, if you're interested in the output. For a detailed explanation see \ref UsingTheInterfaceToR . @see RCommand *@author Thomas Friedrichsmeier */ class RInterface : public QObject, public RCommandReceiver { Q_OBJECT public: /** constructor */ RInterface(); /** destructor */ ~RInterface(); /** issues the given command in the given chain */ void issueCommand (RCommand *command, RCommandChain *chain=0); /** convenience function to create a new command and issue it. See documentation on RCommand::RCommand () and RInterface::issueCommand () */ void issueCommand (const QString &command, int type = 0, const QString &rk_equiv = QString (), RCommandReceiver *receiver=0, int flags=0, RCommandChain *chain=0); /** opens a new command chain. Returns a pointer to the new chain. If you specify a parent, the new chain will be a sub-chain of that chain. */ RCommandChain *startChain (RCommandChain *parent=0); /** closes the command chain. The chain (and even its parent, if it is already closed) may be deleted right afterwards! */ void closeChain (RCommandChain *chain); /** Ensures that the given command will not be executed, or, if it is already running, interrupts it. Note that commands marked RCommand::Sync can not be interrupted. */ void cancelCommand (RCommand *command); /** Cancels the given command, unless it has already been submitted to the backend. Returns true, if command was cancelled, false otherwise. */ bool softCancelCommand (RCommand *command); /** Cancels all running or outstanding commands. @See cancelCommand() */ void cancelAll (); /** Pauses process. The current command will continue to run, but no new command will be */ void pauseProcessing (bool pause); /** returns the command currently running in the thread. Be careful when using the returned pointer! */ RCommand *runningCommand () const { return (all_current_commands.isEmpty () ? 0 : all_current_commands.last ()); }; bool backendIsDead () { return backend_dead; }; bool backendIsIdle (); static bool isNaReal (double value) { return na_real == value; }; static bool isNaInt (int value) { return na_int == value; }; private: void timerEvent (QTimerEvent *); int flush_timer_id; /** Calls RThread::flushOutput(), and takes care of adding the output to all applicable commands */ void flushOutput (bool forced); /** pointer to the RThread */ RKRBackend *r_thread; /** Used by the testing framework. see R function rk.record.commands(). */ QFile command_logfile; enum { NotRecordingCommands, RecordingCommands, RecordingCommandsUnfiltered } command_logfile_mode; void processHistoricalSubstackRequest (const QStringList &calllist); QStringList processPlainGenericRequest (const QStringList &calllist); void processRBackendRequest (RBackendRequest *request); /** A list of all commands that have entered, and not yet left, the backend thread */ QList all_current_commands; /** NOTE: processsing R events while waiting for the next command may, conceivably, lead to new requests, which may also wait for sub-commands! Thus we keep a simple stack of requests. */ QList command_requests; RBackendRequest* currentCommandRequest () const { return (command_requests.isEmpty () ? 0 : command_requests.last ()); }; void tryNextCommand (); void doNextCommand (RCommand *command); RCommand *popPreviousCommand (int id); void handleCommandOut (RCommand *command); bool previously_idle; RCommandChain* openSubcommandChain (RCommand *parent_command); QList current_commands_with_subcommands; void closeSubcommandChain (RCommand *parent_command); /** @see locked */ enum LockType { User=1 /**< locked on user request */ }; /** Used for locking the backend, meaning not further commands will be given to the backend. It is called, if the RThread is paused on User request. * @see RInterface::pauseProcessing * May be an OR'ed combination of several LockType s, but currently, there is only one LockType */ int locked; QString startup_errors; bool startup_phase2_error; int num_active_output_record_requests; ROutput::ROutputType previous_output_type; QString recorded_output; RCommand *dummy_command_on_stack; friend class RKRBackendProtocolFrontend; bool backend_dead; static double na_real; static int na_int; friend class RKWardMainWindow; friend class RCommand; protected: void handleRequest (RBackendRequest *request); void rCommandDone (RCommand *command); }; /** \page UsingTheInterfaceToR Using the Interface to R \brief An Introduction to using the R-backend: Running commands in R and handling the results This page tries to give you an introduction into using the RInterface and related classes. You'll learn how to submit a command for evaluation by R and how you can get and handle the results. Note that this is fairly low-level. You do not need to read this documentation in order to develop plugins. You'll only need to know this in order to do C++-hacking in rkward. The page is divided into several sections on special problems, but if you're new to rkward, you will probably want to read it as a whole instead of jumping right to those sections. \section UsingTheInterfaceToRTheSimpleCase The simple case: Fire and forget In the most simple case, all you want to do is to send a command to be evaluated executed in the R backend, and you are not interested in what happens (whether the command runs successfully, or what the output is). For this, all you need is the following code: \code #include "rkglobals.h" #include "rbackend/rinterface.h" RKGlobals::rInterface ()->issueCommand ("print (\"hello world!\")", RCommand::User); \endcode You will note, that actually there are two RInterface::issueCommand functions, this one is obviously the one taking a QString and several further parameters as argument. It is actually quite similar to the other RInterface::issueCommand function which takes an RCommand as a parameter. This convenience class basically just creates an RCommand with the same parameters as the constructor of RCommand (RCommand::RCommand), and then submits this RCommand. We'll discuss what an RCommand really is further down below. For now a good enough explanations is, that it's simply a container for a command. The first parameter here is fairly obvious, the command-string the R backend should evaluate. The second parameter (RCommand::User) is an integer, which can be a bit-wise or-able combination of the values in the RCommand::CommandTypes enum. We'll deal with the more specific values in that enum later. Here, we're just using RCommand::User, which signifies, that the command came directly from the user. This information is fairly important, as it affects the handling of the command at several places: Which color it will be given in the R-command log (currently class RKwatch), whether the command can be cancelled (RCommand::User commands can be cancelled, but some other types of commands can not be cancelled), etc. See the documentation on RCommand::CommandTypes (not yet complete) for more information. \section UsingTheInterfaceToRHandlingReturns A slightly more realistic example: Handling the result of an RCommand Most of the time you don't just want to run a command, but you also want to know the result. Now, this is a tad bit more difficult than one might expect at first glance. The reason for this is that the R backend runs in a separate thread. Hence, whenever you submit a command, it generally does not get executed right away - or at least you just don't know, when exactly it gets executed, and when the result is available. This is necessary, so (expensive) commands running in the backend do not block operations in the GUI/frontend. Ok, so how do you get informed, when your command was completed? Using RCommandReceiver. What you will want to do is inherit the class you want to handle the results of RCommands from RCommandReceiver. When finished, the RCommand will be submitted to the (pure virtual) RCommandReceiver::rCommandDone function, which of course you'll have to implement in a meaningful way in your derived class. The corresponding code would look something like this: \code #include "rkglobals.h" #include "rbackend/rinterface.h" #include "rbackend/rcommandreceiver.h" class MyReceiver : public RCommandReceiver { //... protected: /// receives finished RCommands and processes them void rCommandDone (RCommand *command); //... private: /// does something by submitting an RCommand void someFunction (); //... }; void MyReceiver::someFunction () { RKGlobals::rInterface ()->issueCommand ("print (1+1)", RCommand::App, QString (), this); } void MyReceiver::rCommandDone (RCommand *command) { if (command->successful ()) { qDebug ("Result was %s", command->output ()->utf8 ()); } } \endcode First thing to note is, that this time we're passing two additional parameters to RInterface::issueCommand. The first (or rather third) is really not used as of the time of this writing. What it's meant to become is a short descriptive string attached to the RCommand, that allows the user to make some sense of what this command is all about. The other (fourth) is a pointer to an RCommandReceiver that should be informed of the result. In this case, we'll handle the result in the same object that we issued the command from, so we use "this". So next the RCommand created with RInterface::issueCommand goes on its way to/through the backend (we'll discuss what happens there further down below). Then later, when it has completed its journey, rCommandDone will be called with a pointer to the command as parameter. Now we can use this pointer to retrieve the information we need to know. RCommand::successful and some further simple functions give information about whether the command had any errors and what kind of errors (see RCommand for details). RCommand::output contains the output of the command as a QString (provided the command was successful and had any output). In this case that should be "[1] 2". \section UsingTheInterfaceToRMultipleCommands Dealing with several RCommands in the same object In many cases you don't just want to deal with a single RCommand in an RCommandReceiver, but rather you might submit a bunch of different commands (for instance to find out about several different properties of an object in R-space), and then use some special handling for each of those commands. So the problem is, how to find out, which of your commands you're currently dealing with in rCommandDone. There are several ways to deal with this: - storing the RCommand::id () (each command is automatically assigned a unique id, TODO: do we need this functionality? Maybe remove it for redundancy) - passing appropriate flags to know how to handle the command - keeping the pointer (CAUTION: don't use that pointer except to compare it with the pointer of an incoming command. Commands get deleted when they are finished, and maybe (in the future) if they become obsolete etc. Hence the pointers you keep may be invalid!) To illustrate the option of using "FLAGS", here is a reduced example of how RKVariable updates information about the dimensions and class of the corresponding object in R-space using two different RCommand s: \code #define UPDATE_DIM_COMMAND 1 #define UPDATE_CLASS_COMMAND 2 void RKVariable::updateFromR () { //... RCommand *command = new RCommand ("length (" + getFullName () + ")", RCommand::App | RCommand::Sync | RCommand::GetIntVector, QString (), this, UPDATE_DIM_COMMAND); RKGlobals::rInterface ()->issueCommand (command, RKGlobals::rObjectList()->getUpdateCommandChain ()); } void RKVariable::rCommandDone (RCommand *command) { //... if (command->getFlags () == UPDATE_DIM_COMMAND) { // ... RCommand *ncommand = new RCommand ("class (" + getFullName () + ")", RCommand::App | RCommand::Sync | RCommand::GetStringVector, QString (), this, UPDATE_CLASS_COMMAND); RKGlobals::rInterface ()->issueCommand (ncommand, RKGlobals::rObjectList()->getUpdateCommandChain ()); } else if (command->getFlags () == UPDATE_CLASS_COMMAND) { //... } } \endcode Note that you can freely assign whatever flags you like. Only your own class will need to know how to interpret the flags. Now what about that RKGlobals::rObjectList()->getUpdateCommandChain ()? We'll talk about RCommandChain and what you need it for further down below. But first we'll have a look at how an RCommand is handled internally. \section UsingTheInterfaceToRInternalHandling What happens with an RCommand internally? So far we've discussed RInterface:issueCommand () and RCommandReceiver::rCommandDone (). But what happens in between? First the RCommand is placed in a first-in-first-out stack. This stack is needed, since - as discussed - the commands get executed in a separate thread, so several command may get stacked up, before the first one gets run. Then, in the backend thread (RThread) there is a loop running, which fetches those commands from the stack and executes them one by one. Whenver a command has been executed in this thread, it gets updated with information on any errors that occurred and of course also with the result of running the command. Next, a QCustomEvent is being posted. What this does is - rougly speaking -, transfer the pointer to the command back to the main thread in a safe way. Whenever the main thread becomes active again, it will find that QCustomEvent and handle it in RInterface::customEvent. The most important thing happening there, is a call to RCommand::finished (RCommand::finished basically just calls the responsible RCommandReceiver::rCommandDone), and right after that the RCommand gets deleted. \section UsingTheInterfaceToRThreadingIssues Threading issues The above description sounds simple enough, but there may be some threading issues to keep in mind. Consider what needs to happen when RKVariable is trying to update the information on the corresponding object in R-space (see code example above): - RKVariable will first run a command to determine the dimensionality of the object. Now this is more significant than you may think, as RKVariable is a special kind of RObject, which only handles one-dimensional data. Hence, if you create an object in R with \code myobject <- c (1, 2, 3) \endcode In RKWard an RKVariable will be responsible for "myobject". However, if next, you assign something different to myobject: \code myobject <- data.frame (x=c (1, 2, 3), y=c (2, 3, 4)) \endcode the object "myobject" can no longer be handled by RKVariable, but instead by RContainerObject. What this means practically is that if RKVariable finds out its corresponding object in R-space has more than a single dimension, the RKVariable will have to be deleted and an RContainerObject needs to be created instead. So what's the problem? Consider this hypothetical example: - RKVariable for object "myobject" runs a command to determine the dimensionality of "myobject" - Before that command has finished, the user assigns a data.frame to "myobject" (or deletes the object, or whatever) - The command to determine the dimensionality gets run and returns "1 dimension". RKVariable will assume it knows how to handle the object, and tries to do something with "myobject" which is only applicable for one-dimensional objects (e.g. trying to get the data as a one-dimensional array) - The user command assigning a data.frame gets run - RKVariables command to get the data fails Now, you may argue this does not sound all that likely, and probably it isn't, but the point to see is that there are cases in which the threaded nature of R access can pose some problems. More generally, that is the case, if you want a sequence of commands to run in exactly that order without being disturbed by intervening commands. To cope with this, it is sometimes desirable to keep closer control over the order in which commands get run in the backend. \section UsingTheInterfaceToRCommandChains How to ensure commands get executed in the correct order: RCommandChain The way to do this is to use RCommandChain. Basically, when you want commands to be executed in a sequence being sure that no other commands intervene, you do this: \code RCommandChain *chain = RKGlobals::rInterface ()->startChain (); // create first command RKGlobals::rInterface ()->issueCommand (first_command, chain); // wait for command to return, potentially allows further calls to RInterface::issueCommand () from other places in the code // create second command RKGlobals::rInterface ()->issueCommand (second_command, chain); RKGlobals::rInterface ()->closeChain (chain); \endcode Now the point is that you place both of your commands in a dedicated "chain", telling RKWard that those two commands will have to be run in direct succession. If between the first and the second command, another section of the code issues a different command, this command will never be run until all commands in the chain have been run and the chain has been marked as closed. To illustrate, consider this series of events: \code 1) RCommandChain *chain = RKGlobals::rInterface ()->startChain (); 2) RKGlobals::rInterface ()->issueCommand (first_command, chain); 3) RKGlobals::rInterface ()->issueCommand (some_command); 4) RKGlobals::rInterface ()->issueCommand (second_command, chain); 5) RKGlobals::rInterface ()->issueCommand (some_command2); 6) RKGlobals::rInterface ()->closeChain (chain); \endcode Now let's assume for a second, the R backend has been busy doing other stuff and has not executed any of the commands so far, then the execution stack will now look like this: - top level - chain - first_command - second_command - chain is marked as closed: after executing second_command, we may proceed with the top level - some_command - some_command2 So the order of execution will be guaranteed to be first_command, second_command, some_command, some_command2, although they were issued ina different order. You can also open sub chains, using \code RCommandChain *sub_chain = RKGlobals::rInterface ()->startChain (parent_chain); \endcode Remember to close chains when you placed all the commands you needed to. If you don't close the chain, RKWard will continue to wait for new commands in that chain, and never proceed with commands outside of the chain. \section UsingTheInterfaceToROutputOptions Sending results to the output and retrieving low-level data from the backend There are a few special type-modifiers you can specify when creating an RCommand (as part of the second parameter to RCommand::RCommand or RInterface::issueCommand), that determine what will be done with the result: - RCommand::EmptyCommand This one tells the backend, that the command does not really need to be executed, and does not contain anything. You'll rarely need this flag, but sometimes it is useful to submit an empty command simply to find out when it is finished. - RCommand::DirectToOutput This is typically used in plugins: When you specify this modifier, the plain text result of this command (i.e. whatever R prints out when evaluating the command) will be added to the HTML output file. Remember to call RKWardMainWindow::newOutput in order to refresh the output-window once the command has finished. - RCommand::GetIntVector, RCommand::GetStringVector, RCommand::GetRealVector These are special modifiers helpful when transferring data from R to RKWard (used primarily in the editor classes and in conjunction with RCommand::Sync): They tell the backend to try to fetch the result as an array of int, char*, or double, respectively. For instance, if you know object "myobject" is an integer vector, you may get the data using \code RKGlobals::rInterface ()->issueCommand ("myobject", RCommand::Sync | RCommand::GetIntVector, QString (), this); \endcode Assuming the data can in fact be converted to a vector of integers, you can then access the data using these members in RCommand: - RCommand::intVectorLength (): size of int array - RCommand::getIntVector (): a pointer to the int array. Warning: The array is owned by the RCommand and will be deleted together with the RCommand, so don't just store the pointer - RCommand::detachIntVector (): If you do want to keep the array, use this call to transfer ownership to your class. You are now responsible for freeing up the data yourself. Obviously, whenever trying to transfer data from the backend, this approach is highly superior to parsing the QString RCommand::output (). \section UsingTheInterfaceToRFurtherReading Where to find more detailed information on these topics The following classes contain (or should contain) further important documentation: - \ref RInterface - \ref RCommand - \ref RCommandReceiver - \ref RCommandStack Even lower level API: - \ref RRBackendProtcolFrontend - \ref RBackend - \ref RRBackendProtcolBackend */ #endif rkward-0.6.4/rkward/rbackend/CMakeLists.txt0000644000175000017500000000347012455741221020175 0ustar thomasthomasINCLUDE(FindR.cmake) IF(NOT WIN32) # for pthread_at_fork FIND_PACKAGE(Threads) ENDIF(NOT WIN32) ADD_SUBDIRECTORY( rpackages ) ADD_SUBDIRECTORY( rkwarddevice ) INCLUDE_DIRECTORIES( ${R_INCLUDEDIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${KDE4_INCLUDE_DIR} ${QT_INCLUDES} ) SET ( rbackend_shared_SRCS rkrbackendprotocol_shared.cpp rdata.cpp ) SET ( rbackend_backend_SRCS rkrbackend.cpp rksignalsupport.cpp rklocalesupport.cpp rkrsupport.cpp rkstructuregetter.cpp rkrbackendprotocol_backend.cpp rkreventloop.cpp ) SET ( rbackend_frontend_SRCS rinterface.cpp rcommand.cpp rcommandreceiver.cpp rcommandstack.cpp rkrbackendprotocol_frontend.cpp rksessionvars.cpp ) SET ( rbackend_FRONTEND_SRCS ${rbackend_frontend_SRCS} ${rbackend_shared_SRCS} rkfrontendtransmitter.cpp rktransmitter.cpp ) QT4_AUTOMOC(${rbackend_FRONTEND_SRCS}) ADD_LIBRARY(rbackend STATIC ${rbackend_FRONTEND_SRCS}) TARGET_LINK_LIBRARIES(rbackend rkgraphicsdevice.frontend) SET ( rbackend_BACKEND_SRCS ${rbackend_backend_SRCS} ${rbackend_shared_SRCS} rkbackendtransmitter.cpp rktransmitter.cpp ) QT4_AUTOMOC(${rbackend_BACKEND_SRCS}) ADD_DEFINITIONS (-DRKWARD_SPLIT_PROCESS) LINK_DIRECTORIES(${R_SHAREDLIBDIR}) ADD_EXECUTABLE(rkward.rbackend ${rbackend_BACKEND_SRCS}) TARGET_LINK_LIBRARIES(rkward.rbackend rkgraphicsdevice.backend ${R_USED_LIBS} ${CMAKE_THREAD_LIBS_INIT} ${QT_QTNETWORK_LIBRARY} ${QT_QTCORE_LIBRARY} ${GETTEXT_LIBRARIES} ) IF(WIN32) # on Widows, we install to the rbackend subdirectory, because 1) LIBEXEC_INSTALL_DIR == BIN_INSTALL_DIR and 2) we don't want the backend to pick up # all the KDE library versions, first, when loading DLLs INSTALL(TARGETS rkward.rbackend DESTINATION ${BIN_INSTALL_DIR}/rbackend) ELSE(WIN32) INSTALL(TARGETS rkward.rbackend DESTINATION ${RKWARD_FRONTEND_LOCATION}) ENDIF(WIN32) rkward-0.6.4/rkward/rbackend/rksessionvars.cpp0000664000175000017500000001176112633754364021073 0ustar thomasthomas/*************************************************************************** rksessionvars - description ------------------- begin : Thu Sep 08 2011 copyright : (C) 2011, 2013 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rksessionvars.h" #include "rinterface.h" #include "../settings/rksettingsmoduledebug.h" #include "../settings/rksettingsmodulegeneral.h" #include "../version.h" #include #include #include #include #include #include "../debug.h" RKSessionVars* RKSessionVars::_instance = 0; quint32 RKSessionVars::rkward_version = 0; QString RKSessionVars::rkward_version_suffix; quint32 RKSessionVars::r_version = 0; QString RKSessionVars::r_version_string; RKSessionVars::RKSessionVars (RInterface *parent) : QObject (parent) { RK_TRACE (RBACKEND); RK_ASSERT (!_instance); _instance = this; } RKSessionVars::~RKSessionVars () { RK_TRACE (RBACKEND); } void RKSessionVars::setInstalledPackages (const QStringList &new_list) { RK_TRACE (RBACKEND); installed_packages = new_list; emit (installedPackagesChanged ()); } void RKSessionVars::setRVersion (const QString& version_string) { RK_TRACE (RBACKEND); if (!r_version_string.isEmpty ()) { RK_DEBUG (RBACKEND, DL_WARNING, "R version has changed during runtime, from %s to %s", qPrintable (r_version_string), qPrintable (version_string)); } r_version_string = version_string; r_version = parseVersionString (version_string, 0); } quint32 RKSessionVars::parseVersionString (const QString &version, QString *suffix) { quint32 ret = 0; int pos = -1; int opos = 0; for (int i = 3; i >= 0; --i) { while (1) { ++pos; if (!(pos < version.size () && version[pos].isDigit ())) { int val = version.mid (opos, pos - opos).toInt (); if ((val < 0) || (val > 255) || (pos == opos)) { RK_DEBUG (MISC, DL_ERROR, "Invalid version specification '%s'", qPrintable (version)); if (val > 255) val = 255; else val = 0; } ret += val << (8 * i); if ((pos < version.size ()) && (version[pos] == '.')) { opos = pos + 1; break; } opos = pos; i = -1; break; } } } if (opos <= (version.size () - 1)) { if (suffix) *suffix = version.mid (opos); else RK_DEBUG (MISC, DL_WARNING, "Non numeric portion ('%s') of version specification '%s' will be ignored.", qPrintable (version.mid (opos)), qPrintable (version)); } return ret; } int RKSessionVars::compareRKWardVersion (const QString& version) { if (!rkward_version) { rkward_version = parseVersionString (RKWARD_VERSION, &rkward_version_suffix); } QString suffix; quint32 ver = parseVersionString (version, &suffix); if (ver < rkward_version) return -1; if (ver > rkward_version) return 1; return (suffix.compare (rkward_version_suffix)); } int RKSessionVars::compareRVersion (const QString& version) { if (r_version_string.isEmpty()) return 0; quint32 ver = parseVersionString (version, 0); if (ver < r_version) return -1; if (ver > r_version) return 1; return 0; } QStringList RKSessionVars::frontendSessionInfo () { QStringList lines; lines.append ("RKWard version: " RKWARD_VERSION); lines.append ("KDE version (runtime): " + QString (KDE::versionString ())); lines.append ("KDE version (compile time): " KDE_VERSION_STRING); lines.append ("Qt build key: " + QLibraryInfo::buildKey ()); #if defined Q_WS_WIN lines.append ("Windows runtime version (refer to QSysInfo documentation to translate code into human readable form): 0x" + QString::number (QSysInfo::windowsVersion (), 16)); #elif defined Q_WS_MAC lines.append ("MacOS runtime version (refer to QSysInfo documentation to translate code into human readable form): 0x" + QString::number (QSysInfo::MacintoshVersion, 16)); #endif lines.append ("Local KDE directory: " + KGlobal::dirs ()->localkdedir ()); lines.append ("RKWard storage directory: " + RKSettingsModuleGeneral::filesPath ()); lines.append ("Backend version (as known to the frontend): " + r_version_string); lines.append (QString()); lines.append ("Debug message file (this may contain relevant diagnostic output in case of trouble):"); lines.append (RKSettingsModuleDebug::debug_file->fileName ()); return lines; } #include "rksessionvars.moc" rkward-0.6.4/rkward/rbackend/rpackages/0000755000175000017500000000000012633754364017403 5ustar thomasthomasrkward-0.6.4/rkward/rbackend/rpackages/rkward/0000755000175000017500000000000012633754364020675 5ustar thomasthomasrkward-0.6.4/rkward/rbackend/rpackages/rkward/DESCRIPTION0000664000175000017500000000260712633754364022412 0ustar thomasthomasPackage: rkward Type: Package Title: Provides functions related to the RKWard GUI Author: Thomas Friedrichsmeier and the RKWard Team Maintainer: RKWard-devel mailing list Depends: R (>= 2.9.0),methods Description: This package contains functions which are useful in combination with the RKWard GUI. Many of these functions only needed for the internal communication between RKWard and R, but some are also useful in user scripts. License: GPL (>= 2) Encoding: UTF-8 LazyLoad: yes URL: http://rkward.kde.org Authors@R: c(person(given="Thomas", family="Friedrichsmeier", email="thomas.friedrichsmeier@ruhr-uni-bochum.de", role=c("aut")), person(given="RKWard-devel", family="mailing list", email="rkward-devel@kde.org", role=c("cre","ctb"))) Version: 0.6.3 Date: 2014-10-30 Collate: 'base_overrides.R' 'internal.R' 'internal_debugger.R' 'internal_graphics.R' 'internal_help.R' 'public_graphics.R' 'rk.KDE_GUI-functions.R' 'rk.demo.R' 'rk.edit-functions.R' 'rk.filename-functions.R' 'rk.label-functions.R' 'rk.plugin-functions.R' 'rk.print-functions.R' 'rk.replace.function.R' 'rk.sessionInfo.R' 'rk.sync-functions.R' 'rk.utility-functions.R' 'rk.workspace-functions.R' 'rkward-package.R' 'ver.R' RoxygenNote: 5.0.1 rkward-0.6.4/rkward/rbackend/rpackages/rkward/R/0000755000175000017500000000000012633754364021076 5ustar thomasthomasrkward-0.6.4/rkward/rbackend/rpackages/rkward/R/rk.sessionInfo.R0000644000175000017500000000156412471622105024124 0ustar thomasthomas#' Print information on the RKWard session #' #' Gathers and prints information on the setup of the current RKWard session. #' In general, you should always include this information when reporting a bug #' in RKWard. #' #' Typically, when reporting a bug, you should use \code{Help->Report Bug...} #' from the menu. Internally, this will call \code{rk.sessionInfo()}. #' #' @return Returns the object created by \code{sessionInfo()}, invisibly. Note #' that this includes only the information on the R portion of the session. #' @author Thomas Friedrichsmeier \email{rkward-devel@@kde.org} #' @seealso \code{\link{sessionInfo}} #' @keywords utilities misc #' @export #' @rdname rk.sessionInfo #' @examples #' #' rk.sessionInfo() "rk.sessionInfo" <- function () { cat (.rk.do.plain.call ("getSessionInfo"), sep="\n") cat ("R runtime session info:\n") print (sessionInfo()) } rkward-0.6.4/rkward/rbackend/rpackages/rkward/R/internal_graphics.R0000644000175000017500000001101312455741221024677 0ustar thomasthomas## Internal functions manipulating graphics should be stored here. ## These functions are _not_ supposed to be called by the end user. #' DEPRECATED: \code{rk.screen.device} is obsolete. It simply calls \code{dev.new()} in this version of RKWard. #' #' Depending on your use case, you should use \code{dev.new()}, \code{RK()} or \code{rk.embed.device()}, instead. #' #' @seealso \link{dev.new}, \link{RK}, \link{rk.embed.device} #' #' @export "rk.screen.device" <- function (...) { warning ("rk.screen.device() is obsolete.\nUse one of dev.new(), RK(), or rk.embed.device(), instead.") dev.new (...) } # Fetch the current size of the given RK() device from the frontend, and redraw "RK.resize" <- function (devnum) { .Call ("rk.graphics.device.resize", as.integer (devnum)-1, PACKAGE="(embedding)") } #' @include internal.R assign(".rk.preview.devices", list (), envir=.rk.variables) #' @export ".rk.startPreviewDevice" <- function (x) { a <- .rk.variables$.rk.preview.devices[[x]] if (is.null (a)) { devnum <- dev.cur () rk.without.plot.history (dev.new ()) if (devnum != dev.cur ()) { .rk.variables$.rk.preview.devices[[x]] <- list (devnum=dev.cur(), par=par (no.readonly=TRUE)) } else { return (0L) # no device could be opened } } else { dev.set (a$devnum) par (a$par) } as.integer (dev.cur ()) } #' @export ".rk.killPreviewDevice" <- function (x) { a <- .rk.variables$.rk.preview.devices[[x]] if (!is.null (a)) { if (a$devnum %in% dev.list ()) { dev.off (a$devnum) } .rk.variables$.rk.preview.devices[[x]] <- NULL } } ".rk.discard.preview.device.num" <- function (devnum) { for (dev in names (.rk.variables$.rk.preview.devices)) { if (.rk.variables$.rk.preview.devices[[dev]]$devnum == devnum) { .rk.variables$.rk.preview.devices[[dev]] <- NULL return (invisible (TRUE)) } } invisible (FALSE) } ".rk.list.preview.device.numbers" <- function () { unlist (sapply (.rk.variables$.rk.preview.devices, function (x) x$devnum)) } .rk.variables$.rk.printer.devices <- list () # see .rk.fix.assignmetns () in internal.R #' @export ".rk.fix.assignments.graphics" <- function () { rk.replace.function ("plot.new", as.environment ("package:graphics"), function () { rk.record.plot$.plot.new.hook () eval (body (.rk.backups$plot.new)) }) rk.replace.function ("dev.off", as.environment ("package:grDevices"), function (which = dev.cur ()) { if (getOption ("rk.enable.graphics.history")) rk.record.plot$onDelDevice (devId = which) # see http://thread.gmane.org/gmane.comp.statistics.rkward.devel/802 .rk.do.call ("killDevice", as.character (which)) ret <- eval (body (.rk.backups$dev.off)) printfile <- .rk.variables$.rk.printer.devices[[as.character (which)]] if (!is.null (printfile)) { .rk.do.plain.call ("printPreview", printfile, FALSE) .rk.variables$.rk.printer.devices[[as.character (which)]] <- NULL } rkward:::.rk.discard.preview.device.num(which) return (ret) }) rk.replace.function ("dev.set", as.environment ("package:grDevices"), function () { ret <- eval (body (.rk.backups$dev.set)) if (getOption ("rk.enable.graphics.history") && rk.record.plot$.is.device.managed (which)) rk.record.plot$.set.trellis.last.object (which) ret }) ## set a hook defining "print.function" for lattice: setHook (packageEvent ("lattice", "onLoad"), function (...) lattice::lattice.options (print.function = function (x, ...) { if (dev.cur() == 1) dev.new () ## TODO: use "trellis" instead of "lattice" to accomodate ggplot2 plots? plot_hist_enabled <- getOption ("rk.enable.graphics.history") if (plot_hist_enabled) { rk.record.plot$record (nextplot.pkg = "lattice") } rk.without.plot.history (plot (x, ...)) if (plot_hist_enabled) { rk.record.plot$.save.tlo.in.hP () } invisible () }) ) if (compareVersion (as.character (getRversion ()), "2.14.0") < 0) { setHook (packageEvent ("grid", "attach"), function (...) rk.replace.function ("grid.newpage", as.environment ("package:grid"), function () { ## TODO: add specific support for ggplots? rk.record.plot$.plot.new.hook () ret <- eval (body (.rk.backups$grid.newpage)) }) ) } else { setHook ("before.grid.newpage", function (...) { rk.record.plot$.plot.new.hook () }, action = "append" ) } ## persp does not call plot.new (), so set a hook. Fortunately, the hook is placed after drawing the plot. setHook ("persp", function (...) { rk.record.plot$.plot.new.hook () }, action = "append" ) } rkward-0.6.4/rkward/rbackend/rpackages/rkward/R/rk.utility-functions.R0000644000175000017500000001507312471622105025336 0ustar thomasthomas#' Miscellaneous utility functions #' #' \code{rk.rename.in.container} renames a named object (column/element) in a #' data.frame/list without changing its position. #' #' \code{rk.make.repos.string} just creates a R statement for \code{repos}. A #' typical user should not need to use this function. #' #' \code{rk.select.CRAN.mirror} is an in-house replacement for #' \code{\link{chooseCRANmirror}} without changing \code{options ("repos")}, #' permanently. It uses native KDE gui and provides more information on each #' mirror. #' #' @aliases rk.misc rk.rename.in.container rk.make.repos.string #' rk.select.CRAN.mirror #' @param x a data.frame or list. #' @param old_name a string, the name of the column or element to be renamed. #' @param new_name a string, the new name. #' @param envir an environment where \code{x} is available. #' @return \code{rk.rename.in.container} returns \code{NULL} on successfule #' renaming, otherwise an error. #' #' \code{rk.make.repos.string} returns a valid R expression as a character #' string which can then be parsed and evaluated. #' #' \code{rk.select.CRAN.mirror} returns the URL of the chosen mirror, as a #' string. #' @author Thomas Friedrichsmeier \email{rkward-devel@@kde.org} #' @keywords attribute misc utilities #' @rdname rk.misc #' @examples #' #' ## rk.rename.in.container #' ir <- iris #' str (ir) #' rk.rename.in.container(ir, "Species", "Taxonomic.Group") #' str (ir) #' # renames a named object in a data.frame/list without changing it's position # TODO: create a generic function instead, that can handle all kinds of renames #' @export "rk.rename.in.container" <- function (x, old_name, new_name, envir=parent.frame()) { temp <- (names (x) == old_name) i = 1; for (val in temp) { if (val) { eval (substitute (names (x)[i] <- new_name), envir=envir) return () } i = i+1; } error ("Could not find column with given name") } #' @export #' @rdname rk.misc "rk.make.repos.string" <- function () { x <- getOption ("repos") len <- length (x) ret <- sprintf ("c (") first <- TRUE for (i in 1:len) { if (first) { first <- FALSE } else { ret <- sprintf ("%s, ", ret) } if (!(is.null (names (x)) || (names (x)[i] == ""))) { ret <- sprintf ("%s%s=\"%s\"", ret, names (x)[i], x[i]) } else { ret <- sprintf ("%s\"%s\"", ret, x[i]) } } ret <- sprintf ("%s)", ret) ret } # a wrapper around chooseCRANmirror() without changing options ("repos"), permanently #' @export #' @rdname rk.misc "rk.select.CRAN.mirror" <- function () { old_repos <- getOption("repos") on.exit (options (repos=old_repos)) if (!interactive()) stop("cannot choose a CRAN mirror non-interactively") m <- getCRANmirrors(all = FALSE, local.only = FALSE) res <- menu (paste(m[, 1L], m[, 5L], sep = " - "), getOption("menu.graphics"), "CRAN mirror") if (res > 0L) { URL <- m[res, "URL"] repos <- getOption("repos") repos["CRAN"] <- gsub("/$", "", URL[1L]) options(repos = repos) } return (as.character (getOption ("repos")["CRAN"])) } #' Slightly smarter variant of old.packages() #' #' For most purposes, this function is identical to old.packages(). However, if the same #' package is installed to different locations, in different versions, old.packages() will #' treat each of these installations separately. Thus, e.g. if lib.loc == c("A", "B") and #' package X is installed in B at an outdated version 0.1, but in A at the most recent version 0.2, #' old.packages() will report package X at B as old. In contrast rk.old.packages() will recognize #' that the current version is higher up in the path, and not report package X as old. #' #' @return a character vector of packages which are really old #' #' @author Thomas Friedrichsmeier \email{rkward-devel@@kde.org} #' @keywords attribute misc utilities #' @rdname rk.old.packages #' @examples #' #' ## NOT RUN #' rk.old.packages() #' #' @export "rk.old.packages" <- function (lib.loc = NULL, repos = getOption("repos"), contriburl = contrib.url(repos, type), instPkgs = installed.packages(lib.loc = lib.loc), method, available = NULL, checkBuilt = FALSE, type = getOption("pkgType")) { if (is.null (lib.loc)) lib.loc <- .libPaths () if (is.null (available)) available <- available.packages (contriburl=contriburl, method=method) seen.packages <- character (0) old <- NULL for (l in lib.loc) { # check packages in one location at a time inst <- instPkgs[instPkgs[,"LibPath"] == l, , drop=FALSE] old <- rbind (old, old.packages (lib.loc=l, repos=repos, contriburl=contriburl, instPkgs=inst, method=method, available=available, checkBuilt=checkBuilt, type=type)) # and discard any which are masked, before looking at further locations seen.packages <- c (seen.packages, inst[, "Package"]) instPkgs <- instPkgs[!(instPkgs[, "Package"] %in% seen.packages), , drop=FALSE] } old } #' Start recording commands that are submitted from RKWard to R #' #' To stop recording, supply NULL or "" as filename. #' Currently used for the purpose of automated testing, only. Perhaps in the future #' this or a similar mechanism could also be added as a user feature. #' #' @param filename filename to write to (file will be truncated!). #' @param include.all By default, some types of command are filtered (internal synchronisation commands, and run again links). Should these be included? #' @export #' @rdname rk.record.commands "rk.record.commands" <- function (filename, include.all = FALSE) { if (is.null (filename)) filename = "" res <- .rk.do.plain.call ("recordCommands", c(as.character (filename), if (include.all) "include.all" else "normal")) if (!length (res)) invisible (TRUE) else { warning (res) invisible (FALSE) } } #' Switch language / translation to use in the frontend. #' #' This feature is mostly intended for the purpose of automated testing, which needs a #' defined language to work. It might also be useful for translators, or e.g. to look up #' some terms untranslated in special cases where you are more familiar with the English terms than #' your native language terms. Note that this will only strings that are translated after the call., only those which get #' translated after the call. Most new dialogs you open, and importantly new plugin dialogs should #' show strings in the new lanuage, however. #' #' To change the language in the backend, use \code{Sys.setenv(LANGUAGE=...)} or \code{Sys.setlocale()}. #' #' @param LANG language code to use. "C" for no translation, i.e. generally English #' #' @export #' @rdname rk.switch.frontend.language "rk.switch.frontend.language" <- function (LANG="C") { .rk.do.plain.call ("switchLanguage", as.character (LANG)) } rkward-0.6.4/rkward/rbackend/rpackages/rkward/R/rk.plugin-functions.R0000644000175000017500000002031612471622105025125 0ustar thomasthomas#' Call built-in RKWard plugin(s) #' #' \code{rk.call.plugin} provides a high level wrapper to call any plugin #' available in RKWard. The exact string to be used as \code{plugin}, and the #' list of arguments available for a particular plugin, are generally not #' transparent to the user. \code{rk.list.plugins} can be used to obtain a list #' of current plugins. For plugin arguments, it is recommended to run the #' plugin, and inspect the "Run again" link that is generated on the output. #' #' \bold{Warning}: Using \code{rk.call.plugin}, especially with submit.modes #' \code{"auto"} or \code{"submit"} to program a sequence of analyses has #' important drawbacks. First, the semantics of plugins are not guaranteed to #' remain unchanged across different versions of RKWard, thus your code may #' stop working after an upgrade. Second, your code will not be usable outside #' of an RKWard session. Consider copying the generated code for each plugin, #' instead. The primary use-cases for \code{rk.call.plugin} are automated #' tests, cross-references, and scripted tutorials. #' #' \bold{Note}: Even when using \code{"submit.mode=submit"}, the plugin code is #' run in the global context. Any local variables of the calling context are #' not available to the plugin. #' #' @param plugin character string, giving the name of the plugin to call. See #' Details. #' @param \dots arguments passed to the \code{plugin} #' @param submit.mode character string, specifying the submission mode: #' \code{"manual"} will open the plugin GUI and leave it to the user to #' submit it manually, \code{"auto"} will try to submit the plugin, if it can #' be submitted with the current settings (i.e. if the "Submit"-button is #' enabled after applying all specified parameters). If the plugin cannot be #' submitted, with the current settings, it will behave like \code{"manual"}. #' \code{"submit"} is like \code{"auot"}, but will close the plugin, and #' generate an error, if it cannot be submitted. \code{"manual"} will always #' return immediately, \code{"auto"} may or may not return immediately, and #' \code{"submit"} will always wait until the plugin has been run, or produce #' an error. #' @return \code{rk.call.plugin} returns \code{TRUE} invisibly. #' #' @author Thomas Friedrichsmeier \email{rkward-devel@@kde.org} #' @seealso \code{\link{rk.results}}, \url{rkward://page/rkward_output} #' @keywords utilities #' @examples #' #' ## "t_test_two_vars" plugin: #' ## see the output: Windows->Show Output #' local({ #' x1 <- rnorm (100) #' x2 <- rnorm (100, 2) #' #' rk.call.plugin ("rkward::t_test_two_vars", #' confint.state="1", conflevel.real="0.95", hypothesis.string="greater", paired.state="0", varequal.state="0", #' x.available="x1", y.available="x2", #' submit.mode="submit") #' }) #' #' @export #' @rdname rk.call.plugin "rk.call.plugin" <- function (plugin, ..., submit.mode = c ("manual", "auto", "submit")) { # prepare arguments settings <- list (...) callstrings <- list () callstrings[1] <- plugin callstrings[2] <- match.arg (submit.mode) if (length (settings) > 0) { for (i in 1:length(settings)) { # properly passing on escaped characters is a pain. This seems to work. deparsed <- deparse (settings[[i]]) deparsed_unquoted <- substr (deparsed, 2, nchar (deparsed) - 1) callstrings[i + 2] <- paste(names(settings)[i], deparsed_unquoted, sep = "=") } } # do call res <- .rk.do.call ("doPlugin", callstrings) # handle result if (!is.null (res)) { if (res$type == "warning") { warning (res$message) } else { stop (res$message) } } invisible (TRUE) } #' (Re-)load the given pluginmap files into the RKWard GUI #' #' @param pluginmap.files a character vector of file names to add. This may be left empty, #' if the only desired effect is to reload all active pluginmaps. #' @param force.add logical. Whether the pluginmap files should also be added, if they had #' been previously de-selected in the settings menu, and regardless of their #' priority setting. In scripted usage, this should generally be set to FALSE. #' @param force.reload logical. By default the active pluginmaps are reloaded, only if any new ones #' were added. If set to TRUE, pluginmaps are reloaded in any case. In #' scripted usage, this should generally be set to FALSE. NOTE: Since #' a reload always means reloading _all_ active pluginmaps, This may be #' slow, and should be used with care. #' #' \bold{Note}: It is not necessary to reload the pluginmap, in order to refresh an individual #' plugin (e.g. after modifying the dialog), as plugins are not kept in memory after closing. #' Any currently opened plugins are not affected by this function. #' #' @author Thomas Friedrichsmeier \email{rkward-devel@@kde.org} #' @seealso \code{\link{rk.call.plugin}}, @seealso \code{\link{rkwarddev::rk.plugin.skeleton}} #' @keywords utilities #' #' @examples #' #' ## NOT RUN #' #' ## reload all active pluginmaps #' rk.load.pluginmaps() #' #' ## END NOT RUN #' @export #' @rdname rk.load.pluginmaps "rk.load.pluginmaps" <- function (pluginmap.files=NULL, force.add = TRUE, force.reload = TRUE) { .rk.do.plain.call ("loadPluginMaps", c (ifelse (isTRUE (force.add), "force", "noforce"), ifelse (isTRUE (force.reload), "reload", "noreload"), as.character (pluginmap.files)), synchronous=FALSE) } #' List of modify loaded plugins #' #' \code{rk.list.plugins} returns the a list of all currently #' registered plugins (in loaded pluginmaps). #' \code{rk.set.plugin.status} allows to control the status of the given plugin(s). Currently, #' only visibility can be controlled. #' #' @param id vector of ids (character) of the plugins to modify #' @param context in which the plugin should be shown / hidden. This can either be "", #' meaning the plugin will be affected in all contexts it occurs in, or a character vector #' of the same length as id. #' @param visible logical, controlling whether the plugin should be shown (\code{TRUE}) or #' hidden (\code{FALSE}). Hidden plugins are essentially removed from the menu. They may still #' be accessible embedded into other plugins. #' #' @return \code{rk.list.plugins} returns a data.frame listing plugin ids, context, menu path #' (tab-separated), and label of the plugin. If a plugin is available in more #' than one context, it will be listed several times. The exact layout (number and order of columns) #' of this data.frame might be subject to change. However, the \bold{names} of the columns in the #' returned data.frame are expected to remain stable. #' \code{rk.set.plugin.status} returns \code{NULL}, invisibly #' #' \bold{Note}: Each call to \code{rk.set.plugin.status} will result in a complete rebuild of the #' menu (in the current implementation). While this should be hardly noticeable in interactive #' use, it could be an issue when changing the status of many plugins, programatically. #' In this case, make sure to do all changes in \bold{one} call to \code{rk.set.plugin.status}, #' rather than many separate calls. #' #' @author Thomas Friedrichsmeier \email{rkward-devel@@kde.org} #' @keywords utilities #' #' @seealso \code{\link{rk.call.plugin}} for invoking a plugin, programatically #' #' @examples #' ## list all current plugins #' rk.list.plugins () #' #' ## NOT RUN #' ## hide t.test plugin #' rk.set.plugin.status ("rkward::t_test", visible=FALSE) #' ## END NOT RUN #' #' @export #' @rdname rk.list.plugins #' @aliases rk.list.plugins rk.set.plugin.status "rk.list.plugins" <- function () { plugs <- .rk.do.plain.call("listPlugins") columns = c ("ID", "Context", "Menupath", "Label") as.data.frame (matrix (plugs, ncol=length (columns), byrow=TRUE, dimnames=list (1:(length (plugs)/length (columns)), columns)), stringsAsFactors=FALSE) } #' @export #' @rdname rk.list.plugins "rk.set.plugin.status" <- function (id, context="", visible=TRUE) { id <- as.character (id) context <- rep (as.character (context), length.out=length (id)) visible <- rep (as.character (as.numeric (visible)), length.out=length (id)) .rk.do.plain.call ("setPluginStatus", c (id, context, visible)) invisible (NULL) } rkward-0.6.4/rkward/rbackend/rpackages/rkward/R/rk.replace.function.R0000644000175000017500000000673612471622105025072 0ustar thomasthomas#' Replace a function inside its package environment / namespace #' #' \code{rk.replace.function} can be used to replace a function inside a #' different package / namespace. It is mainly intended for internal usage #' inside rkward, e.g. to replace \code{menu} and \code{select.list} with #' appropriate GUI implementations. #' #' The original function is assigned to the environment #' \code{rkward::.rk.backups} with the same name as the original, and can be #' referred to from the replacement. WARNING: This mechanism does not support #' several subsequent replacments of the same function, nor does it support #' replacement of generics. #' #' \bold{WARNING}: This function can be used to alter - and disrupt - internal #' functions in arbitrary ways. You better know what you are doing. #' #' \bold{WARNING}: Does not work well on generics! #' #' @param functionname name of the function to be replaced (character). #' @param environment package environment or namespace, where replacment should #' be done. #' @param replacement the replacement. This should be a function. #' @param copy.formals logical; whether to copy the \code{\link{formals}} from #' the original function. #' @return Returns \code{NULL}, invisibly, unconditionally. #' @author Thomas Friedrichsmeier \email{rkward-devel@@kde.org} #' @seealso \code{\link{assignInNamespace}}, \code{\link{debug}} #' @keywords utilities IO #' @export #' @rdname rk.replace.function #' @examples #' #' ## Not run #' rk.replace.function ("history", as.environment ("package:utils"), #' function () { #' cat ("This is what you typed:\n") #' eval (body (.rk.backups$history)) #' }) #' ## End not run #' # Tries to replace a function inside its environemnt/namespace. # Function formals are copied from the original. # A backup of the original is stored as rkward::.rk.backups$FUNCTIONNAME "rk.replace.function" <- function (functionname, environment, replacement, copy.formals=TRUE) { # This is a stripped down copy of utils::assignInNamespace, without the restrictions # added which would prevent us from properly replacing e.g. utils::menu, # but also without some of the fine points for replacing while loading a namespace, # and for handling S3 methods. doAssignInNamespace <- function(x, value, ns, pos = -1, envir = as.environment(pos)) { if (missing(ns)) { nm <- attr(envir, "name", exact = TRUE) if(is.null(nm) || substring(nm, 1L, 8L) != "package:") stop("environment specified is not a package") ns <- asNamespace(substring(nm, 9L)) } else ns <- asNamespace(ns) if (bindingIsLocked(x, ns)) { unlockBinding(x, ns) assign(x, value, envir = ns, inherits = FALSE) w <- options("warn") on.exit(options(w)) options(warn = -1) lockBinding(x, ns) } else { assign(x, value, envir = ns, inherits = FALSE) } } original <- get (functionname, envir=environment, inherits=FALSE) # create a backup assign (functionname, original, envir=.rk.backups) if (copy.formals) formals (replacement) <- formals (original) environment (replacement) <- environment (original) try ( if (bindingIsLocked (functionname, environment)) { unlockBinding (functionname, environment) on.exit (lockBinding (functionname, environment)) } ) try ( if (isNamespace (environment)) { doAssignInNamespace (functionname, replacement, ns=environment) } else { doAssignInNamespace (functionname, replacement, envir=environment) } ) try ( assign (functionname, replacement, envir=environment) ) invisible (NULL) } rkward-0.6.4/rkward/rbackend/rpackages/rkward/R/public_graphics.R0000644000175000017500000012410412471622105024344 0ustar thomasthomas## Public functions manipulating "graphics" should be stored here. ## These functions are accessible to the user. #' Plot graphics to HTML output file #' #' \code{rk.graph.on()} creates an R device that will plot to the output html page in RKWard (\url{rkward://page/rkward_output}). #' The default settings for \code{device.type}, \code{width}, \code{height}, and \code{quality} can be modified from Settings -> Configure RKWard -> Output. #' #' @param device.type Type of device to create / graphics format to use. Currently, supported devices are "PNG", "SVG", or "JPG". #' The default is to use the format configured in Settings -> Configure RKWard -> Output. #' @param width Width of graphics in pixels. The default is to use the width configured in Settings -> Configure RKWard -> Output. #' @param height Height of graphics in pixels. The default is to use the heigth configured in Settings -> Configure RKWard -> Output. #' @param quality For device.type "JPG", quality of the JPG file from 0 to 100. #' The default is to use the quality configured in Settings -> Configure RKWard -> Output. #' @param ... Further options will be passed to the graphics device used internally. #' #' \bold{Warning}: It is advised to use \code{rk.graph.off} and \bold{not} \code{dev.off} to close the device opened by #' \code{rk.graph.on}. \code{dev.print(device = rk.graph.on)} is a \bold{wrong} usage for this "device," and will result in errors. #' #' @author Thomas Friedrichsmeier \email{rkward-devel@@kde.org} #' #' @seealso \link{rk.results} \link{rk.print} \link{rk.get.output.html.file} \link{dev.off} \link{svg} \link{png} \link{jpg} #' #' @examples #' require (rkward) #' #' ## Plot directly to the output (html) file, by-passing screen device: #' rk.graph.on ("JPG", 480, 480, 75) #' plot (rnorm (100)) #' rk.graph.off () #' #' ## Copy the displayed plot to the output: #' plot (rnorm (100)) #' dev.copy (device = rk.graph.on) #' rk.graph.off () #' #' ## WRONG USAGE: not run: #' #plot (rnorm (100)) #' #dev.print (device = rk.graph.on) #' #' @keywords devices #' #' @export #' @aliases rk.graph.on rk.graph.off #' @rdname rk.graph.on "rk.graph.on" <- function (device.type=getOption ("rk.graphics.type"), width=getOption ("rk.graphics.width"), height=getOption ("rk.graphics.height"), quality, ...) { if (!is.numeric (width)) width <- 480 if (!is.numeric (height)) height <- 480 if (is.null (device.type)) device.type <- "PNG" # default behavior is PNG for now assign (".rk.active.device", dev.cur (), .rk.variables) ret <- NULL if (device.type == "PNG") { filename <- rk.get.tempfile.name(prefix = "graph", extension = ".png") ret <- png(filename = file.path(filename), width = width, height = height, ...) .rk.cat.output(paste("
", sep = "")) } else if (device.type == "JPG") { if (missing (quality)) { quality = getOption ("rk.graphics.jpg.quality") # COMPAT: getOption (x, *default*) not yet available in R 2.9 if (is.null (quality)) quality = 75 } filename <- rk.get.tempfile.name(prefix = "graph", extension = ".jpg") ret <- jpeg(filename = file.path(filename), width = width, height = height, "quality"=quality, ...) .rk.cat.output(paste("
", sep = "")) } else if (device.type == "SVG") { if (!capabilities ("cairo")) { # cairo support is not always compiled in require (cairoDevice) svg <- Cairo_svg } filename <- rk.get.tempfile.name(prefix = "graph", extension = ".svg") ret <- svg(filename = file.path(filename), ...) .rk.cat.output(paste("\n", sep = "")) .rk.cat.output(paste("\n", sep = "")) .rk.cat.output(paste("This browser appears incapable of displaying SVG object. The SVG source is at:", filename)) .rk.cat.output("") } else { stop (paste ("Device type \"", device.type, "\" is unknown to RKWard", sep="")) } invisible (ret) } #' \code{rk.graph.off()} closes the device that was opened by \code{rk.graph.on}. #' #' @rdname rk.graph.on #' @export "rk.graph.off" <- function(){ .rk.cat.output ("\n") # so the output will be auto-refreshed ret <- dev.off() # dev.off () sets dev.next () as active, which may not have been active before rk.graph.on was called; # so reset the correct device as active: i <- get (".rk.active.device", .rk.variables) if ((!is.null (i)) && (i %in% dev.list ())) ret <- dev.set (i) ret } #' Plot graphics to RKWard native device #' #' \code{RK()} creates an R on-screen device that will be rendered in the RKWard frontend. #' The default settings for \code{width}, and \code{height} can be modified from Settings -> Configure RKWard -> Onscreen Graphics. #' #' @param width Width of the device in inches. The default is to use the heigth configured in Settings -> Configure RKWard -> Onscreen Graphics. #' @param height Height of the device in inchesgraphics in pixels. The default is to use the heigth configured in Settings -> Configure RKWard -> Onscreen Graphics. #' @param pointsize Default pointsize #' @param family Default font family. This can be a character vector of length 1 or 2. The second value is used for #' plotting symbols. Effectively the default is c("Helvetica", "Symbol"). A wide variety of sepcification is supported, #' including the device independent fonts names "sans", "serif", and "mono" #' @param bg Background color. #' @param title Window title. #' @param antialias Antialiasing. Can be turned off for somewhat faster drawing. #' #' @keywords devices #' #' @export #' @aliases RK #' @rdname RKdevice "RK" <- function (width=getOption("rk.screendevice.width"), height=getOption("rk.screendevice.height"), pointsize=12, family=NULL, bg="white", title="", antialias=TRUE) { if (is.null (width)) width <- 7 if (is.null (height)) height <- 7 ret <- .Call ("rk.graphics.device", as.integer (width), as.integer (height), as.integer (pointsize), family, bg, title, isTRUE (antialias), PACKAGE="(embedding)") rk.record.plot$onAddDevice (dev.cur ()) invisible (ret) # Current always NULL } #' Embed non-RKWard device windows #' #' \code{rk.embed.device} evaluates the given expression, and if this has created a window on the screen, tries to embed it as an RKWard window. #' #' @param expr Expression to evaluate. #' #' @details Theoretically, \code{expr} can be any valid R expression. However typically this should be calls to X11(), Windows(), or, perhaps dev.copy(). #' Importantly, the expression should create exactly one new window for \code{rk.embed.device()} to work. Keep in mind, that this is not #' always the case for \code{plot(...)} and similar commands, which will re-use an existing plot window, if available. #' #' @note \code{rk.embed.device()} will not work on all platforms (most importantly, not in most MacOSX binaries). Further, note that a captured #' \code{X11()} or \code{Windows} device may look similar to an \code{RK()} device, but is actually a very different thing. Capturing a #' window already "owned" by RKWard (importantly, \code{RK()} device windows) may lead to unexpected results, including crashes. #' #' @seealso \link{RK} #' @examples #' #' ## Not run: #' rk.embed.device (grDevices::X11(title="X11 device window")) #' plot (rnorm (10)) #' #' @export "rk.embed.device" <- function (expr) { oldd <- dev.cur () .rk.do.call ("startOpenX11", as.character (oldd)); on.exit (.rk.do.call ("endOpenX11", as.character (dev.cur()))); x <- eval.parent (expr) if (oldd != dev.cur ()) on.exit (rk.record.plot$onAddDevice (), add=TRUE) else warning ("No device appears to have been created (dev.cur() has not changed)"); invisible (x) } # Internal function to create wrapper around an R device function (used for X11(), windows(), and quartz()). ".rk.make.device.wrapper" <- function (devicename) { ret <- eval (substitute ( function (width=getOption("rk.screendevice.width"), height=getOption("rk.screendevice.height"), pointsize=12) { rk.mode <- getOption ("rk.override.platform.devices") if (identical (rk.mode, "replace") || !exists (devicename, envir=asNamespace ("grDevices"), inherits=FALSE)) { if (!identical (rk.mode, "replace")) { warning (paste (devicename, "()-device is not available. Falling back to RK()")) } args <- list () if (exists ("width", inherits=FALSE) && !missing (width)) args$width <- width if (exists ("height", inherits=FALSE) && !missing (height)) args$height <- height if (exists ("pointsize", inherits=FALSE) && !missing (pointsize)) args$pointsize <- pointsize if (exists ("bg", inherits=FALSE) && !missing (bg)) args$bg <- bg if (exists ("title", inherits=FALSE) && !missing (title)) args$title <- title if (exists ("antialias", inherits=FALSE) && !missing (antialias)) args$antialias <- !identical (antialias, "none") do.call (rkward::RK, args) } else if (identical (rk.mode, "embed")) { if (missing (width)) width <- getOption ("rk.screendevice.width") if (!is.numeric (width)) width <- 7 if (missing (height)) height <- getOption ("rk.screendevice.height") if (!is.numeric (height)) height <- 7 rk.embed.device (eval (body (grDevices::devicename))) } else { eval (body (grDevices::devicename)) } } )) if (exists (devicename, envir=asNamespace ("grDevices"), inherits=FALSE)) { devfun <- get (devicename, asNamespace ("grDevices")) formals (ret) <- formals (devfun) environment (ret) <- environment (devfun) } ret } ## NOTE: Adding an Rd-page for these makes "?X11" fail (R 3.0.0). # Overrides for platform specific R plotting devices # # These functions override the platform specific on-screen plotting devices by the same names. # The exact behavior depends on configuration settings, and can be one of: The original R device, # the original R device embedded using \code{rk.embed.device()}, or the call can be re-directed # to the \code{RK()} device. In the last case not all function arguments may be honored. # # @note If you want to use the \link{RK} device, you should call that, explicitly. These # overrides are provided to make it easy to use scripts that refer to the platform specific # plotting devices provided by R. # # @seealso \link{RK} \link{rk.embed.device} \link[grDevices]{X11} \link[grDevices]{Windows} # # @rdname DeviceOverrides #' @export "X11" <- .rk.make.device.wrapper ("X11") #' @export "x11" <- X11 #' @export "windows" <- .rk.make.device.wrapper ("windows") #' @export "win.graph" <- .rk.make.device.wrapper ("win.graph") # NOTE: Has different formals() than windows() #' @export "quartz" <- .rk.make.device.wrapper ("quartz") #' Device for printing using the KDE print dialog #' #' Creates a device operating on a temporary file (internally a #' \code{\link{postscript}}() device). When the device is closed, it is #' printed, automatically, using the KDE print dialog (if installed). #' #' Typically this device is used with \code{\link{dev.print}}, as shown in the #' example, below. #' #' @param ... arguments are passed to \code{\link{postscript}} #' @return Returns the name of the underlying temporary file, invisibly. #' @author Thomas Friedrichsmeier \email{rkward-devel@@kde.org} #' @seealso \code{\link{postscript}}, \code{\link{dev.print}}, #' \code{\link{rk.graph.on}} #' @keywords utilities device #' @rdname rk.printer.device #' @export #' @examples #' #' ## Not run: #' plot (rnorm (10)) #' dev.print (rk.printer.device) #' # Produces a temporary postscript file and opens a print dialog for it # Parameters are passed to postscript(), but typically this is simply used as # dev.print(rk.print.preview) "rk.printer.device" <- function(...) { tf <- paste (tempfile (), "ps", sep=".") # NOTE: Up to R 2.12.x, tempfile() did not have "fileext" parameter postscript (file = tf, ...) .rk.variables$.rk.printer.devices[[as.character (dev.cur ())]] <- tf } #' @export "rk.duplicate.device" <- function (devId = dev.cur ()) { rk.record.plot$duplicating.from.device <- devId on.exit (rk.record.plot$duplicating.from.device <- 1) # NULL device dev.set (devId) dev.copy (device = dev.new) } # A global history of various graphics calls; #' @export "rk.record.plot" <- function () { env <- environment() # .sP.index is used maintain an index of the history using Sys.time. This will help # when "insert"ing a plot into history is implemented. We then have to shift around # only .sP.index and not the whole "savedPlots" list .sP.index <- list () sP.length <- length (.sP.index) .set.sP.length <- function () sP.length <<- length (.sP.index) # template for every element of savedPlots; tlo.ls is ("lattice.status") used only for lattice plots .sP.template <- list (plot = NULL, tlo.ls = NULL, pkg = "", time = NULL, call = NULL) # this is the main list which stores all the history; the list is tagged by Sys.time savedPlots <- list () # length (savedPlots) should always be == length (.sP.index) == sP.length # used for temporarily storing the plots before they are pushed into savedPlots: .unsavedPlot <- list (plot = NULL, tlo.ls = NULL, pkg = NA_character_, is.os = NA, tryerr = NA) # template for every element of histPositions; tlo.ls ("lattice.status") is used only for lattice plots .hP.template <- list (is.this.plot.new = FALSE, is.this.dev.new = TRUE, pos.cur = NA_integer_, pos.prev = NA_integer_, pos.dupfrom = NA_integer_, pkg = "", call = NA_character_, plot = NA, tlo.ls = NA) # this list stores the details for currently displayed plots on the devices; tagged by device number histPositions <- list ("1" = .hP.template) .ss.used <- FALSE # split.screen variable .pop.notify <- TRUE # used when hist limit is reached .cll <- 50 # no. of characters used in the "goto plot" drop down list .set.call.lab.len <- function (x) .cll <<- x ## Generic functions: .get.sys.time <- function () format (Sys.time (), "%Y%m%d%H%M%OS3") .is.device.managed <- function (devId) as.character (devId) %in% names(histPositions)[-1] .set.trellis.last.object <- function (devId = dev.cur ()) { # called only from dev.set (); this appropriately sets the "lattice.status" # list so that trellis.last.object () can retrieve the correct variables if (!.is.device.managed (devId)) return (invisible ()) devId <- as.character (devId) if (histPositions[[devId]]$pkg != "lattice") return (invisible ()) if (histPositions[[devId]]$is.this.plot.new) tlo.ls <- histPositions[[devId]]$tlo.ls else tlo.ls <- savedPlots [[.sP.index [[histPositions[[devId]]$pos.cur]]]]$tlo.ls assign ("lattice.status", tlo.ls, envir = lattice:::.LatticeEnv) } .is.par.or.screen.inuse <- function () { # takes care of par (mfrow / mfcol) and split.screen () issues "almost!" ret <- FALSE if (sum (par ("mfg") * c(-1,-1,1,1)) != 0) ret <- TRUE else if (graphics:::.SSexists ("sp.screens")) { if (!.ss.used) .ss.used <<- TRUE else ret <- TRUE } else .ss.used <<- FALSE ret } ## Device specific functions: onAddDevice <- function (devId = dev.cur ()) { if (!isTRUE (getOption ("rk.enable.graphics.history"))) return (invisible ()) devId.from <- as.character (env$duplicating.from.device) devId <- as.character (devId) histPositions [[devId]] <<- .hP.template if ((env$duplicating.from.device > 1) && !histPositions [[devId.from]]$is.this.dev.new) { # devId.from > 1 ## TODO: see if so many "[[" calls can be reduced? histPositions [[devId]]$is.this.plot.new <<- TRUE histPositions [[devId]]$is.this.dev.new <<- FALSE histPositions [[devId]]$pkg <<- histPositions [[devId.from]]$pkg histPositions [[devId]]$call <<- histPositions [[devId.from]]$call if (!histPositions [[devId.from]]$is.this.plot.new) histPositions [[devId]]$pos.dupfrom <<- histPositions [[devId.from]]$pos.cur histPositions [[devId]]$plot <<- histPositions [[devId.from]]$plot histPositions [[devId]]$tlo.ls <<- histPositions [[devId.from]]$tlo.ls } .rk.update.hist.actions () invisible () } initialize.histPositions <- function () { # this is called from rk.toggle.plot.history (); # when plot history is re-enabled, this initializes device specific lists so that the displayed # plots can be recorded at the next appropriate action on.exit (.rk.update.hist.actions (enable.plot.hist = TRUE)) # all open screen devices .osd <- which (names (dev.list ()) %in% deviceIsInteractive ()) + 1 .opd <- .rk.list.preview.device.numbers() # to be managed devices: if (length (.opd) > 0) .osd <-.osd [!(.osd %in% .opd)] if (length (.osd) == 0) return (invisible ()) d.cur <- dev.cur () histPositions <<- list ("1" = .hP.template) for (d in as.character (.osd)) { .rk.backups$dev.set (as.numeric (d)) if (is.null (recordPlot ()[[1]])) # empty device histPositions [[d]] <<- .hP.template else histPositions [[d]] <<- modifyList(.hP.template, list (is.this.plot.new = TRUE, is.this.dev.new = FALSE, pkg = "unknown")) } .rk.backups$dev.set (d.cur) } onDelDevice <- function (devId = dev.cur()) { devId <- as.character (devId) if (!(devId %in% names(histPositions)[-1])) return (invisible ()) ## TODO: ask for confirmation before appending a plot record (devId, action = "dev.off") histPositions [[devId]] <<- NULL invisible () } flushout.histPositions <- function () { # this is called from rk.toggle.plot.history () # when plot history is disabled, this records any unsaved plots on the devices and # cleans out the device specific lists # save any unsaved plots and "close" the device w/o actually closing the window: for (d in names(histPositions)) record (devId = d, action = "dev.off") .rk.update.hist.actions (enable.plot.hist = FALSE) histPositions <<- list ("1" = .hP.template) } .save.tlo.in.hP <- function (devId = dev.cur ()) { if (!.is.device.managed (devId)) return (invisible ()) # tlo = trellis.last.object # when there are multiple devices showing the same lattice plot in the history, we need to # store the "lattice.status" into each device specific list, so that, if/when removing # one of the displayed plots, the other can still be re-added back in the history. devId <- as.character (devId) histPositions [[devId]]$plot <<- trellis.last.object () histPositions [[devId]]$tlo.ls <<- get ("lattice.status", envir = lattice:::.LatticeEnv) invisible () } .prep.new.device <- function (devId, pkg, .cstr) { histPositions [[devId]]$is.this.dev.new <<- FALSE histPositions [[devId]]$is.this.plot.new <<- TRUE histPositions [[devId]]$pkg <<- pkg histPositions [[devId]]$call <<- .cstr invisible () } ## Recording functions record <- function(devId = dev.cur (), isManaged = NULL, action = "", callUHA = TRUE, nextplot.pkg = "", nextplot.call = NA_character_) { # callUHA is not really utilized, but there to provide a flixibility to not call # .rk.update.hist.action () when not needed devId <- as.character (devId) if (is.null (isManaged)) isManaged <- .is.device.managed (devId) if (!isManaged) return (invisible ()) if (histPositions[[devId]]$is.this.dev.new) { # a new device: ie after either an "x11 ()" call or a "dev.copy (device = x11)" call if (action == "") return (invisible (.prep.new.device (devId, nextplot.pkg, nextplot.call))) # call from plot.new () / persp () / print.trellis () else if (action == "force.append") return (invisible (rk.show.message ("Nothing to record!", "Record Warning", FALSE))) # call from rk.force.append.plot else return (invisible ()) # if needed, handle individual actions separately } newplot.in.Q <- nextplot.pkg != "" if (action == "force.append") { histPositions[[devId]]$is.this.plot.new <<- TRUE histPositions[[devId]]$pkg <<- "unknown" histPositions[[devId]]$call <<- NA_character_ } else if (nextplot.pkg == "graphics") { # unless force.append is used, # check for par (mfrow / mfcol / mfg) and split.screen scenarios: if (.is.par.or.screen.inuse () && action != "dev.off") return (invisible ()) } st <- .get.sys.time () n <- switch (histPositions[[devId]]$pkg, graphics = .record.graphics (devId, action, newplot.in.Q, st), unknown = .record.graphics (devId, action, newplot.in.Q, st, "unknown"), lattice = .record.lattice (devId, action, newplot.in.Q, st), NA_integer_) if (newplot.in.Q) { .tmp.hP <- modifyList (.hP.template, list (is.this.plot.new = TRUE, is.this.dev.new = FALSE, pkg = nextplot.pkg, call = nextplot.call)) .tmp.hP$pos.prev <- ifelse (is.null (.unsavedPlot$plot) && .unsavedPlot$is.os, histPositions [[devId]]$pos.prev, n) histPositions [[devId]] <<- .tmp.hP } else { histPositions [[devId]]$is.this.plot.new <<- FALSE if (!is.na (n)) histPositions [[devId]]$pos.cur <<- n if (action == "force.append") histPositions [[devId]]$plot <<- NA } if (callUHA) .rk.update.hist.actions () invisible () } .record.graphics <- function (devId, action, newplot.in.Q, st, pkg = "graphics") { .record.main (devId, pkg) if (is.null (.unsavedPlot$plot)) return (invisible (NA_integer_)) if (histPositions [[devId]]$is.this.plot.new) { save.mode <- ifelse (newplot.in.Q, "append", action) if (save.mode %in% c("arrows", "dev.off", "force.append")) save.mode <- "append" } else { save.mode <- ifelse (newplot.in.Q, "overwrite", action) if (save.mode %in% c("arrows", "dev.off")) save.mode <- "overwrite" } n <- save.plot.to.history (devId, save.mode, ifelse (action == "force.append", "unknown", pkg), st, histPositions[[devId]]$call) invisible (n) } .record.lattice <- function (devId, action, newplot.in.Q, st) { if (!histPositions [[devId]]$is.this.plot.new) return (invisible (histPositions [[devId]]$pos.cur)) .record.main (devId, "lattice") if (is.null (.unsavedPlot$plot)) return (invisible (NA_integer_)) save.mode <- ifelse (newplot.in.Q, "append", action) if (save.mode %in% c("arrows", "dev.off")) save.mode <- "append" n <- save.plot.to.history (devId, save.mode, "lattice", st, histPositions[[devId]]$call) invisible (n) } .record.main <- function (devId, pkg) { devId.cur <- dev.cur () unsplot <- NULL unsplot.ls <- NULL if (pkg %in% c("graphics", "unknown")) { .rk.backups$dev.set (as.numeric (devId)) try (unsplot <- recordPlot(), silent=TRUE) .rk.backups$dev.set (devId.cur) } else if (pkg == "lattice") { unsplot <- histPositions [[devId]]$plot unsplot.ls <- histPositions [[devId]]$tlo.ls } else { .unsavedPlot <<- list (plot = NULL, tlo.ls = NULL, pkg = NA_character_, is.os = NA, tryerr = NA) return (invisible (rk.show.message ("Unknown graphics function. Use append to store.", "Recording error", FALSE))) } if (class (unsplot) == "try-error") { .unsavedPlot <<- list (plot = NULL, tlo.ls = NULL, pkg = pkg, is.os = NA, tryerr = TRUE) return (invisible (rk.show.message ("Unknown recording error", "Recording error", FALSE))) } .unsavedPlot <<- list (plot = unsplot, tlo.ls = unsplot.ls, pkg = pkg, is.os = object.size (unsplot) > getOption ("rk.graphics.hist.max.plotsize") * 1024, tryerr = FALSE) invisible () } ## Saving (the recorded plot) functions: save.plot.to.history <- function (devId, save.mode, pkg, st, .cstr = NA_character_) { switch (save.mode, append = .save.plot.to.history.append (devId, pkg, st, .cstr), overwrite = .save.plot.to.history.overwrite (devId, pkg, st, .cstr), NA_integer_) } .save.plot.to.history.append <- function (devId, pkg, st, .cstr) { if (!.save.oversized.plot ()) return (invisible (NA_integer_)) n <- .grow.history (st) if (is.na (n)) return (invisible (n)) savedPlots [[st]] <<- list (plot = .unsavedPlot$plot, tlo.ls = .unsavedPlot$tlo.ls, pkg = pkg, time = st, call = NULL) savedPlots [[st]]$call <<- try (.get.oldplot.call (n, .cll, .cstr)) invisible (n) } .save.plot.to.history.overwrite <- function (devId, pkg, st, .cstr) { # this is not setup to handle an (yet unwritten) 'overwrite' action from tool/menu bar n <- histPositions [[devId]]$pos.cur .st. <- .sP.index [[n]] if (!.check.identical (.st., pkg) && !is.null (.unsavedPlot$plot)) { if (!.save.oversized.plot ()) return (invisible (n)) savedPlots [[.st.]]$plot <<- .unsavedPlot$plot savedPlots [[.st.]]$tlo.ls <<- .unsavedPlot$tlo.ls savedPlots [[.st.]]$call <<- try (.get.oldplot.call (n, .cll, .cstr)) .check.other.dev.at.same.pos (devId, n) } invisible (n) } .save.oversized.plot <- function () { if (is.na (.unsavedPlot$is.os)) ret <- FALSE else if (!.unsavedPlot$is.os) ret <- TRUE else ret <- rk.show.question ("Large plot!\nDo you still want to store it in the history?", "WARNING!", button.cancel = "") ret } .check.identical <- function (.st., pkg=NA_character_) { # this may need to be split into separate .check.identical."pkg" functions identical (savedPlots[[.st.]]$plot, .unsavedPlot$plot) } .check.other.dev.at.same.pos <- function (devId, .n.) { # length (.n.) >= 1 when .verify.hist.limits () calls remove () odnames <- names(histPositions) [!(names(histPositions) %in% c("1", devId))] if (length (odnames) == 0) return (invisible ()) odpos <- sapply (histPositions [odnames], "[[", "pos.cur") # names kept odpos <- odpos [which (odpos %in% .n.)] if (length (odpos) == 0) return (invisible ()) for (d in names (odpos)) { histPositions[[d]]$is.this.plot.new <<- TRUE histPositions[[d]]$pos.prev <<- histPositions[[d]]$pos.cur ## may not be approprite for "remove" histPositions[[d]]$pos.cur <<- NA_integer_ } invisible () } .grow.history <- function (st) { len.sP <- sP.length ml <- getOption ('rk.graphics.hist.max.length') if (len.sP < ml) { n <- len.sP + 1 } else if (len.sP == ml) { if (.pop.notify) .pop.notify <<- rk.show.question ("History limit reached, removing the first plot. Limits can be changed at Settings > RKWard > Output.\n\nDo you want to be notified in future?", "WARNING!", button.cancel = "") remove (devId = NULL, pos = 1) # sP.length changes at this point n <- len.sP } else { # this can happen, if max history length gets set below sP.length, without removing the excess plots, # still, this should be avoided. rk.show.message ("Current history length > max length: plot not added to history!", "WARNING!") return (invisible (NA_integer_)) } .sP.index [[n]] <<- st .set.sP.length () n } ## Removal function: remove <- function (devId = dev.cur (), pos = NA_integer_) # pos can be of length > 1 { # devId == NULL when called from verify.hist.length () if (sP.length == 1) { clearHistory () rk.show.message ("Plot history cleared!", "Remove plot", FALSE) } if (sP.length <= 1) return (invisible ()) if (!is.null (devId)) devId <- as.character (devId) if (!is.null (devId)) { if (histPositions[[devId]]$is.this.dev.new) # on an empty device return (invisible (rk.show.message ("Nothing to remove!", "Remove plot", FALSE))) else if (is.na (pos) || histPositions[[devId]]$is.this.plot.new) { # unsaved plot on the device, so just replay the "previous" plot .p. <- histPositions[[devId]]$pos.prev if (is.na (.p.)) .p. <- sP.length replay (.p., devId) return (invisible ()) } } .check.other.dev.at.same.pos (devId, pos) # works for devId = NULL as well .sP.pos <- unlist (.sP.index [pos]) savedPlots [.sP.pos] <<- NULL .sP.index [pos] <<- NULL .set.sP.length () if (!is.null (devId)) replay (min (pos, sP.length), devId) # in this case, length (pos) == 1 .l. <- length (pos) hP.gt.pos <- sapply (histPositions, "[[", "pos.cur") hP.gt.pos <- hP.gt.pos [which (hP.gt.pos > pos[.l.])] # removes NAs; pos[.l.] == max (pos) if (length (hP.gt.pos) > 0) { for (.d. in names (hP.gt.pos)) { histPositions[[.d.]]$pos.cur <<- min (histPositions [[.d.]]$pos.cur - .l., sP.length) histPositions[[.d.]]$pos.prev <<- min (histPositions [[.d.]]$pos.prev - .l., sP.length) } } .rk.update.hist.actions () invisible () } clearHistory <- function () { .sP.index <<- list (); .set.sP.length () savedPlots <<- list () .unsavedPlot <<- list (plot = NULL, tlo.ls = NULL, pkg = NA_character_, is.os = NA, tryerr = NA) .ss.used <<- FALSE for (d in names(histPositions)[-1]) { if (!histPositions [[d]]$is.this.dev.new) histPositions [[d]]$is.this.plot.new <<- TRUE histPositions [[d]]$pos.cur <<- NA_integer_ histPositions [[d]]$pos.prev <<- NA_integer_ histPositions [[d]]$pos.dupfrom <<- NA_integer_ # do not reset "pkg" and "call" elements } .rk.update.hist.actions () invisible () } ## Replay function: replay <- function(n, devId = dev.cur ()) { on.exit (.rk.update.hist.actions ()) if (missing (n)) return (invisible (rk.show.message ("Position missing", "Replay error", FALSE))) if (is.na (n) || n <= 0 || n > sP.length) return (invisible (rk.show.message(paste ("replay: 'n' not in valid range: ", n), "Replay error", FALSE))) devId <- as.character (devId) cur.devId <- dev.cur () .rk.backups$dev.set (as.numeric(devId)) st <- .sP.index [[n]] pkg <- savedPlots [[st]]$pkg if (pkg %in% c("graphics", "unknown")) { # NOTE: replayPlot() does *not* call plot.new() replayPlot (savedPlots [[st]]$plot) } else if (pkg == "lattice") { # (re-)plot the lattice object but, if the current window is NOT active, then do not save # it to lattice:::.LatticeEnv$lattice.status ("trellis.last.object" needs it). But we need # to set lattice.status to whichever was the last lattice plot so that trellis.last.object () can # access it if (cur.devId != as.numeric (devId)) tlo.ls <- get ("lattice.status", envir = lattice:::.LatticeEnv) rk.without.plot.history ({ plot (savedPlots [[st]]$plot, save.object = (cur.devId == as.numeric (devId))) }) if (cur.devId != as.numeric (devId)) assign ("lattice.status", tlo.ls, envir = lattice:::.LatticeEnv) } histPositions [[devId]] <<- modifyList (.hP.template, list (is.this.plot.new = FALSE, is.this.dev.new = FALSE, pos.prev = n, pos.cur = n, pkg = pkg, call = savedPlots [[st]]$call, plot = savedPlots [[st]]$plot, tlo.ls = savedPlots [[st]]$tlo.ls)) .rk.backups$dev.set (cur.devId) invisible() } ## Action wrappers: showFirst <- function(devId = dev.cur()) { if (!.is.device.managed (devId)) return (invisible ()) record (devId, isManaged = TRUE, action = "arrows") replay(n = 1, devId) } showPrevious <- function(devId) { if (!.is.device.managed (devId)) return (invisible ()) record (devId, isManaged = TRUE, action = "arrows") .n. <- histPositions [[as.character (devId)]]$pos.cur - 1L if (is.na (.n.)) .n. <- sP.length # this happens when sP.length > 0 and the user calls x11 () replay(n = .n., devId = devId) } showNext <- function(devId) { if (!.is.device.managed (devId)) return (invisible ()) record (devId, isManaged = TRUE, action = "arrows") replay(n = histPositions [[as.character (devId)]]$pos.cur + 1L, devId = devId) } showLast <- function(devId = dev.cur()) { if (!.is.device.managed (devId)) return (invisible ()) record (devId, isManaged = TRUE, action = "arrows") replay(n = sP.length, devId) } showPlot <- function(devId = dev.cur(), index) { if (!.is.device.managed (devId)) return (invisible ()) .n. <- histPositions [[as.character (devId)]]$pos.cur if (index == ifelse (is.na (.n.), sP.length + 1, .n.)) { # same position! No action needed return (invisible ()) } record (devId, isManaged = TRUE, action = "arrows") index <- max (as.integer (index), 1L) replay(n = min (sP.length, index), devId) } forceAppend <- function (devId = dev.cur ()) { if (!.is.device.managed (devId)) return (invisible (rk.show.message ("Device not managed", "Append this plot", FALSE))) record (devId, isManaged = TRUE, action = "force.append") } removePlot <- function (devId = dev.cur ()) { if (!.is.device.managed (devId)) return (invisible (rk.show.message ("Device not managed", "Remove plot", FALSE))) remove (devId, histPositions[[as.character (devId)]]$pos.cur) } showPlotInfo <- function (devId = dev.cur ()) { rk.show.message (.get.plot.info.str (devId), caption = "Plot properties", FALSE) } ## Utility / print functions: getDevSummary <- function (devId = NULL) { message ("History length : ", sP.length) message ("History size (KB): ", round (object.size (savedPlots) / 1024, 2)) if (is.null (devId)) { .tmp.df <- data.frame ( pNew = sapply (histPositions, "[[", "is.this.plot.new"), dNew = sapply (histPositions, "[[", "is.this.dev.new"), posC = sapply (histPositions, "[[", "pos.cur"), posP = sapply (histPositions, "[[", "pos.prev"), posD = sapply (histPositions, "[[", "pos.dupfrom"), pkg = sapply (histPositions, "[[", "pkg"), pCls = sapply (lapply (histPositions, "[[", "plot"), FUN = function (x) class (x))) rownames (.tmp.df) <- names (histPositions) } else { devId <- as.character (devId) .a.hP <- histPositions[[devId]] .tmp.df <- data.frame ( pNew = .a.hP$is.this.plot.new, dNew = .a.hP$is.this.dev.new, posC = .a.hP$pos.cur, posP = .a.hP$pos.prev, posD = .a.hP$pos.dupfrom, pkg = .a.hP$pkg, pCls = class (.a.hP$plot)) rownames (.tmp.df) <- devId } .tmp.df } getSavedPlotsSummary <- function () { .tmp.df <- data.frame ( call = sapply (savedPlots[unlist (.sP.index, use.names = FALSE)], "[[", "call"), size.KB = sapply (lapply (savedPlots[unlist (.sP.index, use.names = FALSE)], "[[", "plot"), function (x) object.size(x)/1024), pkg = sapply (savedPlots[unlist (.sP.index, use.names = FALSE)], "[[", "pkg"), timestamp = sapply (savedPlots[unlist (.sP.index, use.names = FALSE)], "[[", "time")) rownames (.tmp.df) <- NULL .tmp.df } ## Utility / call labels functions: .get.sP.calls <- function () { labels <- NULL if (sP.length > 0) labels <- paste (format (1:sP.length), sapply (savedPlots [unlist (.sP.index, use.names = FALSE)], "[[", "call"), sep = ": ") names (labels) <- NULL labels } .get.plot.info.str <- function (devId = dev.cur (), l=0) { devId <- as.character (devId) if (!(devId %in% names(histPositions))) return (paste ("Device", devId, "is not managed.")) n <- histPositions [[devId]]$pos.cur if (is.na (n)) { info.str <- paste ("Device: ", devId, ", Position: , Size: ?\nType: ", histPositions [[devId]]$pkg, sep = "") } else if (n == 0) { info.str <- paste ("Device: ", devId, ", Position: 0", sep = "") } else { info.str <- paste ("Device: ", devId, ", Position: ", n, ", Size (KB): ", round (object.size (savedPlots [[.sP.index [[n]]]]$plot)/1024, 2), "\n", .get.oldplot.call (n, l, histPositions [[devId]]$call), sep = "") } info.str } .get.oldplot.call <- function (n, l=0, cs = NA_character_) { # this can be easily extended to more types switch (savedPlots [[.sP.index [[n]]]]$pkg, graphics = .get.oldplot.call.std (l, cs), unknown = .get.oldplot.call.unk (n, l), lattice = .get.oldplot.call.lattice (n, l), "Unknown") } .get.oldplot.call.unk <- function (n,l=0) { # rp <- recordPlot () is a nested pairlist object (of class "recordedplot"): # rp[[1]] is the "meta data", rp[[2]] is always raw, # We then figure out the relevant stuff from rp[[1]]. Use "str (rp)" for details. # Currently, only main, sub, xlab, and ylab meta data can be extracted, unambiguously. # The high level calls are not part of the meta data, only the low level .Internal # calls are stored: Eg: .Primitive (plot.xy), .Primitive (rect), .Primitive (persp), etc... # .f. identifies which element(s) in rp[[1]] contains title (=main,sub,xlab,ylab) information: # differs from call to call. Eg: for plot () calls this is 7, for hist () this is 3, ... .tmp.plot. <- savedPlots [[.sP.index [[n]]]]$plot[[1]] .f. <- function () which (lapply (.tmp.plot., FUN = function (x) x[[1]]) == ".Primitive(\"title\")") # Sometimes there is no title information at all - happens when the high level calling function # does not specifically provide any of main/sub/xlab/ylab arguemnts: Eg: persp (...) # Sometimes there are more than one .Primitive ("title") calls, eg, when title (...) is called # explicitely after a plotting call .x. <- list (main = "", sub = "", xlab = "", ylab = "") # When present, rp [[1]] [.n.] [[2]] contains (in multiple lists) main, sub, xlab, ylab, etc. # From there we pick up the last (which.max) non-null entry for each of main, sub, xlab, and ylab .n. <- .f. () if (length (.n.) > 0) { .T. <- lapply (lapply (.tmp.plot. [.n.], FUN = function (.a.) .a.[[2]]), FUN = function (.aa.) {names (.aa.) <- c("main", "sub", "xlab", "ylab"); .aa.}) for (i in c("main", "sub", "xlab", "ylab")) .x.[[i]] <- .T. [[ which.max (sapply (.T., FUN = function (.a.) !is.null (.a.[[i]]))) ]] [[i]] } #.lab.str <- paste ("Main: '", .x.$main, "'; X label: '", .x.$xlab, "'; Y label: '", .x.$ylab, "'", sep = "") .lab.str <- paste ("X: ", .x.$xlab, "; Y: ", .x.$ylab, "; ", .x.$main, sep = "") if (all (unlist (.x.) == "")) .lab.str <- paste ("", .lab.str) if (l <= 0 || nchar (.lab.str) <= l) return (.lab.str) paste (substr (.lab.str, 1, l), "...", sep = "") } .get.oldplot.call.std <- function (l=0, cs) { .lab.str <- paste (ifelse (is.call (cs), deparse (cs), cs), collapse = ifelse (l<=0, "\n", ", ")) if (l <= 0 || nchar (.lab.str) <= l) return (.lab.str) paste (substr (.lab.str, 1, l), "...", sep = "") } .get.oldplot.call.lattice <- function (n,l=0) { .lab.str <- paste (deparse (savedPlots [[.sP.index [[n]]]]$plot$call), collapse = ifelse (l<=0, "\n", ", ")) if (l <= 0 || nchar (.lab.str) <= l) return (.lab.str) paste (substr (.lab.str, 1, l), "...", sep = "") } .plot.new.hook <- function (.callstr) { if (dev.cur() == 1) dev.new () if (getOption ("rk.enable.graphics.history")) { .callstr <- sys.call (-sys.parents()[sys.nframe ()]) record (nextplot.pkg = "graphics", nextplot.call = .callstr) } } ## Utility / R - C++ connection functions: .rk.update.hist.actions <- function (devIds = names(histPositions), enable.plot.hist = TRUE) { # this function is called whenever the history length changes # or the position changes in any device. devIds <- devIds [devIds != "1"] # ignore NULL device ndevs <- length (devIds) if (ndevs > 0) { positions <- character (2 * ndevs) positions [2 * (1:ndevs) - 1] <- devIds ihP <- sapply (histPositions[devIds], "[[", "pos.cur"); ihP [is.na (ihP)] <- sP.length + 1 positions [2 * (1:ndevs)] <- ihP .rk.do.call ("updateDeviceHistory", c (ifelse (enable.plot.hist, sP.length, 0), .get.sP.calls (), positions)); } invisible () } .verify.hist.limits <- function (len.max) { # this is called from settings/rksettingsmoduleoutput.cpp ~199 # Length restriction: if (len.max < sP.length) { ans <- rk.show.question (paste ("Current plot history has more plots than the specified limit.\nIf you continue then _", sP.length - len.max, "_ of the foremost plots will be removed.\nInstead, if you ignore then the new limit will be effective only after restarting RKWard.", sep =""), "WARNING!", button.yes = "Continue", button.no = "Ignore for this session", button.cancel = "") if (ans) { options ("rk.graphics.hist.max.length" = len.max) remove (devId = NULL, pos = 1:(sP.length - len.max)) } } else { # this takes care of the initialization when RKWard starts.. options ("rk.graphics.hist.max.length" = len.max) } # Size restriction: #s <- getOption ('rk.graphics.hist.max.plotsize') # Existing plots are not checked for their sizes, only the new ones are. } env$duplicating.from.device <- 1 # NULL device env } rk.record.plot <- rk.record.plot () # Users should use only these wrappers: # 1 is always the null device #' @export "rk.toggle.plot.history" <- function (x = TRUE) { if (x) { rk.record.plot$initialize.histPositions () } else { rk.record.plot$flushout.histPositions () } options ("rk.enable.graphics.history" = x) invisible () } #' @export "rk.first.plot" <- function (devId = dev.cur ()) { if (!getOption ("rk.enable.graphics.history")) return (invisible ()) rk.record.plot$showFirst (devId) } #' @export "rk.previous.plot" <- function (devId = dev.cur ()) { if (!getOption ("rk.enable.graphics.history")) return (invisible ()) rk.record.plot$showPrevious (devId) } #' @export "rk.next.plot" <- function (devId = dev.cur ()) { if (!getOption ("rk.enable.graphics.history")) return (invisible ()) rk.record.plot$showNext (devId) } #' @export "rk.last.plot" <- function (devId = dev.cur ()) { if (!getOption ("rk.enable.graphics.history")) return (invisible ()) rk.record.plot$showLast (devId) } #' @export "rk.goto.plot" <- function (devId = dev.cur (), index=1) { if (!getOption ("rk.enable.graphics.history")) return (invisible ()) rk.record.plot$showPlot (devId, index) } #' @export "rk.force.append.plot" <- function (devId = dev.cur ()) { if (!getOption ("rk.enable.graphics.history")) return (invisible ()) rk.record.plot$forceAppend (devId) } #' @export "rk.removethis.plot" <- function (devId = dev.cur ()) { if (!getOption ("rk.enable.graphics.history")) return (invisible ()) rk.record.plot$removePlot (devId) } #' @export "rk.clear.plot.history" <- function () { if (!getOption ("rk.enable.graphics.history")) return (invisible ()) rk.record.plot$clearHistory () } #' @export "rk.show.plot.info" <- function (devId = dev.cur ()) { if (!getOption ("rk.enable.graphics.history")) return (invisible ()) rk.record.plot$showPlotInfo (devId) } #' @export "rk.verify.plot.hist.limits" <- function (lmax) { if (!getOption ("rk.enable.graphics.history")) return (invisible ()) rk.record.plot$.verify.hist.limits (as.integer (lmax)) } #' @export "rk.plot.history.summary" <- function (which = NULL, type = c ("devices", "history")) { ret <- NULL if (getOption ("rk.enable.graphics.history")) ret <- switch ( devices = rk.record.plot$getDevSummary (which), history = rk.record.plot$getSavedPlotsSummary (), NULL) ret } #' Run a (plotting) action, without recording anything in the plot history. #' Internally, the plot history option is turned off for the duration of the action. #' #' @export "rk.without.plot.history" <- function (expr) { if (getOption ("rk.enable.graphics.history")) { on.exit (options ("rk.enable.graphics.history" = TRUE)) } options ("rk.enable.graphics.history" = FALSE) eval.parent(expr) } rkward-0.6.4/rkward/rbackend/rpackages/rkward/R/rk.edit-functions.R0000644000175000017500000000601512471622105024554 0ustar thomasthomas#' Edit / show an object / file #' #' \code{rk.edit} can be used to edit an object in the RKWard data editor. #' Currently only \link{data.frame}s are supported. This is similar to #' \link{edit.data.frame}, but the function returns immediately, and the object #' is edit asynchronously. #' #' \code{rk.edit.files}, \code{rk.show.files}, and \code{rk.show.html} are #' equivalent to \link{edit}, \link{file.show}, and \link{browseURL}, #' respectively, but use RKWard as text/html editor/viewer. Generally it is #' recommended to use \link{edit}, \link{file.edit}, \link{file.show}, #' and \link{browseURL}, instead. These will call the respective RKWard functions #' by default, when run inside an RKWard session. (via \code{getOption("editor")}, #' and \code{getOption("browser")}. #' #' @aliases rk.edit rk.edit.files rk.show.files rk.show.html #' @param x an object to edit. #' @param file character vector, filenames to show or edit. #' @param title character vector, of the same length as \code{file}; This can #' be used to give descriptive titles to each file, which will be displayed #' to the user. #' @param wtitle character vector, of length 1. This will be used as the window #' title. #' @param prompt logical of length 1. If TRUE (the default) a prompt is dialog #' is shown along with the files to show / edit. #' @param delete a logical (not NA), when \code{TRUE} the shown file(s) are #' deleted after closing. #' @return All functions described on this page return \code{NULL}, #' unconditionally. #' @author Thomas Friedrichsmeier \email{rkward-devel@@kde.org} #' @seealso \code{\link{edit}}, \code{\link{file.edit}}, #' \code{\link{file.show}}, \code{\link{browseURL}} #' @keywords utilities IO #' @rdname rk.edit #' @examples #' #' ## Not run #' x <- data.frame (a=c(1:3), b=c(2:4)) #' rk.edit(x) #' @export "rk.edit" <- function (x) { object <- deparse (substitute (x)) .rk.do.call ("edit", object) } #' @export #' @rdname rk.edit "rk.edit.files" <- function (name=NULL, file="", title=NULL, prompt = TRUE) { if (!is.null(name)) { if (is.null (title)) title = deparse (substitute (name)) if (file == "") file = tempfile() env = environment(name) dput (name, file = file, control = c("useSource", "keepNA", "keepInteger", "showAttributes")) .Call ("rk.edit.files", file, title, "", prompt, PACKAGE = "(embedding)") x <- dget(file) environment(x) <- env return(x) } invisible (.Call ("rk.edit.files", as.character (file), as.character (title), as.character (name), isTRUE (prompt), PACKAGE="(embedding)")) } #' @export #' @rdname rk.edit "rk.show.files" <- function (file = file, header = file, title = NULL, delete.file=FALSE, prompt = TRUE, delete = delete.file # For compatibility with earlier versions of R ) { invisible (.Call ("rk.show.files", as.character (file), as.character (header), as.character (title), delete, isTRUE (prompt), PACKAGE="(embedding)")) } #' @export #' @rdname rk.edit "rk.show.html" <- function (url) { invisible (.rk.do.plain.call ("showHTML", as.character (url), synchronous=FALSE)); } rkward-0.6.4/rkward/rbackend/rpackages/rkward/R/rk.sync-functions.R0000644000175000017500000000274412471622105024610 0ustar thomasthomas#' Sync R object(s) #' #' RKWard keeps an internal representation of objects in the R workspace. For #' objects in the \code{.GlobalEnv}, this representation is updated after each #' top-level statement. For the rare cases where this is not enough, #' \code{rk.sync} can be used to update the representation of a single object, #' \code{x}, while \code{rk.sync.global} scans the \code{.GlobalEnv} for new #' and removed objects, and updates as appropriate. #' #' These functions are rarely needed outside automated testing. However, #' rk.sync() can be useful, if an object outside the \code{.GlobalEnv} has #' changed, since this will not be detected automatically. Also, by default #' RKWard does not recurse into environments when updating its representation #' of objects. rk.sync() can be used, here, to inspect the objects inside #' environments (see examples). #' #' @aliases rk.sync rk.sync.global #' @param x any R object to sync #' @return \code{NULL}, invisibly. #' @author Thomas Friedrichsmeier \email{rkward-devel@@kde.org} #' @seealso \url{rkward://page/rkward_workspace_browser} #' @keywords utilities misc #' @rdname rk.sync #' @examples #' #' rk.sync (rkward::rk.record.plot) #' # should this really be public? #' @export "rk.sync" <- function (x) { object <- deparse (substitute (x)) .rk.do.call ("sync", object) } # should this really be public? #' @export #' @rdname rk.sync "rk.sync.global" <- function () { .rk.do.call("syncglobal", ls (envir=globalenv (), all.names=TRUE)) } rkward-0.6.4/rkward/rbackend/rpackages/rkward/R/internal_help.R0000644000175000017500000000336412455741221024041 0ustar thomasthomas## Internal functions related to help search / display # retrieve the (expected) "base" url of help files. Most importantly this will be a local port for R 2.10.0 and above, but a local directory for 2.9.x and below. As a side effect, in R 2.10.0 and above, the dynamic help server is started. #' @export ".rk.getHelpBaseUrl" <- function () { port <- NA if (compareVersion (as.character (getRversion()), "2.10.0") >= 0) { try ({ port <- tools::startDynamicHelp () }) if (is.na (port)) { try ({ port <- tools:::httpdPort }) } } if (is.na (port)) { return (paste ("file://", R.home (), sep="")) } return (paste ("http://127.0.0.1", port, sep=":")) } # a simple wrapper around help() that makes it easier to detect in code, whether help was found or not. # used from RKHelpSearchWindow::getFunctionHelp #' @export ".rk.getHelp" <- function (...) { if (compareVersion (as.character (getRversion()), "2.10.0") >= 0) { res <- help (..., help_type="html") } else { res <- help (..., chmhelp=FALSE, htmlhelp=TRUE) } if (!length (as.character (res))) { # this seems undocumented, but it is what utils:::print.help_files_with_topic checks show (res) stop ("No help found") } show (res) invisible (TRUE) } # Simple wrapper around help.search. Concatenates the relevant fields of the results in order for passing to the frontend. #' @export ".rk.get.search.results" <- function (pattern, ...) { H=as.data.frame (help.search(pattern, ...)$matches) # NOTE: The field "Type" was added in R 2.14.0. For earlier versions of R, only help pages were returned as results of help.search() if ((dim (H)[1] > 0) && (is.null (H$Type))) H$Type <- "help" c (as.character (H$topic), as.character (H$title), as.character(H$Package), as.character(H$Type)) } rkward-0.6.4/rkward/rbackend/rpackages/rkward/R/internal_debugger.R0000644000175000017500000000327712455741221024700 0ustar thomasthomas# gather debug information. # Note: subsequent browser() calls should be suppressed while inside this function! #' @export .rk.callstack.info <- function () { nframes <- sys.nframe() - 1 # strip this function call calls <- character (0) funs <- character (0) envs <- character (0) locals <- character (0) relsrclines <- integer (0) if (nframes > 0) { for (i in 1:nframes) { calls[i] <- as.character (try (paste (deparse (sys.call (i)), collapse="\n")), silent=TRUE) funs[i] <- as.character (try (paste (deparse (sys.function (i), control="all"), collapse="\n"), silent=TRUE)) envs[i] <- as.character (try (capture.output (print (environment (sys.function (i)))), silent=TRUE)) locals[i] <- as.character (try (paste (ls (sys.frame (i), all.names=TRUE), collapse="\n"), silent=TRUE)) relsrclines[i] <- as.character (try (rk.relative.src.line (sys.call (i+1), sys.function (i)), silent=TRUE)) } } list ("calls"=calls, "functions"=funs, "environments"=envs, "locals"=locals, "relsrclines"=relsrclines) } # get relative source location # NOTE: this requires R >= 2.13.0 #' @export rk.relative.src.line <- function (inner, outer) { if (!inherits (inner, "srcref")) inner <- getSrcref (inner) if (!inherits (outer, "srcref")) outer <- getSrcref (outer) if (is.null (inner) || is.null (outer)) return (NA) if (getSrcFilename (inner) != getSrcFilename (outer)) return (NA) outer_first <- getSrcLocation (outer, "line") outer_last <- getSrcLocation (outer, "line", first=FALSE) inner_first <- getSrcLocation (inner, "line") inner_last <- getSrcLocation (inner, "line", first=FALSE) if ((inner_first < outer_first) || (inner_last > outer_last)) return (NA) return (inner_first - outer_first + 1) } rkward-0.6.4/rkward/rbackend/rpackages/rkward/R/rk.KDE_GUI-functions.R0000644000175000017500000001017412471622105024737 0ustar thomasthomas#' Message boxes and selection list using native KDE GUI #' #' Multi-purpose pop-up message boxes and selection list using native KDE GUI #' elements. The message boxes can be used either to show some information or #' ask some question. The selection list can be used to get a vector of #' selected items. #' #' For \code{rk.show.question}, the R interpreter always waits for the user's #' choice. #' #' \code{rk.select.list} replaces \code{utils::select.list} for the running #' session acting as a drop-in replacement for \code{tk_select.list}. Use #' \code{.rk.backups$select.list} for the original \code{utils::select.list} #' function (see Examples). #' #' @aliases rk.show.message rk.show.question rk.select.list #' @param message a string for the content of the message box. #' @param caption a string for title of the message box. #' @param button.yes a string for the text label of the \bold{Yes} button. Can #' be an empty string (\code{""}), in which case the button is not displayed #' at all. #' @param button.no a string used for the text label of the \bold{No} button, #' similar to \code{button.yes}. #' @param button.canel a string used for the text label of the \bold{Cancel} #' button, similar to \code{button.yes}. #' @param wait a logical (not NA) indicating whether the R interpreter should #' wait for the user's action, or run it asynchronously. #' @param list a vector, coerced into a character vector. #' @param preselct a vector, coerced into a character vector, items to be #' preselected. #' @param multiple a logical (not NA), when \code{TRUE} multiple selection #' selection is allowed. #' @param title a string, for the window title of the displayed list #' @return \code{rk.show.message} always returns \code{TRUE}, invisibly. #' #' \code{rk.show.question} returns \code{TRUE} for \bold{Yes}, \code{FALSE} for #' \bold{No}, and \code{NULL} for \bold{Cancel} actions. #' #' \code{rk.select.list} returns the value of \code{\link{select.list}}. #' @author Thomas Friedrichsmeier \email{rkward-devel@@kde.org} #' @seealso \code{\link{system}}, \code{\link{select.list}} #' @keywords utilities #' @rdname rk.show.messages #' @examples #' #' require (rkward) #' #' ## Message boxes #' if (rk.show.question ("Question:\nDo you want to know about RKWard?", #' button.yes = "Yes, I do!", button.no = "No, I don't care!", button.cancel = "")) { #' rk.show.message ("Message:\nRKWard is a KDE GUI for R.", "RKWard Info") #' } else { #' rk.show.message ("You must be joking!", "RKWard Info", wait = FALSE) ## Run asynchronously #' } #' #' ## Selection lists: #' rk.select.list (LETTERS, preselect = c("A", "E", "I", "O", "U"), #' multiple = TRUE, title = "vowels") #' .rk.backups$select.list (LETTERS, preselect = c("A", "E", "I", "O", "U"), #' multiple = TRUE, title = "vowels") #' @export "rk.show.message" <- function (message, caption = "Information", wait=TRUE) { .Call ("rk.dialog", caption, message, "ok", "", "", isTRUE (wait), PACKAGE="(embedding)") invisible (TRUE) } # to disable a button, set it to "" #' @export #' @rdname rk.show.messages "rk.show.question" <- function (message, caption = "Question", button.yes = "yes", button.no = "no", button.cancel = "cancel") { res <- .Call ("rk.dialog", caption, message, button.yes, button.no, button.cancel, TRUE, PACKAGE="(embedding)") if (res > 0) return (TRUE) else if (res < 0) return (FALSE) else return (NULL) # cancelled } # drop-in-replacement for tk_select.list() #' @export #' @rdname rk.show.messages "rk.select.list" <- function (list, preselect = NULL, multiple = FALSE, title = NULL) { preselect <- as.character (preselect) preselect.len = length (preselect) list <- as.character (list) list.len <- length (list) params <- list () # serialize all parameters params[1] <- as.character (title) if (multiple) params[2] <- "multi" else params[2] <- "single" params[3] <- as.character (preselect.len) if (preselect.len) { for (i in 1:preselect.len) { params[3+i] <- preselect[i] } } if (list.len) { # we should hope, the list is not empty... for (i in 1:list.len) { params[3+preselect.len+i] <- list[i] } } .rk.do.plain.call ("select.list", params) } rkward-0.6.4/rkward/rbackend/rpackages/rkward/R/rk.label-functions.R0000644000175000017500000001355012471622105024710 0ustar thomasthomas#' Various label related utility functions #' #' \code{rk.get.label} retrieves the rkward label (if any) of the given object. #' #' \code{rk.set.label} sets the rkward label for the given object. #' #' \code{rk.list.labels} retrieves the rkward labels for a list of objects. #' Most importantly, this can be used for extracting all column labels in a #' \code{data.frame}, conveniently. The parameter \code{fill} controls, what #' happens, when no rkward labels have been assigned. The default (\code{FALSE}) #' is to return empty strings for any missing labels. For \code{fill=TRUE}, missing #' labels will be filled with the short names of the object. You can also pass #' a character vector of default labels to use as the \code{fill} parameter. #' #' \code{rk.get.short.name} creates a short name for the given object. #' #' \code{rk.get.description} creates descriptive string(s) for each of the #' arguments in "\code{\dots{}}"; collapsing into a single string using #' \code{paste.sep} (if not NULL). If \code{is.substitute=TRUE}, the arguments #' will be deparsed, first, which can be useful when using #' \code{rk.get.description} inside a function. #' #' \code{rk.list.names} returns the names of the arguments passed as #' \code{...}; when using \code{rk.list.names} inside a function, it may be #' necessary to increase the \code{deparse.level} level. #' #' \code{rk.list} returns a list of its arguments, with \code{names} set as #' returned by \code{rk.get.description()}. This can be used as a drop-in #' replacement for \code{\link{list}}. #' #' @aliases rk.get.label rk.set.label rk.get.short.name rk.get.description #' rk.list.names rk.list #' @param x any R object #' @param label a string, to set the label attribute of an object #' @param envir an environment, where the attribute is evaluated #' @param fill a logical or character. See Details. #' @param paste.sep a string, used as the \code{collapse} argument for paste #' @param is.substitute a logical (not NA). See Details. #' @return \code{rk.set.label} returns the result of the evaluation of "setting #' the label" while the others return a character vector. #' @author Thomas Friedrichsmeier \email{rkward-devel@@kde.org} #' @keywords utilities #' @rdname rk.label #' @examples #' #' x <- data.frame(a=c(1:3), b=c(2:4)) #' rk.set.label(x[["a"]], "First column") #' rk.get.short.name (x$a) # "x$a" #' rk.get.label (x$a) # "First column" #' rk.get.description (x$a) # "x$a (First column)" #' rk.list.labels (x) # "First column" "" #' rk.list.labels (x, TRUE) # "First column" "b" #' rk.list.names (x, x$a, x$b) # "x" "x$a" "x$b" #' names (rk.list (x$a, x$b)) # "x$a (First column)" "x$b" #' # retrieve the rkward label (if any) of the given object #' @export "rk.get.label" <- function (x) { if (is.call (x) || is.name (x)) { x <- eval (x) } ret <- attr (x, ".rk.meta")[["label"]] if (is.null (ret) || is.na (ret) || (length (ret) < 1)) "" else as.character (as.vector (ret))[1L] } # set rkward label #' @rdname rk.label #' @export "rk.set.label" <- function (x, label, envir=parent.frame()) { if (is.call (x) || is.name (x)) { meta <- attr (eval (x, envir=envir), ".rk.meta") } else { meta <- attr (x, ".rk.meta") } meta[["label"]] <- as.character (label)[1L] eval(substitute(attr(x, ".rk.meta") <- meta), envir = envir) } # retrieve the rkward labels for items in the given list #' @rdname rk.label #' @export "rk.list.labels" <- function (x, fill=FALSE) { ret <- sapply (x, rk.get.label) if (isTRUE (fill)) { ret[ret == ""] <- names(x)[ret == ""] } else if (!is.logical (fill)) { ret[ret == ""] <- as.character (fill)[ret == ""] } ret } # get a short name for the given object #' @rdname rk.label #' @export "rk.get.short.name" <- function (x) { if (is.call (x) || is.name (x)) { .rk.make.short.name (deparse (x)) } else { .rk.make.short.name (deparse (substitute (x))) } } # make a short name from the given arg (a character string) # e.g. return "b" for a[["b"]] (but 'a::"b"' for a::"b" #' @export #' @rdname rk.label ".rk.make.short.name" <- function (x) { splt <- strsplit (x, "[[\"", fixed=TRUE)[[1]] spltlen <- length (splt) if (spltlen == 1) { splt[1] } else { strsplit (splt[spltlen], "\"]]", fixed=TRUE)[[1]][1] } } # get descriptive strings for each of the arguments in ... #' @rdname rk.label #' @export "rk.get.description" <- function (..., paste.sep=NULL, is.substitute=FALSE) { args <- list(...) if (is.substitute) { argnames <- list () j <- 1 for (symb in list (...)) { argnames[j] <- deparse (symb) j <- j + 1 } } else { argnames <- rk.list.names (...) } descript <- c () for (i in 1:length (args)) { lbl <- rk.get.label (args[[i]]) if (is.substitute) { shortname <- .rk.make.short.name (as.character (argnames[i])) } else { shortname <- .rk.make.short.name (argnames[i]) } if (is.null (lbl) || (length (lbl) != 1) || (lbl == "")) descript[i] <- shortname else descript[i] <- paste (shortname, " (", lbl, ")", sep="") } if (is.null (paste.sep)) { descript } else { paste (descript, collapse=paste.sep) } } # Drop-in replacement for list(). Returns a list of the given arguments, but with names set according to rk.get.description #' @rdname rk.label #' @export "rk.list" <- function (...) { ret <- list (...) names (ret) <- rk.get.description (...) ret } # this is basically copied from R-base table (). Returns the arguments passed to ... as a character vector #' @rdname rk.label #' @export "rk.list.names" <- function(..., deparse.level=2) { l <- as.list(substitute(list(...)))[-1] nm <- names(l) fixup <- if (is.null(nm)) seq(along = l) else nm == "" dep <- sapply(l[fixup], function(x) switch(deparse.level + 1, "", if (is.symbol(x)) as.character(x) else "", deparse(x)[1])) if (is.null(nm)) dep else { nm[fixup] <- dep nm } } rkward-0.6.4/rkward/rbackend/rpackages/rkward/R/rk.print-functions.R0000644000175000017500000002154312471622105024766 0ustar thomasthomas#' Print objects and results to output #' #' Various utilty functions which can be used to print or export R objects to #' the (html) output file. The output file can be accessed from Windows -> Show #' Output. Basically, these functions along with the ones described in #' \code{\link{rk.get.label}}, \code{\link{rk.get.tempfile.name}}, and #' \code{\link{rk.graph.on}} can be used to create a HTML report. #' #' \code{rk.print} prints/exports the given object to the output (html) file #' using the \code{\link{HTML}} function. This requires the \code{R2HTML} #' package. Additional arguments in \code{...} are passed on to #' \code{\link{HTML}}. #' #' \code{rk.print.literal} prints/exports the given object using a #' \code{paste(x, collapse="\n")} construct to the output (html) file. #' #' \code{rk.print.code} applies syntax highlighting to the given code string, #' and writes it to the output (html) file. #' #' \code{rk.header} prints a header / caption, possibly with parameters, to the #' output file. See example. #' #' \code{rk.results} is similar to \code{rk.print} but prints in a more #' tabulated fashion. This has been implemented only for certain types of #' \code{x}: tables, lists (or data.frames), and vectors. See example. #' #' \code{rk.describe.alternatives} describes the alternative (H1) hypothesis of #' a \code{htest}. This is similar to \code{stats:::print.htext} and makes #' sense only when \code{x$alternatives} exists. #' #' @aliases rk.print rk.print.code rk.print.literal rk.header rk.results #' rk.describe.alternative #' @param x any R object to be printed/exported. A suitable list in case of #' \code{rk.describe.alternative}. #' @param code a character vector (single string) of R code #' @param title a string, used as a header for the html output #' @param level an integer, header level. For example, \code{level=2} creates #' the header with \code{

} tag. #' @param parameters a list, preferably named, giving a list of "parameters" to #' be printed to the output #' @param toc If \code{NULL}, the default, \code{rk.header()} will automatically #' add headers h1 to h4 to the TOC menu of the output document. \code{TRUE} will always #' add the header, and \code{FALSE} will suppress it. #' @param titles a character vector, giving the column headers for a html #' table. #' @param print.rownames controls printing of rownames. TRUE to force printing, #' FALSE to suppress printing, omitted (default) to print rownames, unless #' they are plain row numbers. #' @return \code{rk.describe.alternatives} returns a string while all other #' functions return \code{NULL}, invisibly. #' @author Thomas Friedrichsmeier \email{rkward-devel@@kde.org} #' @seealso \code{\link{HTML}}, \code{\link{rk.get.output.html.file}}, #' \code{\link{rk.get.description}}, \code{\link{rk.call.plugin}}, #' \url{rkward://page/rkward_output} #' @keywords utilities #' @rdname rk.results #' @examples #' #' require (rkward) #' require (R2HTML) #' #' ## see the output: Windows->Show Output #' ## stolen from the two-sample t-test plugin ;) #' local({ #' x1 <- rnorm (100) #' x2 <- rnorm (100, 2) #' nm <- rk.get.description (x1,x2) #' #' result <- t.test (x1, x2, alternative="less") #' rk.print.code ("result <- t.test (x1, x2, alternative=\"less\")") #' #' rk.header (result$method, #' parameters=list ("Comparing", paste (nm[1], "against", nm[2]), #' "H1", rk.describe.alternative (result), #' "Equal variances", "not assumed")) #' #' rk.print.literal ("Raw data (first few rows):") #' rk.print (head (cbind (x1,x2)), align = "left") #' #' rk.print.literal ("Test results:") #' rk.results (list ( #' 'Variable Name'=nm, #' 'estimated mean'=result$estimate, #' 'degrees of freedom'=result$parameter, #' t=result$statistic, #' p=result$p.value, #' 'confidence interval percent'=(100 * attr(result$conf.int, "conf.level")), #' 'confidence interval of difference'=result$conf.int )) #' }) #' #' @export "rk.print" <- function(x,...) { htmlfile <- rk.get.output.html.file() if(require("R2HTML")==TRUE) { HTML(x, file=htmlfile,...) } } #' @export #' @rdname rk.results "rk.print.code" <- function(code) { .rk.cat.output (.rk.do.plain.call ("highlightRCode", as.character (code))) } #' @export #' @rdname rk.results "rk.header" <- function (title, parameters=list (), level=1, toc=NULL) { sink (rk.get.output.html.file(), append=TRUE) on.exit (sink ()) # give header a name to be able to set anchors # it's just a time string down to the fraction of a second: yyyy-mm-dd HH:MM:SS.ssssss if (!isTRUE (options (".rk.suppress.toc")[[1]])) { # disabled during plugin automated testing header.id <- format(Sys.time(), "%Y-%m-%d_%H:%M:%OS6") header.title <- format(Sys.time(), "%Y-%m-%d %H:%M:%S") # add 'id', 'name' and 'title' attributes to the header cat ("", title, "\n", sep="") # if 'toc' is true, also add a javascript function call to add this header to the TOC menu # the function addToTOC() will be defined in the document head # see rk.set.output.html.file() in rk.filename-functions.R if (isTRUE(toc) || (is.null(toc) && level <= 4)){ cat("\n", sep="") } } else { cat ("", title, "\n", sep="") } if (length (parameters)) { # legacy handling: parameter=value used to be passed as parameter, value if (is.null (names (parameters))) { warning ("Unnamed parameter lists are deprecated in rk.header()") s <- seq.int (1, length (parameters), by=2) pnames <- as.character (parameters[s]) parameters <- parameters[s+1] } else { pnames <- names (parameters) } cat ("Parameters\n
    ", sep="") for (i in 1:length (parameters)) { cat ("
  • ", pnames[i], ": ", parameters[[i]], "
  • \n", sep="") } cat ("
\n") } if (level==1) cat (.rk.date ()) cat ("
\n") } # Dummy to allow the rkwardtest package to override rk.header() behavior, easily ".rk.date" <- function () date () #' @export #' @rdname rk.results "rk.results" <- function (x, titles=NULL, print.rownames) { sink (rk.get.output.html.file(), append=TRUE) on.exit (sink ()) # convert 2d tables to data.frames with values labelled if ((is.table(x) || is.matrix(x)) && (length(dim(x)) == 2)) { rows = dim(x)[1] cols = dim(x)[2] if (is.null(titles)) { titles <- names(dimnames(x)) } rn <- dimnames(x)[[1]] if (!is.na (titles[1])) rn <- paste(titles[1], "=", rn) cn <- dimnames(x)[[2]] if (!is.na (titles[2])) cn <- paste(titles[2], "=", cn) titles <- c ("", cn) x <- data.frame (cbind (x), stringsAsFactors=FALSE) rownames (x) <- as.character (rn) if (missing (print.rownames)) print.rownames <- TRUE } if (is.list (x)) { # or a data.frame if (is.data.frame (x)) { # by default, print rownames, unless they are just plain row numbering if (missing (print.rownames)) print.rownames <- !isTRUE (all.equal (rownames (x), as.character (1:dim(x)[1]))) if (isTRUE (print.rownames)) { x <- cbind (rownames (x), x, stringsAsFactors=FALSE) names (x)[1] <- ''; } } if (is.null (titles)) { titles <- names (x) } cat ("\n") try ({ # if anything fails, make sure the "
" is still printed for (i in 1:length (x)) { cat ("", titles[i], "", sep="") } cat ("\n") if (is.data.frame (x)) { x <- as.data.frame (lapply (x, format), stringsAsFactors=FALSE) for (row in 1:dim (x)[1]) { cat ("") for (col in 1:dim (x)[2]) { cat ("", x[row, col], "", sep="") } cat ("\n") } } else { # generic list cat ("") for (col in x) { col <- as.vector (col) cat ("") for (row in 1:length (col)) { if (row != 1) cat ("\n
") cat (col[row]) } cat ("") } cat ("\n") } }) cat ("\n") } else if (is.vector (x)) { cat ("

", titles[1], ": ", sep="") cat (x) cat ("

") } else { stop ("uninmplemented") } } #' @export #' @rdname rk.results "rk.print.literal" <- function (x) { cat ("
", paste (x, collapse="\n"), "
\n", sep="", file=rk.get.output.html.file(), append=TRUE); } # Describe the alternative (H1) of an htest. # This code adapted from stats:::print.htest #' @export #' @rdname rk.results "rk.describe.alternative" <- function (x) { res <- "" if (!is.null(x$alternative)) { if (!is.null(x$null.value)) { if (length(x$null.value) == 1) { alt.char <- switch(x$alternative, two.sided = "not equal to", less = "less than", greater = "greater than") res <- paste ("true", names(x$null.value), "is", alt.char, x$null.value) } else { res <- paste (x$alternative, "\nnull values:\n", x$null.value) } } else { res <- (x$alternative) } } res } rkward-0.6.4/rkward/rbackend/rpackages/rkward/R/ver.R0000664000175000017500000000023412633754364022016 0ustar thomasthomas# DO NOT CHANGE THIS FILE MANUALLY! # version number will be updated by cmake, see # rkward/SetVersionNumber.cmake #' @export ".rk.app.version" <- "0.6.4" rkward-0.6.4/rkward/rbackend/rpackages/rkward/R/internal.R0000644000175000017500000003766012455741221023037 0ustar thomasthomas# check the context in which this package is loaded #' @export .onAttach <- function(...) { .rk.inside.rkward.session(warn = TRUE) } # this function shall test if the rkward package was loaded in a running RKWard session #' @export .rk.inside.rkward.session <- function(warn = FALSE){ inside.rkward <- is.loaded("rk.do.generic.request") if(isTRUE(warn) & !isTRUE(inside.rkward)){ warning("You've loaded the package 'rkward', but RKWard doesn't appear to be running. If this causes trouble, try detach(\"package:rkward\").", call. = FALSE) } return(inside.rkward) } #' @export ".rk.get.meta" <- function (x) { y <- attr (x, ".rk.meta"); c (names (y), as.character (y)) } #' @export ".rk.set.meta" <- function (x, m) { eval (substitute (attr (x, ".rk.meta") <<- m)) } ".rk.set.invalid.field" <- function (x, row, value) { l <- attr (x, ".rk.invalid.fields"); if (is.null (l)) l <- list (); l[[as.character(row)]] <- value; if (length (l) == 0) l <- NULL eval (substitute (attr (x, ".rk.invalid.fields") <<- l)) } #' Work around some peculiarities in R's handling of levels #' @export ".rk.set.levels" <- function (var, levels) { if (is.factor (var)) { if (is.null (levels)) levels <- NA # must never set NULL levels on a factor old_l <- levels (var) # using attr (..., "levels) instead of levels (...) in order to bypass checking eval (substitute (attr (var, "levels") <<- levels)) if ((length (var) > 0) && (is.null (old_l) || ((length (old_l) == 1L) && is.na (old_l[1L])))) { # if levels were empty, this means var is all NAs. R will set all to first level, instead, in some cases len <- length (var) eval(substitute(var[1:len] <<- NA)) } } else { eval (substitute (attr (var, "levels") <<- levels)) } } #' @export ".rk.set.invalid.fields" <- function (x, set, values, clear) { l <- attr (x, ".rk.invalid.fields"); if (is.null (l)) l <- list (); if (!missing (set)) { set <- as.character (set) l[set] <- as.character (values) } if (!missing (clear)) { l[as.character (clear)] <- NULL } if (length (l) == 0) l <- NULL eval (substitute (attr (x, ".rk.invalid.fields") <<- l)) } #' @export ".rk.data.frame.insert.row" <- function (x, index=0) { if ((index == 0) || (index > dim (x)[1])) { # insert row at end eval (substitute (x[dim(x)[1]+1,] <<- c (NA))) } else { for (i in dim (x)[1]:index) { eval (substitute (x[i+1,] <<- x[i,])) } eval (substitute (row.names (x) <<- c (1:dim(x)[1]))) eval (substitute (x[index,] <<- c (NA))) } } # TODO: is there a nice way to get rid of a row without removing the meta-data? #' @export ".rk.data.frame.delete.row" <- function (x, index) { attriblist <- list () for (i in 1:dim (x)[2]) { attriblist[[names (x)[i]]] <- attributes (x[[i]]) } newx <- x[-index, , drop=FALSE] row.names (newx) <- c (1:dim(newx)[1]) for (i in 1:dim (newx)[2]) { attributes (newx[[i]]) <- attriblist[[names (newx)[i]]] } eval (substitute (x <<- newx)) } # function below is only needed to ensure a nice ordering of the columns. Simply adding a new column would be much easier than this. #' @export ".rk.data.frame.insert.column" <- function (x, label, index=0) { column <- rep (as.numeric (NA), times=dim (x)[1]) if ((index == 0) || (index > dim (x)[2])) { # insert column at end eval (substitute (x[[label]] <<- column)) } else { for (i in dim (x)[2]:index) { eval (substitute (x[i+1] <<- x[[i]])) eval (substitute (names (x)[i+1] <<- names (x)[i])) } eval (substitute (x[index] <<- column)) eval (substitute (names (x)[index] <<- label)) } } #' @export ".rk.do.error" <- function () { # comment in R sources says, it may not be good to query options during error handling. But what can we do, if R_ShowErrorMessages is not longer exported? if (getOption ("show.error.messages")) { .Call ("rk.do.error", c (geterrmessage ()), PACKAGE="(embedding)"); } } #' @export ".rk.set.reply" <- function (x) .rk.variables$.rk.rkreply <- x #' @export ".rk.do.call" <- function (x, args=NULL) { .rk.set.reply (NULL) .Call ("rk.do.command", c (x, args), PACKAGE="(embedding)"); return (.rk.variables$.rk.rkreply) } #' @export ".rk.do.plain.call" <- function (x, args=NULL, synchronous=TRUE) { .Call ("rk.do.generic.request", c (x, args), isTRUE (synchronous), PACKAGE="(embedding)") } #' @export ".rk.find.package.pluginmaps" <- function (package, all.maps=FALSE) { if(isTRUE(all.maps)){ # look for all pluginmaps in the rkward folder pluginmaps <- sapply(package, function(this.package){ dir(system.file("rkward", package=this.package), pattern="*.pluginmap", full.names=TRUE) }) } else { # check if a main .pluginmap file is provided pluginmaps <- sapply(package, function(this.package){ system.file(file.path("rkward", paste(this.package, ".pluginmap", sep="")), package=this.package) }) } return(pluginmaps) } # Gather status information on installed and available packages. # Return value is used in class RKRPackageInstallationStatus of the frontend #' @export ".rk.get.package.intallation.state" <- function () { # fetch all status information available <- .rk.cached.available.packages () inst <- installed.packages (fields="Title") old <- as.data.frame (rk.old.packages (available=available), stringsAsFactors=FALSE) new <- new.packages (instPkgs=inst, available=available) # convert info to a more suitable format available <- as.data.frame (available, stringsAsFactors=FALSE) inst <- as.data.frame (inst, stringsAsFactors=FALSE) oldinst <- match (paste (old$Package, old$LibPath), paste (inst$Package, inst$LibPath)) # convert package names to position with in the installed packages info oldavail <- match (old$Package, available$Package) # convert package names to position with in the available packages info new <- match (new, available$Package) # same for new packages # as a side effect, we update the list of known installed packages in the frontend .rk.do.plain.call ("updateInstalledPackagesList", sort (unique (as.character (inst$Package))), synchronous=FALSE) list ("available" = list (available$Package, available$Title, available$Version, available$Repository, grepl ("rkward", available$Enhances)), "installed" = list (inst$Package, inst$Title, inst$Version, inst$LibPath, grepl ("rkward", inst$Enhances)), "new" = as.integer (new - 1), "old" = list (as.integer (oldinst - 1), as.integer (oldavail - 1)), "repos" = as.character (options("repos")$repos)) } # package information formats may - according to the help - be subject to change. Hence this function to cope with "missing" values # also it concatenates everything to a single vector, so we can easily get the whole structure with a single call #' @export ".rk.get.installed.packages" <- function () { x <- as.data.frame(installed.packages(fields="Title")) # does a package enhance RKWard, i.e. provide plugins? enhance.rk <- ifelse(is.na(x$Enhances), FALSE, grepl("rkward", x$Enhances)) # as a side effect, we update the list of known installed packages in the frontend .rk.do.plain.call ("updateInstalledPackagesList", sort (unique (as.character (x$Package))), synchronous=FALSE) # check for pluginmaps only in packages which enhance RKWard rk.load.pluginmaps (.rk.find.package.pluginmaps(x$Package[enhance.rk]), force.add=FALSE, force.reload=FALSE) return(list(Package=as.character(x$Package), Title=as.character(x$Title), Version=as.character(x$Version), LibPath=as.character(x$LibPath), EnhanceRK=as.logical(enhance.rk))) } # This function works like available.packages (with no arguments), but does simple caching of the result, and of course uses a cache if available. Cache is only used, if it is less than 1 hour old, and options("repos") is unchanged. #' @export ".rk.cached.available.packages" <- function () { x <- NULL if (exists ("available.packages.cache", envir=.rk.variables) && (!is.null (.rk.variables$available.packages.cache))) { if (.rk.variables$available.packages.cache$timestamp > (Sys.time () - 3600)) { if (all (.rk.variables$available.packages.cache$repos$repos == options ("repos")$repos)) { x <- .rk.variables$available.packages.cache$cache } } } if (is.null(x)) { x <- available.packages(fields="Title") .rk.variables$available.packages.cache <- list (cache = x, timestamp = Sys.time (), repos = options ("repos")) } return (x) } #".rk.init.handlers" <- function () { # options (warning.expression = expression ()) # .Internal (.addCondHands (c ("message", "warning", "error"), list (function (m) { .Call ("rk.do.condition", c ("m", conditionMessage (m))) }, function (w) { .Call ("rk.do.condition", c ("w", conditionMessage (w))) }, function (e) { .Call ("rk.do.condition", c ("e", conditionMessage (e))) }), globalenv (), NULL, TRUE)) #} # these functions can be used to track assignments to R objects. The main interfaces are .rk.watch.symbol (k) and .rk.unwatch.symbol (k). This works by copying the symbol to a backup environment, removing it, and replacing it by an active binding to the backup location #' @export ".rk.watched.symbols" <- new.env () #' @export ".rk.make.watch.f" <- function (k) { # we need to make sure, the functions we use are *not* looked up as symbols in .GlobalEnv. # else, for instance, if the user names a symbol "missing", and we try to resolve it in the # wrapper function below, evaluation would recurse to look up "missing" in the .GlobalEnv # due to the call to "if (!missing(value))". get <- base::get missing <- base::missing assign <- base::assign .rk.do.call <- rkward::.rk.do.call invisible <- base::invisible function (value) { if (!missing (value)) { assign (k, value, envir=.rk.watched.symbols) .Call ("rk.do.command", c ("ws", k), PACKAGE="(embedding)"); # NOTE: the above is essentially the same a # .rk.do.call ("ws", k); # only minimally faster. invisible (value) } else { get (k, envir=.rk.watched.symbols) } } } #' @export ".rk.watch.symbol" <- function (k) { f <- .rk.make.watch.f (k) .Call ("rk.copy.no.eval", k, globalenv(), .rk.watched.symbols, PACKAGE="(embedding)"); #assign (k, get (k, envir=globalenv ()), envir=.rk.watched.symbols) rm (list=k, envir=globalenv ()) .rk.makeActiveBinding.default (k, f, globalenv ()) invisible (TRUE) } # not needed by rkward but provided for completeness #' @export ".rk.unwatch.symbol" <- function (k) { rm (list=k, envir=globalenv ()) assign (k, .rk.watched.symbols$k, envir=globalenv ()) rm (k, envir=.rk.watched.symbols); invisible (TRUE) } #' @export ".rk.watch.globalenv" <- function () { newlist <- ls (globalenv (), all.names=TRUE) oldlist <- ls (.rk.watched.symbols, all.names=TRUE) for (old in oldlist) { # unwatch no longer present items if (!(old %in% newlist)) { rm (list=old, envir=.rk.watched.symbols); } } for (new in newlist) { # watch new items if (!(new %in% oldlist)) { .rk.watch.symbol (new) } } } #' @export ".rk.get.vector.data" <- function (x) { ret <- list (); ret$data <- as.vector (unclass (x)); ret$levels <- levels (x) if (is.null (ret$levels)) ret$levels <- "" i <- attr (x, ".rk.invalid.fields") ret$invalids <- as.character (c (names (i), i)); if (length (ret$invalids) == 0) ret$invalids <- "" ret } # Change storage type of x to mode newmode. # Keeps the .rk.meta attribute, and levels attributes, but the data is erased! #' @export ".rk.set.vector.mode" <- function (x, fun, envir=parent.frame ()) { y <- fun(rep(NA, length.out = length(x))) newattrs <- attributes(y) if (is.null (newattrs)) newattrs <- list () newattrs[[".rk.meta"]] <- attributes(x)[[".rk.meta"]] lvls <- attributes(x)[["levels"]] if (!is.null (lvls)) newattrs[["levels"]] <- lvls attributes(y) <- newattrs eval (substitute (x <- y), envir=envir) } #' @export ".rk.get.structure" <- function (x, name, envlevel=0, namespacename=NULL) { .Call ("rk.get.structure", x, as.character (name), as.integer (envlevel), namespacename, PACKAGE="(embedding)") } #' @export ".rk.try.get.namespace" <- function (name) { tryCatch (asNamespace (name), error = function(e) NULL) } #' @export ".rk.get.structure.global" <- function (name, envlevel=0, namespacename=NULL) { .Call ("rk.get.structure.global", as.character (name), as.integer (envlevel), namespacename, PACKAGE="(embedding)") } #' @export ".rk.get.slots" <- function (x) { slotnames <- methods::slotNames (class (x)) ret <- lapply (slotnames, function (slotname) slot (x, slotname)) names (ret) <- slotnames ret } #' @export ".rk.get.environment.children" <- function (x, envlevel=0, namespacename=NULL) { ret <- list () if (envlevel < 2) { # prevent infinite recursion lst <- ls (x, all.names=TRUE) if (is.null (namespacename)) { for (childname in lst) { ret[[childname]] <- .rk.get.structure (name=childname, envlevel=envlevel, envir=x) } } else { # for R 2.4.0 or greater: operator "::" works if package has no namespace at all, or has a namespace with the symbol in it ns <- tryCatch (asNamespace (namespacename), error = function(e) NULL) for (childname in lst) { misplaced <- FALSE if ((!is.null (ns)) && (!exists (childname, envir=ns, inherits=FALSE))) misplaced <- TRUE ret[[childname]] <- .rk.get.structure (name=childname, envlevel=envlevel, misplaced=misplaced, envir=x) } } } ret } # hidden, as this is not portable to different output formats #' @export ".rk.cat.output" <- function (x) { cat (x, file = rk.get.output.html.file(), append = TRUE) } #' @export ".rk.rerun.plugin.link" <- function (plugin, settings, label) { .rk.cat.output (paste ("", label, "", sep="")) } #' @export ".rk.make.hr" <- function () { .rk.cat.output ("
\n"); } # General purpose storage environment (which will hopefully never get locked by R) #' @export ".rk.variables" <- new.env () assign(".rk.active.device", 1, envir=.rk.variables) assign(".rk.output.html.file", NULL, envir=.rk.variables) assign(".rk.rkreply", NULL, envir=.rk.variables) assign("available.packages.cache", NULL, envir=.rk.variables) #' @export ".rk.backups" <- new.env () # where masking is not enough, we need to assign in the original environment / namespace. This can only be done after package loading, # so we have a separate function for that. #' @export ".rk.fix.assignments" <- function () { ## History manipulation function (overloads for functions by the same name in package utils) rk.replace.function ("loadhistory", as.environment ("package:utils"), function (file = ".Rhistory") { invisible (.rk.do.plain.call ("commandHistory", c ("set", readLines (file)))) }, copy.formals = FALSE) rk.replace.function ("savehistory", as.environment ("package:utils"), function (file = ".Rhistory") { invisible (writeLines (.rk.do.plain.call ("commandHistory", "get"), file)) }, copy.formals = FALSE) rk.replace.function ("timestamp", as.environment ("package:utils"), function (stamp = date(), prefix = "##------ ", suffix = " ------##", quiet = FALSE) { stamp <- paste(prefix, stamp, suffix, sep = "") .rk.do.plain.call ("commandHistory", c ("append", stamp)) if (!quiet) cat(stamp, sep = "\n") invisible(stamp) }, copy.formals = FALSE) ## Interactive menus rk.replace.function ("select.list", as.environment ("package:utils"), function () { # the "list" parameter was renamed to "choices" in R 2.11.0 if (!exists ("list", inherits=FALSE)) list <- choices # the "graphics" parameter was introduced in R 2.11.0, so we cannot rely on its existance if (!exists ("graphics", inherits=FALSE)) graphics <- TRUE if (graphics) { return (rk.select.list (list, preselect, multiple, title)) } # for text list, use the default implementation eval (body (.rk.backups$select.list)) }) rk.replace.function ("menu", as.environment ("package:utils"), function () { if (graphics) { res <- rk.select.list (choices, multiple=FALSE, title=title) return (match(res, choices, nomatch = 0L)) } # for text menus, use the default implementation eval (body (.rk.backups$menu)) }) # call separate assignments functions: if (exists (".rk.fix.assignments.graphics")) eval (body (.rk.fix.assignments.graphics)) # internal_graphics.R } rkward-0.6.4/rkward/rbackend/rpackages/rkward/R/rk.demo.R0000644000175000017500000000216412471622105022546 0ustar thomasthomas#' Opens an R demo script for editing #' #' \code{rk.demo} behaves similar to \code{\link{demo}}, but opens the demo #' script for editing, instead of sourcing it. Contrary to \code{\link{demo}}, #' the specification of a topic is mandatory. #' #' @param topic topic of the example #' @param package package(s) to search for the demo. If NULL (the default), all #' currently loaded packages are searched. #' @param lib.loc Library locations. #' @return Return \code{NULL}, unconditionally. #' @author Thomas Friedrichsmeier \email{rkward-devel@@kde.org} #' @seealso \code{\link{rk.edit.files}}, \code{\link{rk.show.files}}, #' \code{\link{demo}} #' @keywords utilities IO #' @rdname rk.demo #' @export #' @examples #' #' ## Not run #' rk.demo("graphics") "rk.demo" <- function (topic, package=NULL, lib.loc=NULL) { if (is.null (package)) { package <- .packages (lib.loc=lib.loc) } loc <- "" for (p in package) { loc <- system.file ("demo", paste (topic, ".R", sep=""), package=p, lib.loc=lib.loc) if (loc != "") break } if (loc == "") stop ("No demo found for topic'", topic, "'") rk.edit.files (loc, prompt=FALSE) } rkward-0.6.4/rkward/rbackend/rpackages/rkward/R/base_overrides.R0000644000175000017500000000421012455741221024200 0ustar thomasthomas # override makeActiveBinding: If active bindings are created in globalenv (), watch them properly .rk.makeActiveBinding.default <- base::makeActiveBinding #' @export "makeActiveBinding" <- function (sym, fun, env, ...) { if (identical (env, globalenv ())) { .rk.makeActiveBinding.default (sym, fun, .rk.watched.symbols, ...) f <- .rk.make.watch.f (sym) .rk.makeActiveBinding.default (sym, f, globalenv (), ...) } else { .rk.makeActiveBinding.default (sym, fun, env, ...) } } #' @export "require" <- function (package, quietly = FALSE, character.only = FALSE, ...) { if (!character.only) { package <- as.character(substitute(package)) } if (!base::require(as.character(package), quietly = quietly, character.only = TRUE, ...)) { .rk.do.call("require", as.character(package)) invisible(base::require(as.character(package), quietly = TRUE, character.only = TRUE, ...)) } else { invisible(TRUE) } } # overriding q, to ask via GUI instead. Arguments are not interpreted. #' @export "q" <- function (save = "default", status = 0, runLast = TRUE, ...) { # test if this is running in RKWard, otherwise pass through to the actual q() if (isTRUE(.rk.inside.rkward.session())){ res <- .rk.do.plain.call ("quit") if (length (res) && (res == "FALSE")) stop ("Quitting was cancelled") } else { base:::q(save = save, status = status, runLast = runLast) } } #' @export "quit" <- function (save = "default", status = 0, runLast = TRUE, ...) { q (save, status, runLast, ...) } #' @export "Sys.setlocale" <- function (category = "LC_ALL", locale = "", ...) { if (category == "LC_ALL" || category == "LC_CTYPE" || category == "LANG") { allow <- .rk.do.plain.call ("preLocaleChange", NULL) if (length (allow) && (allow == "FALSE")) stop ("Changing the locale was cancelled by user") ret <- base::Sys.setlocale (category, locale, ...) .Call ("rk.update.locale", PACKAGE="(embedding)") ret } else { base::Sys.setlocale (category, locale, ...) } } #' @export "setwd" <- function () { ret <- eval (body (base::setwd)) .rk.do.plain.call ("wdChange", base::getwd (), synchronous=FALSE) invisible (ret) } formals (setwd) <- formals (base::setwd) rkward-0.6.4/rkward/rbackend/rpackages/rkward/R/rk.filename-functions.R0000644000175000017500000002173112471622105025411 0ustar thomasthomas#' RKWard file names #' #' In RKWard the output is saved as a html file which is located at "~/.rkward" #' by default. (\bold{TODO}: make this platform free). The name of this html #' file can be retrieved and set using \code{rk.get.output.html.file} and #' \code{rk.set.output.html.file}. \code{rk.flush.output.html.file} will delete #' the current (or specified) html file, and re-initialize it. #' #' \code{rk.get.tempfile.name} returns a non-existing filename inside the #' directory of the output file. It is mainly used by \link{rk.graph.on} to #' create filenames suitable for storing images in the output. The filenames of #' the temporary files are of the form #' "\code{prefix}\emph{xyz}.\code{extension}". \code{rk.get.tempfile.name} is #' somewhat misnamed. For truly temporary files, \link{tempfile} is generally #' more suitable. #' #' \code{rk.get.workspace.url} returns the url of workspace file which has been #' loaded in RKWard, or NULL, if no workspace has been loaded. NOTE: This value #' is note affected by running \code{load} in R, only by loading R workspaces #' via the RKWard GUI. #' #' @aliases rk.get.tempfile.name rk.get.workspace.url rk.get.output.html.file #' rk.set.output.html.file #' @param prefix a string, used as a filename prefix when saving images to the #' output file #' @param extension a string, used as a filename extension when saving images #' to the output file #' @param x a string, giving the filename of the of the output file #' @param additional.header.contents NULL or an additional string to add to the HTML header section. #' This could be scripts or additional CSS definitions, for example. Note that #' \emph{nothing} will be added to the header, if the file already exists. #' @param flush.images. If true, any images used in the output file will be deleted as well. #' @param ask Logical: Whether to ask before flushing the output file. #' @return \code{rk.get.tempfile.name}, \code{rk.get.output.html.file}, and #' \code{rk.get.workspace.url} return a string while #' \code{rk.set.output.html.file} returns \code{NULL}. #' @author Thomas Friedrichsmeier \email{rkward-devel@@kde.org} #' @seealso \url{rkward://page/rkward_output}, \link{tempfile}, \link{file}, #' \link{rk.print} #' @keywords utilities IO #' @rdname rk.get.tempfile.name #' @examples #' #' testfile.name <- rk.get.tempfile.name(prefix="test", extension=".txt") #' testfile <- file(testfile.name) #' cat("This is a test\n", file=testfile) #' close(testfile) #' unlink(testfile.name) #' #' outfile <- rk.get.output.html.file() #' #' ## Not run #' rk.set.output.html.file("~/.rkward/another_file.html") #' rk.header("Output on a different output file") #' rk.show.html(rk.get.output.html.file()) #' rk.flush.output() #' rk.set.output.html.file(outfile) #' #' @export "rk.get.tempfile.name" <- function (prefix="image", extension=".jpg") { return (.rk.do.plain.call ("get.tempfile.name", c (prefix, extension))) } #' @export #' @rdname rk.get.tempfile.name "rk.get.workspace.url" <- function () { res <- .rk.do.plain.call ("getWorkspaceUrl") if (length (res)) res else NULL } #' @export #' @rdname rk.get.tempfile.name "rk.get.output.html.file" <- function () { return (.rk.variables$.rk.output.html.file) } #' @export #' @rdname rk.get.tempfile.name "rk.set.output.html.file" <- function (x, additional.header.contents = getOption ("rk.html.header.additions")) { stopifnot (is.character (x)) assign (".rk.output.html.file", x, .rk.variables) if (!file.exists (x)) { .rk.cat.output (paste ("\n", sep="")) .rk.cat.output (paste ("\nRKWard Output\n", .rk.do.plain.call ("getCSSlink"), sep="")) # the next part defines a JavaScript function to add individual results to a global table of contents menu in the document .rk.cat.output (paste ("\t\n", sep="")) # positioning of the TOC is done by CSS, default state is hidden # see $SRC/rkward/pages/rkward_output.css if (!is.null (additional.header.contents)) .rk.cat.output (as.character (additional.header.contents)) .rk.cat.output ("\n\n") # This initial output mostly to indicate the output is really there, just empty for now .rk.cat.output (paste ("\n
RKWard output initialized on", .rk.date (), "
\n")) # an empty
where the TOC menu gets added to dynamically, and a second one to toggle show/hide .rk.cat.output (paste ( "
\n", "\tHide TOC\n", "\tGo to top\n
", "\t\t\n\t\t\t1 •\n", "\t\t\t2 •\n", "\t\t\t3 •\n", "\t\t\t4\n\t\t\n", "\t\n
\n", "
\n", "\tShow TOC\n", "\tGo to top\n", "
\n", sep="")) } # needs to come after initialization, so initialization alone does not trigger an update during startup .rk.do.plain.call ("set.output.file", x, synchronous=FALSE) invisible (NULL) } # Internal helper function to extract file names of images used in html files. # Almost definitely, this could be simplified, but I'll leave that as an exercise to the reader ;-) # Note that this uses heuristics, rather than real parsing ".rk.get.images.in.html.file" <- function (file) { lines <- readLines (file) lines <- grep ("<(img|object)", lines, ignore.case=TRUE, value=TRUE) files <- character (0) for (line in lines) { slines <- strsplit (line, "<")[[1]] for (sline in slines) { sline <- toupper (sline) if (substring (sline, 0, 3) == "IMG") { parts <- strsplit (sline, "SRC")[[1]] if (length (parts) < 2) next parts <- strsplit (parts[2], "\"")[[1]] if (length (parts) < 2) next files <- c (files, parts[2]) } else if (substring (sline, 0, 6) == "OBJECT") { parts <- strsplit (sline, "DATA")[[1]] if (length (parts) < 2) next parts <- strsplit (parts[2], "\"")[[1]] if (length (parts) < 2) next files <- c (files, parts[2]) } } } files } #' @export #' @rdname rk.get.tempfile.name "rk.flush.output" <- function (x=rk.get.output.html.file (), flush.images=TRUE, ask=TRUE) { images <- character (0) if (flush.images) images <- .rk.get.images.in.html.file (x) desc <- x if (length (images)) { desc <- paste (x, ", along with ", length (images), " image files", sep="") } if (isTRUE (ask)) { if (!rk.show.question (paste ("Do you really want to flush the output file (", desc, ")?\nIt will not be possible to restore it.", sep=""))) stop ("Aborted by user") } unlink (x) try ( for (image in images) { unlink (image) } ) rk.set.output.html.file (x) } rkward-0.6.4/rkward/rbackend/rpackages/rkward/R/rkward-package.R0000644000175000017500000000141512455741221024073 0ustar thomasthomas#' Provides functions related to the RKWard GUI. #' #' \tabular{ll}{ #' Package: \tab rkward\cr #' Type: \tab Package\cr #' Version: \tab 0.6.3\cr #' Date: \tab 2014-10-30\cr #' Depends: \tab R (>= 2.9.0),methods\cr #' Encoding: \tab UTF-8\cr #' License: \tab GPL (>= 2)\cr #' LazyLoad: \tab yes\cr #' URL: \tab http://rkward.kde.org\cr #' } #' #' This package contains functions which are useful in combination with the RKWard GUI. Many of these #' functions only needed for the internal communication between RKWard and R, but some are also useful in user scripts. #' #' @aliases rkward-package #' @name rkward-package #' @docType package #' @title The rkward Package #' @author Thomas Friedrichsmeier, with contributions from RKWard-devel mailing list #' @keywords package NULL rkward-0.6.4/rkward/rbackend/rpackages/rkward/R/rk.workspace-functions.R0000644000175000017500000000561112471622105025626 0ustar thomasthomas#' Save or restore RKWard workplace #' #' \code{rk.save.workplace} can be used to save a representation of the RKWard #' workplace (i.e. which scripts, data edtiors and other windows are shown) to #' a file. \code{rk.restore.workplace} restores an RKWard workplace as saved by #' \code{rk.save.workplace}. #' #' If the \code{file} parameter is omitted (or \code{NULL}), a suitable #' filename is selected automatically. If a workspace has been loaded, this is #' the URL of the workspace with an appended \code{.rkworkplace}. Otherwise a #' filename in the RKWard directory, as generated by #' \link{rk.get.tempfile.name}. #' #' NOTE: Not all types of windows can be saved and restored. Esp. graphics #' device windows will not be restored (but WILL be closed by #' \code{rk.restore.workplace()}, if \code{close.windows} is TRUE). #' #' @aliases rk.save.workplace rk.restore.workplace #' @param file a character string giving the url of the file to save to, or #' NULL for automatic selection of a suitable file (see Details). #' @param description For internal use, only. A character string describing the #' workplace status to save. Generally, you should leave this as the default #' value (\code{NULL}). #' @param close.windows a logical; whether current windows should be closed #' before restoring. #' @return Both functions return \code{NULL}. #' @author Thomas Friedrichsmeier \email{rkward-devel@@kde.org} #' @seealso \url{rkward://page/rkward_for_r_users}, \link{rk.get.workspace.url} #' @keywords utilities #' @rdname rk.workplace #' @examples #' #' ## Not run #' rk.save.workplace () #' rk.restore.workplace () #' ## End not run #' #' @export "rk.save.workplace" <- function (file=NULL, description=NULL) { if (is.null (file)) { file <- URLdecode (rk.get.workspace.url ()) if (is.null (file)) file <- rk.get.tempfile.name (prefix="unsaved", extension=".RData") file <- paste (file, "rkworkplace", sep=".") } if (is.null (description)) lines <- .rk.do.plain.call ("workplace.layout", "get") else lines <- description writeLines (lines, file) } #' @rdname rk.workplace #' @export "rk.restore.workplace" <- function (file=NULL, close.windows=TRUE) { if (is.null (file)) { if (exists (".rk.workplace.save", envir=globalenv (), inherits=FALSE)) { # For backwards compatibility with workspaces saved by RKWard 0.5.4 and earlier. # TODO: remove in time. lines <- as.character (.GlobalEnv$.rk.workplace.save) rm (list = c (".rk.workplace.save"), envir=globalenv ()) } else { file <- URLdecode (rk.get.workspace.url ()) if (is.null (file)) file <- rk.get.tempfile.name (prefix="unsaved", extension=".RData") file <- paste (file, "rkworkplace", sep=".") } } close <- "close" if (!isTRUE (close.windows)) close <- "noclose" if (!exists ("lines", inherits=FALSE)) lines <- readLines (file) .rk.do.plain.call ("workplace.layout", c ("set", close, lines), synchronous=FALSE) invisible (NULL) } rkward-0.6.4/rkward/rbackend/rpackages/rkward/inst/0000755000175000017500000000000012455741221021640 5ustar thomasthomasrkward-0.6.4/rkward/rbackend/rpackages/rkward/inst/CITATION0000644000175000017500000000071612455741221023001 0ustar thomasthomasbibentry("Manual", title="rkward: Provides functions related to the RKWard GUI", author="Thomas Friedrichsmeier", year="2014", note="(Version 0.6.3)", url="http://rkward.kde.org", textVersion = paste("Friedrichsmeier, T. (2014). ", "rkward: Provides functions related to the RKWard GUI (Version 0.6.3). ", "Available from http://rkward.kde.org", sep=""), mheader = "To cite rkward in publications use:") rkward-0.6.4/rkward/rbackend/rpackages/rkward/man/0000755000175000017500000000000012633754364021450 5ustar thomasthomasrkward-0.6.4/rkward/rbackend/rpackages/rkward/man/rk.without.plot.history.Rd0000664000175000017500000000102612633754364026533 0ustar thomasthomas% Generated by roxygen2: do not edit by hand % Please edit documentation in R/public_graphics.R \name{rk.without.plot.history} \alias{rk.without.plot.history} \title{Run a (plotting) action, without recording anything in the plot history. Internally, the plot history option is turned off for the duration of the action.} \usage{ rk.without.plot.history(expr) } \description{ Run a (plotting) action, without recording anything in the plot history. Internally, the plot history option is turned off for the duration of the action. } rkward-0.6.4/rkward/rbackend/rpackages/rkward/man/rk.workplace.Rd0000664000175000017500000000345212633754364024347 0ustar thomasthomas% Generated by roxygen2: do not edit by hand % Please edit documentation in R/rk.workspace-functions.R \name{rk.save.workplace} \alias{rk.restore.workplace} \alias{rk.save.workplace} \title{Save or restore RKWard workplace} \usage{ rk.save.workplace(file = NULL, description = NULL) rk.restore.workplace(file = NULL, close.windows = TRUE) } \arguments{ \item{file}{a character string giving the url of the file to save to, or NULL for automatic selection of a suitable file (see Details).} \item{description}{For internal use, only. A character string describing the workplace status to save. Generally, you should leave this as the default value (\code{NULL}).} \item{close.windows}{a logical; whether current windows should be closed before restoring.} } \value{ Both functions return \code{NULL}. } \description{ \code{rk.save.workplace} can be used to save a representation of the RKWard workplace (i.e. which scripts, data edtiors and other windows are shown) to a file. \code{rk.restore.workplace} restores an RKWard workplace as saved by \code{rk.save.workplace}. } \details{ If the \code{file} parameter is omitted (or \code{NULL}), a suitable filename is selected automatically. If a workspace has been loaded, this is the URL of the workspace with an appended \code{.rkworkplace}. Otherwise a filename in the RKWard directory, as generated by \link{rk.get.tempfile.name}. NOTE: Not all types of windows can be saved and restored. Esp. graphics device windows will not be restored (but WILL be closed by \code{rk.restore.workplace()}, if \code{close.windows} is TRUE). } \examples{ ## Not run rk.save.workplace () rk.restore.workplace () ## End not run } \author{ Thomas Friedrichsmeier \email{rkward-devel@kde.org} } \seealso{ \url{rkward://page/rkward_for_r_users}, \link{rk.get.workspace.url} } \keyword{utilities} rkward-0.6.4/rkward/rbackend/rpackages/rkward/man/rk.edit.Rd0000664000175000017500000000412612633754364023304 0ustar thomasthomas% Generated by roxygen2: do not edit by hand % Please edit documentation in R/rk.edit-functions.R \name{rk.edit} \alias{rk.edit} \alias{rk.edit.files} \alias{rk.show.files} \alias{rk.show.html} \title{Edit / show an object / file} \usage{ rk.edit(x) rk.edit.files(name = NULL, file = "", title = NULL, prompt = TRUE) rk.show.files(file = file, header = file, title = NULL, delete.file = FALSE, prompt = TRUE, delete = delete.file) rk.show.html(url) } \arguments{ \item{x}{an object to edit.} \item{file}{character vector, filenames to show or edit.} \item{title}{character vector, of the same length as \code{file}; This can be used to give descriptive titles to each file, which will be displayed to the user.} \item{prompt}{logical of length 1. If TRUE (the default) a prompt is dialog is shown along with the files to show / edit.} \item{delete}{a logical (not NA), when \code{TRUE} the shown file(s) are deleted after closing.} \item{wtitle}{character vector, of length 1. This will be used as the window title.} } \value{ All functions described on this page return \code{NULL}, unconditionally. } \description{ \code{rk.edit} can be used to edit an object in the RKWard data editor. Currently only \link{data.frame}s are supported. This is similar to \link{edit.data.frame}, but the function returns immediately, and the object is edit asynchronously. } \details{ \code{rk.edit.files}, \code{rk.show.files}, and \code{rk.show.html} are equivalent to \link{edit}, \link{file.show}, and \link{browseURL}, respectively, but use RKWard as text/html editor/viewer. Generally it is recommended to use \link{edit}, \link{file.edit}, \link{file.show}, and \link{browseURL}, instead. These will call the respective RKWard functions by default, when run inside an RKWard session. (via \code{getOption("editor")}, and \code{getOption("browser")}. } \examples{ ## Not run x <- data.frame (a=c(1:3), b=c(2:4)) rk.edit(x) } \author{ Thomas Friedrichsmeier \email{rkward-devel@kde.org} } \seealso{ \code{\link{edit}}, \code{\link{file.edit}}, \code{\link{file.show}}, \code{\link{browseURL}} } \keyword{IO} \keyword{utilities} rkward-0.6.4/rkward/rbackend/rpackages/rkward/man/rkward-package.Rd0000664000175000017500000000150512633754364024625 0ustar thomasthomas% Generated by roxygen2: do not edit by hand % Please edit documentation in R/rkward-package.R \docType{package} \name{rkward-package} \alias{rkward-package} \title{The rkward Package} \description{ Provides functions related to the RKWard GUI. } \details{ \tabular{ll}{ Package: \tab rkward\cr Type: \tab Package\cr Version: \tab 0.6.3\cr Date: \tab 2014-10-30\cr Depends: \tab R (>= 2.9.0),methods\cr Encoding: \tab UTF-8\cr License: \tab GPL (>= 2)\cr LazyLoad: \tab yes\cr URL: \tab http://rkward.kde.org\cr } This package contains functions which are useful in combination with the RKWard GUI. Many of these functions only needed for the internal communication between RKWard and R, but some are also useful in user scripts. } \author{ Thomas Friedrichsmeier, with contributions from RKWard-devel mailing list } \keyword{package} rkward-0.6.4/rkward/rbackend/rpackages/rkward/man/rk.get.tempfile.name.Rd0000664000175000017500000000611712633754364025663 0ustar thomasthomas% Generated by roxygen2: do not edit by hand % Please edit documentation in R/rk.filename-functions.R \name{rk.get.tempfile.name} \alias{rk.flush.output} \alias{rk.get.output.html.file} \alias{rk.get.tempfile.name} \alias{rk.get.workspace.url} \alias{rk.set.output.html.file} \title{RKWard file names} \usage{ rk.get.tempfile.name(prefix = "image", extension = ".jpg") rk.get.workspace.url() rk.get.output.html.file() rk.set.output.html.file(x, additional.header.contents = getOption("rk.html.header.additions")) rk.flush.output(x = rk.get.output.html.file(), flush.images = TRUE, ask = TRUE) } \arguments{ \item{prefix}{a string, used as a filename prefix when saving images to the output file} \item{extension}{a string, used as a filename extension when saving images to the output file} \item{x}{a string, giving the filename of the of the output file} \item{additional.header.contents}{NULL or an additional string to add to the HTML header section. This could be scripts or additional CSS definitions, for example. Note that \emph{nothing} will be added to the header, if the file already exists.} \item{ask}{Logical: Whether to ask before flushing the output file.} \item{flush.images.}{If true, any images used in the output file will be deleted as well.} } \value{ \code{rk.get.tempfile.name}, \code{rk.get.output.html.file}, and \code{rk.get.workspace.url} return a string while \code{rk.set.output.html.file} returns \code{NULL}. } \description{ In RKWard the output is saved as a html file which is located at "~/.rkward" by default. (\bold{TODO}: make this platform free). The name of this html file can be retrieved and set using \code{rk.get.output.html.file} and \code{rk.set.output.html.file}. \code{rk.flush.output.html.file} will delete the current (or specified) html file, and re-initialize it. } \details{ \code{rk.get.tempfile.name} returns a non-existing filename inside the directory of the output file. It is mainly used by \link{rk.graph.on} to create filenames suitable for storing images in the output. The filenames of the temporary files are of the form "\code{prefix}\emph{xyz}.\code{extension}". \code{rk.get.tempfile.name} is somewhat misnamed. For truly temporary files, \link{tempfile} is generally more suitable. \code{rk.get.workspace.url} returns the url of workspace file which has been loaded in RKWard, or NULL, if no workspace has been loaded. NOTE: This value is note affected by running \code{load} in R, only by loading R workspaces via the RKWard GUI. } \examples{ testfile.name <- rk.get.tempfile.name(prefix="test", extension=".txt") testfile <- file(testfile.name) cat("This is a test\\n", file=testfile) close(testfile) unlink(testfile.name) outfile <- rk.get.output.html.file() ## Not run rk.set.output.html.file("~/.rkward/another_file.html") rk.header("Output on a different output file") rk.show.html(rk.get.output.html.file()) rk.flush.output() rk.set.output.html.file(outfile) } \author{ Thomas Friedrichsmeier \email{rkward-devel@kde.org} } \seealso{ \url{rkward://page/rkward_output}, \link{tempfile}, \link{file}, \link{rk.print} } \keyword{IO} \keyword{utilities} rkward-0.6.4/rkward/rbackend/rpackages/rkward/man/rk.screen.device.Rd0000664000175000017500000000074612633754364025100 0ustar thomasthomas% Generated by roxygen2: do not edit by hand % Please edit documentation in R/internal_graphics.R \name{rk.screen.device} \alias{rk.screen.device} \title{DEPRECATED: \code{rk.screen.device} is obsolete. It simply calls \code{dev.new()} in this version of RKWard.} \usage{ rk.screen.device(...) } \description{ Depending on your use case, you should use \code{dev.new()}, \code{RK()} or \code{rk.embed.device()}, instead. } \seealso{ \link{dev.new}, \link{RK}, \link{rk.embed.device} } rkward-0.6.4/rkward/rbackend/rpackages/rkward/man/rk.call.plugin.Rd0000664000175000017500000000557512633754364024600 0ustar thomasthomas% Generated by roxygen2: do not edit by hand % Please edit documentation in R/rk.plugin-functions.R \name{rk.call.plugin} \alias{rk.call.plugin} \title{Call built-in RKWard plugin(s)} \usage{ rk.call.plugin(plugin, ..., submit.mode = c("manual", "auto", "submit")) } \arguments{ \item{plugin}{character string, giving the name of the plugin to call. See Details.} \item{submit.mode}{character string, specifying the submission mode: \code{"manual"} will open the plugin GUI and leave it to the user to submit it manually, \code{"auto"} will try to submit the plugin, if it can be submitted with the current settings (i.e. if the "Submit"-button is enabled after applying all specified parameters). If the plugin cannot be submitted, with the current settings, it will behave like \code{"manual"}. \code{"submit"} is like \code{"auot"}, but will close the plugin, and generate an error, if it cannot be submitted. \code{"manual"} will always return immediately, \code{"auto"} may or may not return immediately, and \code{"submit"} will always wait until the plugin has been run, or produce an error.} \item{\dots}{arguments passed to the \code{plugin}} } \value{ \code{rk.call.plugin} returns \code{TRUE} invisibly. } \description{ \code{rk.call.plugin} provides a high level wrapper to call any plugin available in RKWard. The exact string to be used as \code{plugin}, and the list of arguments available for a particular plugin, are generally not transparent to the user. \code{rk.list.plugins} can be used to obtain a list of current plugins. For plugin arguments, it is recommended to run the plugin, and inspect the "Run again" link that is generated on the output. } \details{ \bold{Warning}: Using \code{rk.call.plugin}, especially with submit.modes \code{"auto"} or \code{"submit"} to program a sequence of analyses has important drawbacks. First, the semantics of plugins are not guaranteed to remain unchanged across different versions of RKWard, thus your code may stop working after an upgrade. Second, your code will not be usable outside of an RKWard session. Consider copying the generated code for each plugin, instead. The primary use-cases for \code{rk.call.plugin} are automated tests, cross-references, and scripted tutorials. \bold{Note}: Even when using \code{"submit.mode=submit"}, the plugin code is run in the global context. Any local variables of the calling context are not available to the plugin. } \examples{ ## "t_test_two_vars" plugin: ## see the output: Windows->Show Output local({ x1 <- rnorm (100) x2 <- rnorm (100, 2) rk.call.plugin ("rkward::t_test_two_vars", confint.state="1", conflevel.real="0.95", hypothesis.string="greater", paired.state="0", varequal.state="0", x.available="x1", y.available="x2", submit.mode="submit") }) } \author{ Thomas Friedrichsmeier \email{rkward-devel@kde.org} } \seealso{ \code{\link{rk.results}}, \url{rkward://page/rkward_output} } \keyword{utilities} rkward-0.6.4/rkward/rbackend/rpackages/rkward/man/rk.replace.function.Rd0000664000175000017500000000346712633754364025625 0ustar thomasthomas% Generated by roxygen2: do not edit by hand % Please edit documentation in R/rk.replace.function.R \name{rk.replace.function} \alias{rk.replace.function} \title{Replace a function inside its package environment / namespace} \usage{ rk.replace.function(functionname, environment, replacement, copy.formals = TRUE) } \arguments{ \item{functionname}{name of the function to be replaced (character).} \item{environment}{package environment or namespace, where replacment should be done.} \item{replacement}{the replacement. This should be a function.} \item{copy.formals}{logical; whether to copy the \code{\link{formals}} from the original function.} } \value{ Returns \code{NULL}, invisibly, unconditionally. } \description{ \code{rk.replace.function} can be used to replace a function inside a different package / namespace. It is mainly intended for internal usage inside rkward, e.g. to replace \code{menu} and \code{select.list} with appropriate GUI implementations. } \details{ The original function is assigned to the environment \code{rkward::.rk.backups} with the same name as the original, and can be referred to from the replacement. WARNING: This mechanism does not support several subsequent replacments of the same function, nor does it support replacement of generics. \bold{WARNING}: This function can be used to alter - and disrupt - internal functions in arbitrary ways. You better know what you are doing. \bold{WARNING}: Does not work well on generics! } \examples{ ## Not run rk.replace.function ("history", as.environment ("package:utils"), function () { cat ("This is what you typed:\\n") eval (body (.rk.backups$history)) }) ## End not run } \author{ Thomas Friedrichsmeier \email{rkward-devel@kde.org} } \seealso{ \code{\link{assignInNamespace}}, \code{\link{debug}} } \keyword{IO} \keyword{utilities} rkward-0.6.4/rkward/rbackend/rpackages/rkward/man/rk.demo.Rd0000664000175000017500000000161712633754364023305 0ustar thomasthomas% Generated by roxygen2: do not edit by hand % Please edit documentation in R/rk.demo.R \name{rk.demo} \alias{rk.demo} \title{Opens an R demo script for editing} \usage{ rk.demo(topic, package = NULL, lib.loc = NULL) } \arguments{ \item{topic}{topic of the example} \item{package}{package(s) to search for the demo. If NULL (the default), all currently loaded packages are searched.} \item{lib.loc}{Library locations.} } \value{ Return \code{NULL}, unconditionally. } \description{ \code{rk.demo} behaves similar to \code{\link{demo}}, but opens the demo script for editing, instead of sourcing it. Contrary to \code{\link{demo}}, the specification of a topic is mandatory. } \examples{ ## Not run rk.demo("graphics") } \author{ Thomas Friedrichsmeier \email{rkward-devel@kde.org} } \seealso{ \code{\link{rk.edit.files}}, \code{\link{rk.show.files}}, \code{\link{demo}} } \keyword{IO} \keyword{utilities} rkward-0.6.4/rkward/rbackend/rpackages/rkward/man/rk.graph.on.Rd0000664000175000017500000000441412633754364024073 0ustar thomasthomas% Generated by roxygen2: do not edit by hand % Please edit documentation in R/public_graphics.R \name{rk.graph.on} \alias{rk.graph.off} \alias{rk.graph.on} \title{Plot graphics to HTML output file} \usage{ rk.graph.on(device.type = getOption("rk.graphics.type"), width = getOption("rk.graphics.width"), height = getOption("rk.graphics.height"), quality, ...) rk.graph.off() } \arguments{ \item{device.type}{Type of device to create / graphics format to use. Currently, supported devices are "PNG", "SVG", or "JPG". The default is to use the format configured in Settings -> Configure RKWard -> Output.} \item{width}{Width of graphics in pixels. The default is to use the width configured in Settings -> Configure RKWard -> Output.} \item{height}{Height of graphics in pixels. The default is to use the heigth configured in Settings -> Configure RKWard -> Output.} \item{quality}{For device.type "JPG", quality of the JPG file from 0 to 100. The default is to use the quality configured in Settings -> Configure RKWard -> Output.} \item{...}{Further options will be passed to the graphics device used internally. \bold{Warning}: It is advised to use \code{rk.graph.off} and \bold{not} \code{dev.off} to close the device opened by \code{rk.graph.on}. \code{dev.print(device = rk.graph.on)} is a \bold{wrong} usage for this "device," and will result in errors.} } \description{ \code{rk.graph.on()} creates an R device that will plot to the output html page in RKWard (\url{rkward://page/rkward_output}). The default settings for \code{device.type}, \code{width}, \code{height}, and \code{quality} can be modified from Settings -> Configure RKWard -> Output. \code{rk.graph.off()} closes the device that was opened by \code{rk.graph.on}. } \examples{ require (rkward) ## Plot directly to the output (html) file, by-passing screen device: rk.graph.on ("JPG", 480, 480, 75) plot (rnorm (100)) rk.graph.off () ## Copy the displayed plot to the output: plot (rnorm (100)) dev.copy (device = rk.graph.on) rk.graph.off () ## WRONG USAGE: not run: #plot (rnorm (100)) #dev.print (device = rk.graph.on) } \author{ Thomas Friedrichsmeier \email{rkward-devel@kde.org} } \seealso{ \link{rk.results} \link{rk.print} \link{rk.get.output.html.file} \link{dev.off} \link{svg} \link{png} \link{jpg} } \keyword{devices} rkward-0.6.4/rkward/rbackend/rpackages/rkward/man/RKdevice.Rd0000664000175000017500000000257012633754364023441 0ustar thomasthomas% Generated by roxygen2: do not edit by hand % Please edit documentation in R/public_graphics.R \name{RK} \alias{RK} \title{Plot graphics to RKWard native device} \usage{ RK(width = getOption("rk.screendevice.width"), height = getOption("rk.screendevice.height"), pointsize = 12, family = NULL, bg = "white", title = "", antialias = TRUE) } \arguments{ \item{width}{Width of the device in inches. The default is to use the heigth configured in Settings -> Configure RKWard -> Onscreen Graphics.} \item{height}{Height of the device in inchesgraphics in pixels. The default is to use the heigth configured in Settings -> Configure RKWard -> Onscreen Graphics.} \item{pointsize}{Default pointsize} \item{family}{Default font family. This can be a character vector of length 1 or 2. The second value is used for plotting symbols. Effectively the default is c("Helvetica", "Symbol"). A wide variety of sepcification is supported, including the device independent fonts names "sans", "serif", and "mono"} \item{bg}{Background color.} \item{title}{Window title.} \item{antialias}{Antialiasing. Can be turned off for somewhat faster drawing.} } \description{ \code{RK()} creates an R on-screen device that will be rendered in the RKWard frontend. The default settings for \code{width}, and \code{height} can be modified from Settings -> Configure RKWard -> Onscreen Graphics. } \keyword{devices} rkward-0.6.4/rkward/rbackend/rpackages/rkward/man/public.Rd0000644000175000017500000000215612455741221023207 0ustar thomasthomas\name{test} \alias{test} %- Also NEED an '\alias' for EACH other topic documented here. \title{ ~~function to do ... ~~ } \description{ ~~ A concise (1-5 lines) description of what the function does. ~~ } \usage{ test(x) } %- maybe also 'usage' for other objects documented here. \arguments{ \item{x}{ ~~Describe \code{x} here~~ } } \details{ ~~ If necessary, more details than the __description__ above ~~ } \value{ ~Describe the value returned If it is a LIST, use \item{comp1 }{Description of 'comp1'} \item{comp2 }{Description of 'comp2'} ... } \references{ ~put references to the literature/web site here ~ } \author{ ~~who you are~~ } \note{ ~~further notes~~ } %- ~Make other sections like Warning with \section{Warning }{....} ~ %- \seealso{ ~~objects to See Also as \code{\link{~~fun~~}}, ~~~ } \examples{ ##---- Should be DIRECTLY executable !! ---- ##-- ==> Define data, use random, ##-- or do help(data=index) for the standard data sets. ## The function is currently defined as function (x) { x } } \keyword{ ~kwd1 }% at least one, from doc/KEYWORDS \keyword{ ~kwd2 }% __ONLY ONE__ keyword per line rkward-0.6.4/rkward/rbackend/rpackages/rkward/man/rk.label.Rd0000664000175000017500000000614612633754364023442 0ustar thomasthomas% Generated by roxygen2: do not edit by hand % Please edit documentation in R/rk.label-functions.R \name{rk.get.label} \alias{.rk.make.short.name} \alias{rk.get.description} \alias{rk.get.label} \alias{rk.get.short.name} \alias{rk.list} \alias{rk.list.labels} \alias{rk.list.names} \alias{rk.set.label} \title{Various label related utility functions} \usage{ rk.get.label(x) rk.set.label(x, label, envir = parent.frame()) rk.list.labels(x, fill = FALSE) rk.get.short.name(x) .rk.make.short.name(x) rk.get.description(..., paste.sep = NULL, is.substitute = FALSE) rk.list(...) rk.list.names(..., deparse.level = 2) } \arguments{ \item{x}{any R object} \item{label}{a string, to set the label attribute of an object} \item{envir}{an environment, where the attribute is evaluated} \item{fill}{a logical or character. See Details.} \item{paste.sep}{a string, used as the \code{collapse} argument for paste} \item{is.substitute}{a logical (not NA). See Details.} } \value{ \code{rk.set.label} returns the result of the evaluation of "setting the label" while the others return a character vector. } \description{ \code{rk.get.label} retrieves the rkward label (if any) of the given object. } \details{ \code{rk.set.label} sets the rkward label for the given object. \code{rk.list.labels} retrieves the rkward labels for a list of objects. Most importantly, this can be used for extracting all column labels in a \code{data.frame}, conveniently. The parameter \code{fill} controls, what happens, when no rkward labels have been assigned. The default (\code{FALSE}) is to return empty strings for any missing labels. For \code{fill=TRUE}, missing labels will be filled with the short names of the object. You can also pass a character vector of default labels to use as the \code{fill} parameter. \code{rk.get.short.name} creates a short name for the given object. \code{rk.get.description} creates descriptive string(s) for each of the arguments in "\code{\dots{}}"; collapsing into a single string using \code{paste.sep} (if not NULL). If \code{is.substitute=TRUE}, the arguments will be deparsed, first, which can be useful when using \code{rk.get.description} inside a function. \code{rk.list.names} returns the names of the arguments passed as \code{...}; when using \code{rk.list.names} inside a function, it may be necessary to increase the \code{deparse.level} level. \code{rk.list} returns a list of its arguments, with \code{names} set as returned by \code{rk.get.description()}. This can be used as a drop-in replacement for \code{\link{list}}. } \examples{ x <- data.frame(a=c(1:3), b=c(2:4)) rk.set.label(x[["a"]], "First column") rk.get.short.name (x$a) # "x$a" rk.get.label (x$a) # "First column" rk.get.description (x$a) # "x$a (First column)" rk.list.labels (x) # "First column" "" rk.list.labels (x, TRUE) # "First column" "b" rk.list.names (x, x$a, x$b) # "x" "x$a" "x$b" names (rk.list (x$a, x$b)) # "x$a (First column)" "x$b" } \author{ Thomas Friedrichsmeier \email{rkward-devel@kde.org} } \keyword{utilities} rkward-0.6.4/rkward/rbackend/rpackages/rkward/man/rk.record.plot.Rd0000644000175000017500000001072312471622105024573 0ustar thomasthomas\name{rk.record.plot} \alias{rk.record.plot} \alias{rk.toggle.plot.history} \alias{rk.first.plot} \alias{rk.previous.plot} \alias{rk.next.plot} \alias{rk.last.plot} \alias{rk.goto.plot} \alias{rk.force.append.plot} \alias{rk.removethis.plot} \alias{rk.clear.plot.history} \alias{rk.show.plot.info} \alias{rk.verify.plot.hist.limits} \alias{rk.plot.history.summary} \alias{rk.duplicate.device} \title{Record screen device history and duplicate a screen device} \description{ TODO TODO } \usage{ rk.toggle.plot.history(x = TRUE) rk.first.plot(devId = dev.cur()) rk.previous.plot(devId = dev.cur()) rk.next.plot(devId = dev.cur()) rk.last.plot(devId = dev.cur()) rk.goto.plot(devId = dev.cur(), index=1) rk.force.append.plot(devId = dev.cur()) rk.removethis.plot(devId = dev.cur()) rk.clear.plot.history() rk.show.plot.info(devId = dev.cur()) rk.plot.history.summary(which = NULL, type = c("devices", "history")) rk.duplicate.device(devId = dev.cur()) } \arguments{ \item{devId, which}{ an integer, the screen device on which an action is to be performed.} \item{x}{ a logical (not NA), whether to enable the screen device history.} \item{index}{ an integer, which plot to jump to?} \item{type}{ a string, either \code{"devices"} or \code{"history"}, the type of summary to be printed.} } \details{ TODO TODO \code{rk.record.plot} is an environment to store the screen device history. You should not use the functions / variables in this environment directly, instead use the many wrapper functions as described below. \code{rk.toggle.plot.history} enables or disables the screen device history. You should not use this function. Instead use the checkbox in Settings->Configure RKWard->Onscreen Graphics->Screen device history. After the needed initialization / clean up, it sets the option variable \code{"rk.enable.graphics.history"} to \code{x}. \code{rk.first.plot}, \code{rk.previous.plot}, \code{rk.next.plot}, \code{rk.last.plot} these functions provide browing actions to respective plots saved in the history on the specified device (\code{devId}). \code{rk.goto.plot} this function provides a one step jump action to the plot specified by \code{index} on the specified device (\code{devId}). \code{rk.force.append.plot} forcefully append the currently displayed plot to the history. This function ignores the type of plot (graphics / lattice) and by-passes the general recording mechanism, as a result the plot call can not be identified. \code{rk.removethis.plot} removes the plot displayed on the specified device from history. If there are more than one device showing the same plot then removing from one device does not wipe it from the other devices. They can be re-added to the history from the other devices. \code{rk.clear.plot.history} clears the screen device history. \code{rk.show.plot.info} when available, shows some extra information regarding the displayed plot on the specified device. \code{rk.plot.history.summary} provides some summaries of the screen device history. \code{type = "devices"} provides summary of all or one device(s) depending on whether \code{which = NULL} or \code{which} is a single number identifying the device. \code{type = "history"} provides summary of the entire stored history. \code{rk.duplicate.device} duplicates the specified screen device. The plot on the new device behaves independently of the one it was duplicated from. } \value{ Except those mentioned below, the rest return \code{NULL} invisibly. \code{rk.plot.history.summary} returns a data.frame with some messages. \code{rk.duplicate.device} returns the value of a \code{dev.copy} call. } \author{Prasenjit Kapat \email{rkward-devel@kde.org}} \section{Warning}{ TODO TODO } \seealso{ \code{\link{recordPlot}}, \code{\link{replayPlot}} \code{\link{print.trellis}}, \code{\link{trellis.last.object}} \url{rkward://page/rkward_plot_history} } \examples{ ## Example for this? require (rkward) .L. <- getOption ("rk.graphics.hist.max.length") local ({ options ("rk.graphics.hist.max.length" = 150) x <- seq(-2*pi,2*pi,length=400) xlim <- range(x); ylim <- c(-1,1) n <- 100; for (i in seq_len (n)) { plot(x, sin(x-(i-1)*4*pi/n), type='l', xlim=xlim, ylim=ylim, bty='n', xlab='', ylab='', xaxt='n', yaxt='n') } }) rk.first.plot () for (i in 1:(rk.record.plot$sP.length-1)) rk.next.plot () ## Not run rk.clear.plot.history () options ("rk.graphics.hist.max.length" = .L.) } \keyword{device} \keyword{dynamic} \keyword{environment} rkward-0.6.4/rkward/rbackend/rpackages/rkward/man/rk.load.pluginmaps.Rd0000664000175000017500000000333412633754364025454 0ustar thomasthomas% Generated by roxygen2: do not edit by hand % Please edit documentation in R/rk.plugin-functions.R \name{rk.load.pluginmaps} \alias{rk.load.pluginmaps} \title{(Re-)load the given pluginmap files into the RKWard GUI} \usage{ rk.load.pluginmaps(pluginmap.files = NULL, force.add = TRUE, force.reload = TRUE) } \arguments{ \item{pluginmap.files}{a character vector of file names to add. This may be left empty, if the only desired effect is to reload all active pluginmaps.} \item{force.add}{logical. Whether the pluginmap files should also be added, if they had been previously de-selected in the settings menu, and regardless of their priority setting. In scripted usage, this should generally be set to FALSE.} \item{force.reload}{logical. By default the active pluginmaps are reloaded, only if any new ones were added. If set to TRUE, pluginmaps are reloaded in any case. In scripted usage, this should generally be set to FALSE. NOTE: Since a reload always means reloading _all_ active pluginmaps, This may be slow, and should be used with care. \bold{Note}: It is not necessary to reload the pluginmap, in order to refresh an individual plugin (e.g. after modifying the dialog), as plugins are not kept in memory after closing. Any currently opened plugins are not affected by this function.} } \description{ (Re-)load the given pluginmap files into the RKWard GUI } \examples{ ## NOT RUN ## reload all active pluginmaps rk.load.pluginmaps() ## END NOT RUN } \author{ Thomas Friedrichsmeier \email{rkward-devel@kde.org} } \seealso{ \code{\link{rk.call.plugin}}, @seealso \code{\link{rkwarddev::rk.plugin.skeleton}} } \keyword{utilities} rkward-0.6.4/rkward/rbackend/rpackages/rkward/man/rk.switch.frontend.language.Rd0000664000175000017500000000201312633754364027251 0ustar thomasthomas% Generated by roxygen2: do not edit by hand % Please edit documentation in R/rk.utility-functions.R \name{rk.switch.frontend.language} \alias{rk.switch.frontend.language} \title{Switch language / translation to use in the frontend.} \usage{ rk.switch.frontend.language(LANG = "C") } \arguments{ \item{LANG}{language code to use. "C" for no translation, i.e. generally English} } \description{ This feature is mostly intended for the purpose of automated testing, which needs a defined language to work. It might also be useful for translators, or e.g. to look up some terms untranslated in special cases where you are more familiar with the English terms than your native language terms. Note that this will only strings that are translated after the call., only those which get translated after the call. Most new dialogs you open, and importantly new plugin dialogs should show strings in the new lanuage, however. } \details{ To change the language in the backend, use \code{Sys.setenv(LANGUAGE=...)} or \code{Sys.setlocale()}. } rkward-0.6.4/rkward/rbackend/rpackages/rkward/man/rk.sync.Rd0000664000175000017500000000246512633754364023337 0ustar thomasthomas% Generated by roxygen2: do not edit by hand % Please edit documentation in R/rk.sync-functions.R \name{rk.sync} \alias{rk.sync} \alias{rk.sync.global} \title{Sync R object(s)} \usage{ rk.sync(x) rk.sync.global() } \arguments{ \item{x}{any R object to sync} } \value{ \code{NULL}, invisibly. } \description{ RKWard keeps an internal representation of objects in the R workspace. For objects in the \code{.GlobalEnv}, this representation is updated after each top-level statement. For the rare cases where this is not enough, \code{rk.sync} can be used to update the representation of a single object, \code{x}, while \code{rk.sync.global} scans the \code{.GlobalEnv} for new and removed objects, and updates as appropriate. } \details{ These functions are rarely needed outside automated testing. However, rk.sync() can be useful, if an object outside the \code{.GlobalEnv} has changed, since this will not be detected automatically. Also, by default RKWard does not recurse into environments when updating its representation of objects. rk.sync() can be used, here, to inspect the objects inside environments (see examples). } \examples{ rk.sync (rkward::rk.record.plot) } \author{ Thomas Friedrichsmeier \email{rkward-devel@kde.org} } \seealso{ \url{rkward://page/rkward_workspace_browser} } \keyword{misc} \keyword{utilities} rkward-0.6.4/rkward/rbackend/rpackages/rkward/man/rk.old.packages.Rd0000664000175000017500000000230112633754364024703 0ustar thomasthomas% Generated by roxygen2: do not edit by hand % Please edit documentation in R/rk.utility-functions.R \name{rk.old.packages} \alias{rk.old.packages} \title{Slightly smarter variant of old.packages()} \usage{ rk.old.packages(lib.loc = NULL, repos = getOption("repos"), contriburl = contrib.url(repos, type), instPkgs = installed.packages(lib.loc = lib.loc), method, available = NULL, checkBuilt = FALSE, type = getOption("pkgType")) } \value{ a character vector of packages which are really old } \description{ For most purposes, this function is identical to old.packages(). However, if the same package is installed to different locations, in different versions, old.packages() will treat each of these installations separately. Thus, e.g. if lib.loc == c("A", "B") and package X is installed in B at an outdated version 0.1, but in A at the most recent version 0.2, old.packages() will report package X at B as old. In contrast rk.old.packages() will recognize that the current version is higher up in the path, and not report package X as old. } \examples{ ## NOT RUN rk.old.packages() } \author{ Thomas Friedrichsmeier \email{rkward-devel@kde.org} } \keyword{attribute} \keyword{misc} \keyword{utilities} rkward-0.6.4/rkward/rbackend/rpackages/rkward/man/rk.record.commands.Rd0000664000175000017500000000133112633754364025430 0ustar thomasthomas% Generated by roxygen2: do not edit by hand % Please edit documentation in R/rk.utility-functions.R \name{rk.record.commands} \alias{rk.record.commands} \title{Start recording commands that are submitted from RKWard to R} \usage{ rk.record.commands(filename, include.all = FALSE) } \arguments{ \item{filename}{filename to write to (file will be truncated!).} \item{include.all}{By default, some types of command are filtered (internal synchronisation commands, and run again links). Should these be included?} } \description{ To stop recording, supply NULL or "" as filename. Currently used for the purpose of automated testing, only. Perhaps in the future this or a similar mechanism could also be added as a user feature. } rkward-0.6.4/rkward/rbackend/rpackages/rkward/man/rk.sessionInfo.Rd0000664000175000017500000000154412633754364024657 0ustar thomasthomas% Generated by roxygen2: do not edit by hand % Please edit documentation in R/rk.sessionInfo.R \name{rk.sessionInfo} \alias{rk.sessionInfo} \title{Print information on the RKWard session} \usage{ rk.sessionInfo() } \value{ Returns the object created by \code{sessionInfo()}, invisibly. Note that this includes only the information on the R portion of the session. } \description{ Gathers and prints information on the setup of the current RKWard session. In general, you should always include this information when reporting a bug in RKWard. } \details{ Typically, when reporting a bug, you should use \code{Help->Report Bug...} from the menu. Internally, this will call \code{rk.sessionInfo()}. } \examples{ rk.sessionInfo() } \author{ Thomas Friedrichsmeier \email{rkward-devel@kde.org} } \seealso{ \code{\link{sessionInfo}} } \keyword{misc} \keyword{utilities} rkward-0.6.4/rkward/rbackend/rpackages/rkward/man/rk.misc.Rd0000664000175000017500000000322112633754364023305 0ustar thomasthomas% Generated by roxygen2: do not edit by hand % Please edit documentation in R/rk.utility-functions.R \name{rk.rename.in.container} \alias{rk.make.repos.string} \alias{rk.misc} \alias{rk.rename.in.container} \alias{rk.select.CRAN.mirror} \title{Miscellaneous utility functions} \usage{ rk.rename.in.container(x, old_name, new_name, envir = parent.frame()) rk.make.repos.string() rk.select.CRAN.mirror() } \arguments{ \item{x}{a data.frame or list.} \item{old_name}{a string, the name of the column or element to be renamed.} \item{new_name}{a string, the new name.} \item{envir}{an environment where \code{x} is available.} } \value{ \code{rk.rename.in.container} returns \code{NULL} on successfule renaming, otherwise an error. \code{rk.make.repos.string} returns a valid R expression as a character string which can then be parsed and evaluated. \code{rk.select.CRAN.mirror} returns the URL of the chosen mirror, as a string. } \description{ \code{rk.rename.in.container} renames a named object (column/element) in a data.frame/list without changing its position. } \details{ \code{rk.make.repos.string} just creates a R statement for \code{repos}. A typical user should not need to use this function. \code{rk.select.CRAN.mirror} is an in-house replacement for \code{\link{chooseCRANmirror}} without changing \code{options ("repos")}, permanently. It uses native KDE gui and provides more information on each mirror. } \examples{ ## rk.rename.in.container ir <- iris str (ir) rk.rename.in.container(ir, "Species", "Taxonomic.Group") str (ir) } \author{ Thomas Friedrichsmeier \email{rkward-devel@kde.org} } \keyword{attribute} \keyword{misc} \keyword{utilities} rkward-0.6.4/rkward/rbackend/rpackages/rkward/man/rk.results.Rd0000664000175000017500000001000712633754364024053 0ustar thomasthomas% Generated by roxygen2: do not edit by hand % Please edit documentation in R/rk.print-functions.R \name{rk.print} \alias{rk.describe.alternative} \alias{rk.header} \alias{rk.print} \alias{rk.print.code} \alias{rk.print.literal} \alias{rk.results} \title{Print objects and results to output} \usage{ rk.print(x, ...) rk.print.code(code) rk.header(title, parameters = list(), level = 1, toc = NULL) rk.results(x, titles = NULL, print.rownames) rk.print.literal(x) rk.describe.alternative(x) } \arguments{ \item{x}{any R object to be printed/exported. A suitable list in case of \code{rk.describe.alternative}.} \item{code}{a character vector (single string) of R code} \item{title}{a string, used as a header for the html output} \item{parameters}{a list, preferably named, giving a list of "parameters" to be printed to the output} \item{level}{an integer, header level. For example, \code{level=2} creates the header with \code{

} tag.} \item{toc}{If \code{NULL}, the default, \code{rk.header()} will automatically add headers h1 to h4 to the TOC menu of the output document. \code{TRUE} will always add the header, and \code{FALSE} will suppress it.} \item{titles}{a character vector, giving the column headers for a html table.} \item{print.rownames}{controls printing of rownames. TRUE to force printing, FALSE to suppress printing, omitted (default) to print rownames, unless they are plain row numbers.} } \value{ \code{rk.describe.alternatives} returns a string while all other functions return \code{NULL}, invisibly. } \description{ Various utilty functions which can be used to print or export R objects to the (html) output file. The output file can be accessed from Windows -> Show Output. Basically, these functions along with the ones described in \code{\link{rk.get.label}}, \code{\link{rk.get.tempfile.name}}, and \code{\link{rk.graph.on}} can be used to create a HTML report. } \details{ \code{rk.print} prints/exports the given object to the output (html) file using the \code{\link{HTML}} function. This requires the \code{R2HTML} package. Additional arguments in \code{...} are passed on to \code{\link{HTML}}. \code{rk.print.literal} prints/exports the given object using a \code{paste(x, collapse="\n")} construct to the output (html) file. \code{rk.print.code} applies syntax highlighting to the given code string, and writes it to the output (html) file. \code{rk.header} prints a header / caption, possibly with parameters, to the output file. See example. \code{rk.results} is similar to \code{rk.print} but prints in a more tabulated fashion. This has been implemented only for certain types of \code{x}: tables, lists (or data.frames), and vectors. See example. \code{rk.describe.alternatives} describes the alternative (H1) hypothesis of a \code{htest}. This is similar to \code{stats:::print.htext} and makes sense only when \code{x$alternatives} exists. } \examples{ require (rkward) require (R2HTML) ## see the output: Windows->Show Output ## stolen from the two-sample t-test plugin ;) local({ x1 <- rnorm (100) x2 <- rnorm (100, 2) nm <- rk.get.description (x1,x2) result <- t.test (x1, x2, alternative="less") rk.print.code ("result <- t.test (x1, x2, alternative=\\"less\\")") rk.header (result$method, parameters=list ("Comparing", paste (nm[1], "against", nm[2]), "H1", rk.describe.alternative (result), "Equal variances", "not assumed")) rk.print.literal ("Raw data (first few rows):") rk.print (head (cbind (x1,x2)), align = "left") rk.print.literal ("Test results:") rk.results (list ( 'Variable Name'=nm, 'estimated mean'=result$estimate, 'degrees of freedom'=result$parameter, t=result$statistic, p=result$p.value, 'confidence interval percent'=(100 * attr(result$conf.int, "conf.level")), 'confidence interval of difference'=result$conf.int )) }) } \author{ Thomas Friedrichsmeier \email{rkward-devel@kde.org} } \seealso{ \code{\link{HTML}}, \code{\link{rk.get.output.html.file}}, \code{\link{rk.get.description}}, \code{\link{rk.call.plugin}}, \url{rkward://page/rkward_output} } \keyword{utilities} rkward-0.6.4/rkward/rbackend/rpackages/rkward/man/rk.list.plugins.Rd0000664000175000017500000000440412633754364025011 0ustar thomasthomas% Generated by roxygen2: do not edit by hand % Please edit documentation in R/rk.plugin-functions.R \name{rk.list.plugins} \alias{rk.list.plugins} \alias{rk.set.plugin.status} \title{List of modify loaded plugins} \usage{ rk.list.plugins() rk.set.plugin.status(id, context = "", visible = TRUE) } \arguments{ \item{id}{vector of ids (character) of the plugins to modify} \item{context}{in which the plugin should be shown / hidden. This can either be "", meaning the plugin will be affected in all contexts it occurs in, or a character vector of the same length as id.} \item{visible}{logical, controlling whether the plugin should be shown (\code{TRUE}) or hidden (\code{FALSE}). Hidden plugins are essentially removed from the menu. They may still be accessible embedded into other plugins.} } \value{ \code{rk.list.plugins} returns a data.frame listing plugin ids, context, menu path (tab-separated), and label of the plugin. If a plugin is available in more than one context, it will be listed several times. The exact layout (number and order of columns) of this data.frame might be subject to change. However, the \bold{names} of the columns in the returned data.frame are expected to remain stable. \code{rk.set.plugin.status} returns \code{NULL}, invisibly \bold{Note}: Each call to \code{rk.set.plugin.status} will result in a complete rebuild of the menu (in the current implementation). While this should be hardly noticeable in interactive use, it could be an issue when changing the status of many plugins, programatically. In this case, make sure to do all changes in \bold{one} call to \code{rk.set.plugin.status}, rather than many separate calls. } \description{ \code{rk.list.plugins} returns the a list of all currently registered plugins (in loaded pluginmaps). \code{rk.set.plugin.status} allows to control the status of the given plugin(s). Currently, only visibility can be controlled. } \examples{ ## list all current plugins rk.list.plugins () ## NOT RUN ## hide t.test plugin rk.set.plugin.status ("rkward::t_test", visible=FALSE) ## END NOT RUN } \author{ Thomas Friedrichsmeier \email{rkward-devel@kde.org} } \seealso{ \code{\link{rk.call.plugin}} for invoking a plugin, programatically } \keyword{utilities} rkward-0.6.4/rkward/rbackend/rpackages/rkward/man/rk.show.messages.Rd0000664000175000017500000000603612633754364025147 0ustar thomasthomas% Generated by roxygen2: do not edit by hand % Please edit documentation in R/rk.KDE_GUI-functions.R \name{rk.show.message} \alias{rk.select.list} \alias{rk.show.message} \alias{rk.show.question} \title{Message boxes and selection list using native KDE GUI} \usage{ rk.show.message(message, caption = "Information", wait = TRUE) rk.show.question(message, caption = "Question", button.yes = "yes", button.no = "no", button.cancel = "cancel") rk.select.list(list, preselect = NULL, multiple = FALSE, title = NULL) } \arguments{ \item{message}{a string for the content of the message box.} \item{caption}{a string for title of the message box.} \item{wait}{a logical (not NA) indicating whether the R interpreter should wait for the user's action, or run it asynchronously.} \item{button.yes}{a string for the text label of the \bold{Yes} button. Can be an empty string (\code{""}), in which case the button is not displayed at all.} \item{button.no}{a string used for the text label of the \bold{No} button, similar to \code{button.yes}.} \item{list}{a vector, coerced into a character vector.} \item{multiple}{a logical (not NA), when \code{TRUE} multiple selection selection is allowed.} \item{title}{a string, for the window title of the displayed list} \item{button.canel}{a string used for the text label of the \bold{Cancel} button, similar to \code{button.yes}.} \item{preselct}{a vector, coerced into a character vector, items to be preselected.} } \value{ \code{rk.show.message} always returns \code{TRUE}, invisibly. \code{rk.show.question} returns \code{TRUE} for \bold{Yes}, \code{FALSE} for \bold{No}, and \code{NULL} for \bold{Cancel} actions. \code{rk.select.list} returns the value of \code{\link{select.list}}. } \description{ Multi-purpose pop-up message boxes and selection list using native KDE GUI elements. The message boxes can be used either to show some information or ask some question. The selection list can be used to get a vector of selected items. } \details{ For \code{rk.show.question}, the R interpreter always waits for the user's choice. \code{rk.select.list} replaces \code{utils::select.list} for the running session acting as a drop-in replacement for \code{tk_select.list}. Use \code{.rk.backups$select.list} for the original \code{utils::select.list} function (see Examples). } \examples{ require (rkward) ## Message boxes if (rk.show.question ("Question:\\nDo you want to know about RKWard?", button.yes = "Yes, I do!", button.no = "No, I don't care!", button.cancel = "")) { rk.show.message ("Message:\\nRKWard is a KDE GUI for R.", "RKWard Info") } else { rk.show.message ("You must be joking!", "RKWard Info", wait = FALSE) ## Run asynchronously } ## Selection lists: rk.select.list (LETTERS, preselect = c("A", "E", "I", "O", "U"), multiple = TRUE, title = "vowels") .rk.backups$select.list (LETTERS, preselect = c("A", "E", "I", "O", "U"), multiple = TRUE, title = "vowels") } \author{ Thomas Friedrichsmeier \email{rkward-devel@kde.org} } \seealso{ \code{\link{system}}, \code{\link{select.list}} } \keyword{utilities} rkward-0.6.4/rkward/rbackend/rpackages/rkward/man/rk.embed.device.Rd0000664000175000017500000000254712633754364024676 0ustar thomasthomas% Generated by roxygen2: do not edit by hand % Please edit documentation in R/public_graphics.R \name{rk.embed.device} \alias{rk.embed.device} \title{Embed non-RKWard device windows} \usage{ rk.embed.device(expr) } \arguments{ \item{expr}{Expression to evaluate.} } \description{ \code{rk.embed.device} evaluates the given expression, and if this has created a window on the screen, tries to embed it as an RKWard window. } \details{ Theoretically, \code{expr} can be any valid R expression. However typically this should be calls to X11(), Windows(), or, perhaps dev.copy(). Importantly, the expression should create exactly one new window for \code{rk.embed.device()} to work. Keep in mind, that this is not always the case for \code{plot(...)} and similar commands, which will re-use an existing plot window, if available. } \note{ \code{rk.embed.device()} will not work on all platforms (most importantly, not in most MacOSX binaries). Further, note that a captured \code{X11()} or \code{Windows} device may look similar to an \code{RK()} device, but is actually a very different thing. Capturing a window already "owned" by RKWard (importantly, \code{RK()} device windows) may lead to unexpected results, including crashes. } \examples{ ## Not run: rk.embed.device (grDevices::X11(title="X11 device window")) plot (rnorm (10)) } \seealso{ \link{RK} } rkward-0.6.4/rkward/rbackend/rpackages/rkward/man/rk.printer.device.Rd0000664000175000017500000000170612633754364025301 0ustar thomasthomas% Generated by roxygen2: do not edit by hand % Please edit documentation in R/public_graphics.R \name{rk.printer.device} \alias{rk.printer.device} \title{Device for printing using the KDE print dialog} \usage{ rk.printer.device(...) } \arguments{ \item{...}{arguments are passed to \code{\link{postscript}}} } \value{ Returns the name of the underlying temporary file, invisibly. } \description{ Creates a device operating on a temporary file (internally a \code{\link{postscript}}() device). When the device is closed, it is printed, automatically, using the KDE print dialog (if installed). } \details{ Typically this device is used with \code{\link{dev.print}}, as shown in the example, below. } \examples{ ## Not run: plot (rnorm (10)) dev.print (rk.printer.device) } \author{ Thomas Friedrichsmeier \email{rkward-devel@kde.org} } \seealso{ \code{\link{postscript}}, \code{\link{dev.print}}, \code{\link{rk.graph.on}} } \keyword{device} \keyword{utilities} rkward-0.6.4/rkward/rbackend/rpackages/rkward/NAMESPACE0000664000175000017500000000573412633754364022127 0ustar thomasthomas# Generated by roxygen2: do not edit by hand export(.onAttach) export(.rk.app.version) export(.rk.backups) export(.rk.cached.available.packages) export(.rk.callstack.info) export(.rk.cat.output) export(.rk.data.frame.delete.row) export(.rk.data.frame.insert.column) export(.rk.data.frame.insert.row) export(.rk.do.call) export(.rk.do.error) export(.rk.do.plain.call) export(.rk.find.package.pluginmaps) export(.rk.fix.assignments) export(.rk.fix.assignments.graphics) export(.rk.get.environment.children) export(.rk.get.installed.packages) export(.rk.get.meta) export(.rk.get.package.intallation.state) export(.rk.get.search.results) export(.rk.get.slots) export(.rk.get.structure) export(.rk.get.structure.global) export(.rk.get.vector.data) export(.rk.getHelp) export(.rk.getHelpBaseUrl) export(.rk.inside.rkward.session) export(.rk.killPreviewDevice) export(.rk.make.hr) export(.rk.make.short.name) export(.rk.make.watch.f) export(.rk.rerun.plugin.link) export(.rk.set.invalid.fields) export(.rk.set.levels) export(.rk.set.meta) export(.rk.set.reply) export(.rk.set.vector.mode) export(.rk.startPreviewDevice) export(.rk.try.get.namespace) export(.rk.unwatch.symbol) export(.rk.variables) export(.rk.watch.globalenv) export(.rk.watch.symbol) export(.rk.watched.symbols) export(RK) export(Sys.setlocale) export(X11) export(makeActiveBinding) export(q) export(quartz) export(quit) export(require) export(rk.call.plugin) export(rk.clear.plot.history) export(rk.demo) export(rk.describe.alternative) export(rk.duplicate.device) export(rk.edit) export(rk.edit.files) export(rk.embed.device) export(rk.first.plot) export(rk.flush.output) export(rk.force.append.plot) export(rk.get.description) export(rk.get.label) export(rk.get.output.html.file) export(rk.get.short.name) export(rk.get.tempfile.name) export(rk.get.workspace.url) export(rk.goto.plot) export(rk.graph.off) export(rk.graph.on) export(rk.header) export(rk.last.plot) export(rk.list) export(rk.list.labels) export(rk.list.names) export(rk.list.plugins) export(rk.load.pluginmaps) export(rk.make.repos.string) export(rk.next.plot) export(rk.old.packages) export(rk.plot.history.summary) export(rk.previous.plot) export(rk.print) export(rk.print.code) export(rk.print.literal) export(rk.printer.device) export(rk.record.commands) export(rk.record.plot) export(rk.relative.src.line) export(rk.removethis.plot) export(rk.rename.in.container) export(rk.replace.function) export(rk.restore.workplace) export(rk.results) export(rk.save.workplace) export(rk.screen.device) export(rk.select.CRAN.mirror) export(rk.select.list) export(rk.sessionInfo) export(rk.set.label) export(rk.set.output.html.file) export(rk.set.plugin.status) export(rk.show.files) export(rk.show.html) export(rk.show.message) export(rk.show.plot.info) export(rk.show.question) export(rk.switch.frontend.language) export(rk.sync) export(rk.sync.global) export(rk.toggle.plot.history) export(rk.verify.plot.hist.limits) export(rk.without.plot.history) export(setwd) export(win.graph) export(windows) export(x11) rkward-0.6.4/rkward/rbackend/rpackages/CMakeLists.txt0000664000175000017500000000057112633754364022150 0ustar thomasthomasINCLUDE_DIRECTORIES( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${KDE4_INCLUDE_DIR} ${QT_INCLUDES} ) CONFIGURE_FILE( "${CMAKE_CURRENT_SOURCE_DIR}/rpackage_install.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/rpackage_install.cmake" @ONLY) INSTALL(SCRIPT ${CMAKE_CURRENT_BINARY_DIR}/rpackage_install.cmake -DDESTDIR=${DESTDIR} -DBUILD_TIMESTAMP=${BUILD_TIMESTAMP}) rkward-0.6.4/rkward/rbackend/rpackages/rkwardtests/0000755000175000017500000000000012455741221021746 5ustar thomasthomasrkward-0.6.4/rkward/rbackend/rpackages/rkwardtests/DESCRIPTION0000644000175000017500000000204212455741221023452 0ustar thomasthomasPackage: rkwardtests Type: Package Title: RKWard Plugin Test Suite Framework Author: Thomas Friedrichsmeier, Meik Michalke Maintainer: Thomas Friedrichsmeier , Meik Michalke Depends: R (>= 2.9.0),methods Description: A set of functions, classes and methods to test plugins that were written for RKWard. License: GPL (>= 2) Encoding: UTF-8 LazyLoad: yes URL: http://rkward.kde.org Authors@R: c(person(given="Thomas", family="Friedrichsmeier", email="thomas.friedrichsmeier@ruhr-uni-bochum.de", role=c("aut", "cre")), person(given="Meik", family="Michalke", email="meik.michalke@hhu.de", role=c("aut", "cre"))) Version: 0.6.1 Date: 2012-09-28 Collate: 'RKTest-class.R' 'rktest.getTempDir.R' 'rktest.makeplugintests.R' 'rktest.replaceRunAgainLink.R' 'RKTestResult-class.R' 'rktest.runRKTestSuite.R' 'rktest.setSuiteStandards.R' 'RKTestSuite-class.R' 'rkwardtests-internal.R' 'rkwardtests-package.R' 'show-method.R' rkward-0.6.4/rkward/rbackend/rpackages/rkwardtests/R/0000755000175000017500000000000012633754364022161 5ustar thomasthomasrkward-0.6.4/rkward/rbackend/rpackages/rkwardtests/R/rkwardtests-package.R0000644000175000017500000000120612455741221026237 0ustar thomasthomas#' RKWard Plugin Test Suite Framework. #' #' \tabular{ll}{ #' Package: \tab rkwardtests\cr #' Type: \tab Package\cr #' Version: \tab 0.5.7\cr #' Date: \tab 2012-06-03\cr #' Depends: \tab R (>= 2.9.0),methods\cr #' Encoding: \tab UTF-8\cr #' License: \tab GPL (>= 2)\cr #' LazyLoad: \tab yes\cr #' URL: \tab http://rkward.kde.org\cr #' } #' #' A set of functions, classes and methods to test plugins that were written for RKWard. #' #' @aliases rkwardtests-package rkwardtests #' @name rkwardtests-package #' @docType package #' @title RKWard Plugin Test Suite Framework. #' @author Thomas Friedrichsmeier, Meik Michalke #' @keywords package NULL rkward-0.6.4/rkward/rbackend/rpackages/rkwardtests/R/rktest.makeplugintests.R0000644000175000017500000000605512471622105027027 0ustar thomasthomas#' Run a whole RKWard plugin test suite #' #' The function \code{rktest.makeplugintests} will run a whole test suite that was prepared to check one or several RKWard plugins. #' #' @title Run RKWard plugin test suite #' @usage rktest.makeplugintests(testsuites="testsuite.R", testroot=getwd(), #' outfile="make_plugintests.txt", append=FALSE, test.id=NULL) #' @aliases rktest.makeplugintests #' @param testsuites A character string or vector naming the test suites to be run. #' @param testroot A character string pointing to the root directory where the test suite resides (including its folder with test standards). #' @param outfile A character string giving a file name for the result log. #' @param append If TRUE, append output to an existing file. #' @param test.id Optional character string or vector naming one or more tests of a suite to be run (if NULL, all tests are run). #' @return Results are printed to stdout and saved to the defined output file. #' @author Thomas Friedrichsmeier \email{thomas.friedrichsmeier@@ruhr-uni-bochum.de}, Meik Michalke \email{meik.michalke@@uni-duesseldorf.de} #' @keywords utilities #' @seealso \code{\link[rkwardtests:RKTestSuite]{RKTestSuite-class}}, \code{\link[rkwardtests:RKTestResult]{RKTestResult-class}} #' @export #' @examples #' \dontrun{ #' rktest.makeplugintests(testsuites=c("rkward_application_tests.R", #' "import_export_plugins.R"), testroot=getwd()) #' rktest.makeplugintests(testsuites="distribution.R", #' testroot=getwd(), test.id=c("poisson_quantiles", "geom_quantiles")) #' } rktest.makeplugintests <- function(testsuites="testsuite.R", testroot=getwd(), outfile="make_plugintests.txt", append=FALSE, test.id=NULL){ ## change to test root directory oldwd <- getwd() on.exit(setwd(oldwd)) setwd(testroot) ## initialize rktest.initializeEnvironment() on.exit(rktest.resetEnvironment(), add=TRUE) sink (file = outfile, append=append, type="output", split=TRUE) cat ("RKWard Version:\n") print (.rk.app.version) cat ("\n\nR-Version:\n") print (R.version) cat ("\n\nInstalled packages:\n") print (subset(installed.packages(),select=c(LibPath,Version))) allresults <- new ("RKTestResult") for (testsuite in testsuites) { tryCatch(source(testsuite, local=TRUE), error=function(e) e) results <- rktest.runRKTestSuite (suite=suite, testroot=testroot, test.id=test.id) allresults <- rktest.appendTestResults (allresults, results) } cat ("\n\nOverall results:\n") print (allresults) if (any (is.na (allresults@passed))) { missing.libs <- unique(allresults@missing_libs[!is.na(allresults@missing_libs)]) cat ("\nNOTE: Skipped tests due to missing libaries are not an indication of problems.") cat ("\nThe following missing R packages are needed in order to run all tests:\n") print(missing.libs) } sink() cat (paste ("\n\nThese output are saved in: ", paste (getwd(), outfile, sep=.Platform$file.sep), ".\nIf needed, send them to rkward-devel@kde.org\n", sep="")) cat (paste("\nThe full test results have been saved to this temporary directory:\n", rktest.getTempDir(),"\n")) }rkward-0.6.4/rkward/rbackend/rpackages/rkwardtests/R/rktest.replaceRunAgainLink.R0000644000175000017500000000251512455741221027466 0ustar thomasthomas#' Replace "Run again" link in RKWard with code #' #' You can use this to temporarily replace .rk.rerun.plugin.link (see example below). #' This way, after running a plugin, you are shown the call needed to run this #' plugin with those settings, instead of the link. #' #' This code can be used in a plugin test suite. #' #' @title Replace "Run again" link in RKWard #' @usage rktest.replaceRunAgainLink(restore=FALSE) #' @aliases .rk.rerun.plugin.link.replacement #' @param restore Logical: If TRUE, restore the original behaviour. #' @return Replaces the "Run again" link in RKWard with the code that would have been called, or vice versa. #' @author Thomas Friedrichsmeier \email{thomas.friedrichsmeier@@ruhr-uni-bochum.de}, Meik Michalke \email{meik.michalke@@uni-duesseldorf.de} #' @keywords utilities #' @seealso \code{\link[rkwardtests:RKTestSuite]{RKTestSuite-class}}, \code{\link[rkwardtests:rktest.makeplugintests]{rktest.makeplugintests}} #' @export #' @rdname rktest.replaceRunAgainLink #' @examples #' rktest.replaceRunAgainLink() rktest.replaceRunAgainLink <- function(restore=FALSE){ if(!restore){ rktest.replace (".rk.rerun.plugin.link", .rk.rerun.plugin.link.replacement, backup.name=".rk.rerun.plugin.link.manual.replace") } else { rktest.restore (".rk.rerun.plugin.link", backup.name=".rk.rerun.plugin.link.manual.replace") } } rkward-0.6.4/rkward/rbackend/rpackages/rkwardtests/R/RKTestResult-class.R0000644000175000017500000000316612455741221025756 0ustar thomasthomas## temporarily turned off most of the roxygen comments ## class docs will remain static until roxygen2 supports "@slot" #' S4 Class RKTestResult #' #' This class is used internally by \code{\link[rkwardtests:rktest.makeplugintests]{rktest.makeplugintests}}. #' #' @noRd # @slot id A unique character string naming a test. # @slot code_match A character string indicating whether the run code matched the standard. # @slot output_match A character string indicating whether the resulting output matched the standard. # @slot message_match A character string indicating whether the resulting R messages matched the standard. # @slot error A character string indicating errors. # @slot missing_libs A character string indicating missing libraries. # @slot passed Logical: Did the test pass? #' @name RKTestResult #' @import methods #' @keywords classes #' @author Thomas Friedrichsmeier \email{thomas.friedrichsmeier@@ruhr-uni-bochum.de} #' @exportClass RKTestResult # @rdname RKTestResult-class setClass ("RKTestResult", representation (id = "character", code_match = "character", output_match = "character", message_match = "character", error="character", missing_libs="character", passed="logical"), prototype(character(0), id = character (0), code_match = character (0), output_match = character (0), message_match = character (0), error = character (0), missing_libs = character (0), passed=FALSE), validity=function (object) { return (all.equal (length (object@id), length (object@code_match), length (object@output_match), length (object@message_match), length (object@error), length (object@missing_libs), length (object@passed))) } ) rkward-0.6.4/rkward/rbackend/rpackages/rkwardtests/R/rktest.getTempDir.R0000644000175000017500000000160212455741221025650 0ustar thomasthomas#' Get the path to the recent temporary directory, if one exists. #' #' This function will return either the local path to the temporary directory where #' all test results have been saved to, or FALSE if none exitsts. #' #' @title Get path to the temporary directory #' @usage rktest.getTempDir() #' @aliases rktest.getTempDir #' @return Either a character string, or FALSE. #' @author Meik Michalke \email{meik.michalke@@uni-duesseldorf.de} #' @keywords utilities #' @seealso \code{\link[rkwardtests:rktest.makeplugintests]{rktest.makeplugintests}} #' @export #' @examples #' rktest.getTempDir() rktest.getTempDir <- function(){ if(exists(".rktest.temp.dir", where=.rktest.tmp.storage)){ temp.dir <- get(".rktest.temp.dir", pos=.rktest.tmp.storage) if(file_test("-d", temp.dir)) { return(temp.dir) } else { return(FALSE) } } else { return(FALSE) } } rkward-0.6.4/rkward/rbackend/rpackages/rkwardtests/R/rktest.runRKTestSuite.R0000644000175000017500000000464212455741221026526 0ustar thomasthomas#' Run a single RKWard plugin test suite #' #' This function can be called to run a single plugin test suite. #' #' @title Run RKWard plugin test suite #' @usage rktest.runRKTestSuite(suite, testroot=getwd(), test.id=NULL) #' @aliases rktest.runRKTestSuite #' @param suite Character string naming the test suite to run. #' @param testroot Defaults to the working directory. #' @param test.id An optional character string or vector naming one or more tests of a suite to be run (if NULL, all tests are run). #' @return An object of class \code{\link[rkwardtests:RKTestResult]{RKTestResult-class}}. #' @author Thomas Friedrichsmeier \email{thomas.friedrichsmeier@@ruhr-uni-bochum.de}, Meik Michalke \email{meik.michalke@@uni-duesseldorf.de} #' @keywords utilities #' @seealso \code{\link[rkwardtests:RKTestSuite]{RKTestSuite-class}}, \code{\link[rkwardtests:rktest.makeplugintests]{rktest.makeplugintests}} #' @export #' @examples #' \dontrun{ #' result <- rktest.runRKTestSuite() #' } rktest.runRKTestSuite <- function (suite, testroot=getwd (), test.id=NULL) { # check wheter test environment is already set, # otherwise initialize if(!exists("initialized", where=rkwardtests::.rktest.tmp.storage) || !get("initialized", pos=rkwardtests::.rktest.tmp.storage)){ rktest.initializeEnvironment() on.exit(rktest.resetEnvironment()) } result <- new ("RKTestResult") # FALSE by default if (!inherits (suite, "RKTestSuite")) return (result) if (!validObject (suite)) return (result) # clean any old results rktest.cleanRKTestSuite (suite) oldwd = getwd () on.exit (setwd (oldwd), add=TRUE) # setwd (file.path(testroot, suite@id)) setwd (rktest.createTempSuiteDir(suite@id)) if (length (suite@initCalls) > 0) { for (i in 1:length (suite@initCalls)) try (suite@initCalls[[i]]()) } rk.sync.global () # objects might have been added/changed in the init calls # check if only a subset of tests is desired if(length(test.id) > 0) suite@tests <- suite@tests[is.element(sapply(suite@tests, function(test){test@id}), test.id)] for (i in 1:length (suite@tests)) { suite@tests[[i]]@libraries <- c(suite@libraries, suite@tests[[i]]@libraries) try (res <- rktest.runRKTest(test=suite@tests[[i]], standard.path=file.path(testroot, suite@id), suite.id=suite@id)) result <- rktest.appendTestResults (result, res) } if (length (suite@postCalls) > 0) { for (i in 1:length (suite@postCalls)) try (suite@postCalls[[i]]()) } return(result) } rkward-0.6.4/rkward/rbackend/rpackages/rkwardtests/R/rktest.setSuiteStandards.R0000644000175000017500000000423412455741221027261 0ustar thomasthomas#' Set RKWard plugin test suite standards #' #' Use this function after you plugin passed all tests to set the resulting code, #' output and R messages as the standard that will be compared to during following tests. #' #' @title Set RKWard suite standards #' @usage rktest.setSuiteStandards(suite, testroot=getwd(), file=TRUE) #' @aliases rktest.setSuiteStandards #' @param suite Character string naming the test suite to set standards for. #' @param testroot Path to the test root directory, defaults to the working directory. #' @param file Logical: If \code{suite} is already a present R object, set this to FALSE. #' Otherwise it is assumed to be a file and fed to \code{source}. #' @return The function simply copies the previously created files from the temporary directory #' to the directory containing the test standards (inside the testroot). #' @author Thomas Friedrichsmeier \email{thomas.friedrichsmeier@@ruhr-uni-bochum.de} #' @keywords utilities #' @seealso \code{\link[rkwardtests:RKTestSuite]{RKTestSuite-class}}, \code{\link[rkwardtests:rktest.makeplugintests]{rktest.makeplugintests}} #' @export #' @examples #' \dontrun{ #' rktest.setSuiteStandards("rkward_application_tests.R") #' } rktest.setSuiteStandards <- function (suite, testroot=getwd (), file=TRUE) { if(file){ tryCatch(source(suite, local=TRUE), error=function(e) e) } else {} if (!inherits (suite, "RKTestSuite")) return (result) if (!validObject (suite)) return (result) ok <- rk.show.question ("You are about to set new standards for this suite. This means you are certain that ALL tests in this suite have produced the expected/correct result on the last run. Are you absolutely sure, that you want to proceed?", caption="Really set new standards?"); if (!isTRUE (ok)) stop ("Aborted") oldwd = getwd () on.exit (setwd (oldwd)) setwd (file.path(testroot, suite@id)) temp.suite.dir <- rktest.createTempSuiteDir(suite@id) files <- list.files (temp.suite.dir) files <- grep ("\\.(messages.txt|rkcommands.R|rkout)$", files, value=TRUE) file.copy (file.path(temp.suite.dir, files), files, overwrite=TRUE) # clean anything that is *not* a standard file rktest.cleanRKTestSuite (suite) } rkward-0.6.4/rkward/rbackend/rpackages/rkwardtests/R/RKTest-class.R0000644000175000017500000000261212455741221024552 0ustar thomasthomas## temporarily turned off most of the roxygen comments ## class docs will remain static until roxygen2 supports "@slot" #' S4 class RKTest #' #' This class is used internally by \code{\link[rkwardtests:rktest.makeplugintests]{rktest.makeplugintests}}. #' #' @noRd # @slot id A unique character string # @slot call A function to be called # @slot fuzzy_output Allow fuzzy results # @slot expect_error Expect errors # @slot libraries A charcter vector naming needed libraries # @slot files A character vector naming needed files, path relative to the test standards directory # @slot record.all.commands Should synchronization commands and commands to generate run-again-links be included in the command recording? Generally, this should be FALSE (the default). #' @name RKTest #' @import methods #' @keywords classes #' @author Thomas Friedrichsmeier \email{thomas.friedrichsmeier@@ruhr-uni-bochum.de} #' @exportClass RKTest # @rdname RKTest-class setClass ("RKTest", representation (id="character", call="function", fuzzy_output="logical", expect_error="logical", libraries="character", files="character", record.all.commands="logical"), prototype(character(0), id=NULL, call=function () { stop () }, fuzzy_output=FALSE, expect_error=FALSE, libraries=character(0), files=character(0), record.all.commands=FALSE), validity=function (object) { if (is.null (object@id)) return (FALSE) return (TRUE) } ) rkward-0.6.4/rkward/rbackend/rpackages/rkwardtests/R/show-method.R0000644000175000017500000000313312455741221024530 0ustar thomasthomas#' show method for S4 objects of class RKTestResult #' #' Prints a summary of plugin test results. #' #' @title show method for objects of class RKTestResult #' @method show RKTestResult #' @param object An object of class RKTestResult #' @aliases show,RKTestResult-method #' @author Thomas Friedrichsmeier \email{thomas.friedrichsmeier@@ruhr-uni-bochum.de} #' @keywords methods #' @examples #' \dontrun{ #' rktest.makeplugintests("rkward_application_tests.R") #' } #' @exportMethod show #' @rdname show setMethod ("show", "RKTestResult", function (object) { stopifnot (inherits (object, "RKTestResult")) cat (format ("ID", width=30)) cat (format ("code match", width=15)) cat (format ("output match", width=15)) cat (format ("message match", width=15)) cat (format ("error", width=15)) cat (format ("result", width=15)) cat ("\n", rep ("-", 96), "\n", sep="") for (i in 1:length (object@id)) { cat (format (object@id[i], width=30)) cat (format (object@code_match[i], width=15)) cat (format (object@output_match[i], width=15)) cat (format (object@message_match[i], width=15)) cat (format (object@error[i], width=15)) cat (format (if (is.na (object@passed[i])) "--skipped--" else if (object@passed[i]) "pass" else "FAIL", width=15)) cat ("\n") } cat (rep ("-", 96), "\n", sep="") cat (as.character (sum (object@passed, na.rm=TRUE)), " / ", as.character (sum (!is.na (object@passed))), " tests passed\n") if (sum (is.na (object@passed)) > 0) cat ("(", as.character (sum (is.na (object@passed))), " / ", as.character (length (object@passed)), " tests skipped due to missing libraries)", sep=""); }) rkward-0.6.4/rkward/rbackend/rpackages/rkwardtests/R/rkwardtests-internal.R0000664000175000017500000003006412633754364026500 0ustar thomasthomas# these functions are all used internally # create an internal environment for temporary information storage #' @export .rktest.tmp.storage .rktest.tmp.storage <- new.env() .rk.rerun.plugin.link.replacement <- function (plugin, settings, label) { op <- options (useFancyQuotes = FALSE) on.exit (options (op)) .rk.cat.output("

Rerun code:

") .rk.cat.output("
")
	.rk.cat.output("rk.call.plugin (")
	.rk.cat.output(dQuote (plugin))
	.rk.cat.output(", ")

	lines = strsplit (settings, '\n', fixed=TRUE)[[1]]
	first = TRUE
	for (line in lines) {
		if (first) first <- FALSE
		else .rk.cat.output (", ")
		split <- strsplit (line, '=', fixed=TRUE)[[1]]
		key <- split[1]
		value <- paste (split[-1], collapse="=")
		.rk.cat.output(paste (key, dQuote (value), sep="="))
	}
	.rk.cat.output(", submit.mode=\"submit\")
") } rktest.appendTestResults <- function (objecta, objectb) { stopifnot (inherits (objecta, "RKTestResult") && validObject (objecta)) stopifnot (inherits (objectb, "RKTestResult") && validObject (objectb)) index <- length (objecta@id) for (i in 1:length (objectb@id)) { objecta@id[index+i] = objectb@id[i] objecta@code_match[index+i] = objectb@code_match[i] objecta@output_match[index+i] = objectb@output_match[i] objecta@message_match[index+i] = objectb@message_match[i] objecta@error[index+i] = objectb@error[i] objecta@missing_libs[index+i] = objectb@missing_libs[i] objecta@passed[index+i] = objectb@passed[i] } objecta } rktest.file <- function (id, extension, suite.id) { # get or create a temporary directory temp.suite.dir <- rktest.createTempSuiteDir(suite.id) file.path(temp.suite.dir, paste (id, extension, sep="")) } # returns true, if file corresponds to standard. rktest.compare.against.standard <- function (file, standard.path, fuzzy=FALSE) { standard_file <- file.path(standard.path, gsub ("^(.*\\/)", "", file)) # gsub strips any leading directories from the path if (file.exists (file)) { # purge empty files info <- file.info (file) if (info$size[1] == 0) file.remove (file) } if (!file.exists (file)) { # if neither exists, that means both files are empty if (!file.exists (standard_file)) return ("match (empty)") } output.diff <- suppressWarnings (system(paste("diff", shQuote(file), shQuote(standard_file), "--strip-trailing-cr", "--new-file"), intern=TRUE)) if (!length (output.diff)) return ("match") if ((length (output.diff) == 1) && (!nzchar (output.diff))) return ("match") # below: there are *some* differences if (fuzzy) { size <- if (file.exists (file)) file.info (file)$size[1] else 0 s_size <- if (file.exists (standard_file)) file.info (standard_file)$size[1] else 0 # crude test: files should at least have a similar size if ((size < (s_size + 20)) && (size > (s_size - 20))) return ("fuzzy match") } print (paste ("Differences between", file, "and", standard_file, ":")) print (output.diff) return ("MISMATCH") } rktest.runRKTest.internal <- function (test, output_file, code_file, message_file) { # save / restore old output file old_output <- rk.get.output.html.file () rk.set.output.html.file (output_file) on.exit (rk.set.output.html.file (old_output), add=TRUE) message_file_handle <- file (message_file, open="w+") sink(message_file_handle, type="message") on.exit ({ sink (NULL, type="message") close (message_file_handle) }, add=TRUE) rk.record.commands (code_file, include.all=test@record.all.commands) on.exit (rk.record.commands (NULL), add=TRUE) old.symbols <- ls (envir=globalenv (), all.names=TRUE) on.exit ({ # clean up any new objects created by the test new.symbols <- ls (envir=globalenv (), all.names=TRUE) new.symbols <- new.symbols[!(new.symbols %in% old.symbols)] rm (list=new.symbols, envir=globalenv ()) rk.sync.global () }, add=TRUE) failed <- TRUE try ({ test@call () failed <- FALSE }) return (failed) } rktest.runRKTest <- function (test, standard.path, suite.id) { result <- new ("RKTestResult") # FALSE by default if (!inherits (test, "RKTest")) return (result) result@id <- test@id if (!validObject (test)) return (result) missing_libs <- character(0) for (lib in test@libraries) { if (!suppressWarnings (base::require (lib, character.only=TRUE, quietly=TRUE))) { missing_libs <- c (missing_libs, lib) } } if (length (missing_libs) > 0) { result@output_match <- result@message_match <- result@code_match <- NA_character_ result@error <- "missing lib(s)" result@missing_libs <- missing_libs result@passed <- NA cat ("\nSkipping test \"", test@id, "\" due to missing libraries: \"", paste (missing_libs, collapse="\", \""), "\"\n", sep="") return (result) } for (testfile in test@files) { file.copy(file.path(standard.path, testfile), getwd()) } output_file <- rktest.file (test@id, ".rkout", suite.id) code_file <- rktest.file (test@id, ".rkcommands.R", suite.id) message_file <- rktest.file (test@id, ".messages.txt", suite.id) # the essence of the test: res.error <- rktest.runRKTest.internal (test, output_file, code_file, message_file) passed <- (res.error == test@expect_error) if (res.error) { if (test@expect_error) result@error <- "expected error" else result@error <- "ERROR" } else { if (test@expect_error) result@error <- "MISSING ERROR" else result@error <- "no" } rktest.cleanTestFile (output_file) rktest.cleanTestFile (code_file) rktest.cleanTestFile (message_file) result@output_match = rktest.compare.against.standard (output_file, standard.path, test@fuzzy_output) if (result@output_match == "MISMATCH") passed <- FALSE result@message_match = rktest.compare.against.standard (message_file, standard.path) if (result@message_match == "MISMATCH") passed <- FALSE result@code_match = rktest.compare.against.standard (code_file, standard.path) if (result@code_match == "MISMATCH") passed <- FALSE result@passed <- passed result } # Make replacements to stabilize comparison. Currently strips tempdir path. rktest.cleanTestFile <- function (filename) { if (file.exists (filename)) { raw <- readLines (filename) cleaned <- gsub (getwd (), "PATH", raw, fixed=TRUE) writeLines (cleaned, filename) } } rktest.cleanRKTestSuite <- function (suite) { # kept for backwards compatibility ;-) rktest.removeTempSuiteDir(suite@id) } ## Convenience functions for replacing / restoring functions for the test runs rktest.replace <- function (name, replacement, envir=as.environment ("package:rkward"), backup.name=name, targetEnv=.rktest.tmp.storage) { if (exists (backup.name, envir=targetEnv, inherits=FALSE)) { message ("It looks like ", name, " has already been replaced. Not replacing it again.") } else { if (identical (envir, as.environment ("package:rkward"))) { # # Apparently R 2.14.x starts forcing namespaces for all packages, which makes things a bit more difficult try (unlockBinding (name, asNamespace ("rkward")), silent=TRUE) try (assign (name, replacement, asNamespace ("rkward")), silent=TRUE) assign (backup.name, get (name, asNamespace ("rkward")), envir=targetEnv) } else { assign (backup.name, get (name, envir), envir=targetEnv) } environment (replacement) <- envir try (unlockBinding (name, envir), silent=TRUE) try (assign (name, replacement, envir), silent=TRUE) } return(invisible(NULL)) } rktest.restore <- function (name, envir=as.environment ("package:rkward"), backup.name=name, targetEnv=.rktest.tmp.storage) { if (exists (backup.name, envir=targetEnv, inherits=FALSE)) { if (identical (envir, as.environment ("package:rkward"))) { assign (name, get (backup.name, envir=targetEnv), envir=asNamespace ("rkward")) } try (assign (name, get (backup.name, envir=targetEnv), envir=envir), silent=TRUE) rm (list=backup.name, envir=targetEnv) } else { message ("No backup available for ", name, ". Already restored?") } return(invisible(NULL)) } ## Initialize test environment rktest.initializeEnvironment <- function () { # Almost all tests depend on R2HTML, indirectly, so we should really assume it (or have the user install it) at the start stopifnot (require (R2HTML)) # By default .rk.rerun.plugin.link() and .rk.make.hr() are silenced during the test runs rktest.replace (".rk.rerun.plugin.link", function (...) list (...)) rktest.replace (".rk.make.hr", function (...) list (...)) # This should make the output of rk.graph.on() fixed rktest.replace ("rk.get.tempfile.name", function (prefix="image", extension=".jpg") paste (prefix, extension, sep="")) options (rk.graphics.type="PNG", rk.graphics.width=480, rk.graphics.height=480) # HACK: Override date, so we don't get a difference for each call of rk.header () rktest.replace (".rk.date", function () return ("DATE")) # disable toc printing options (.rk.suppress.toc=TRUE) # numerical precision is often a problem. To work around this in many places, reduce default printed precision to 5 digits options (digits=5) # Make sure i18n does not get in the way invisible (Sys.setenv (LANGUAGE="C")) invisible (Sys.setenv (LANG="C")) if (.Platform$OS.type == "unix"){ invisible (Sys.setlocale (category="LC_MESSAGES", locale="C")) } rk.switch.frontend.language ("C") options (useFancyQuotes=FALSE) # This version of rk.set.output.html.file does not notify the frontend of the change. Without this, you'll get lots of output windows. rktest.replace ("rk.set.output.html.file", function (x) { stopifnot(is.character(x)) assign(".rk.output.html.file", x, .rk.variables) .rk.do.plain.call ("set.output.file", c (x, "SILENT"), synchronous=FALSE) }) assign("initialized", TRUE, envir=.rktest.tmp.storage) } # counterpart to rktest.initializeEnvironment. Restores the most important settings rktest.resetEnvironment <- function () { # return to previously dumped state rktest.restore (".rk.rerun.plugin.link") rktest.restore (".rk.make.hr") rktest.restore ("rk.get.tempfile.name") rktest.restore ("rk.set.output.html.file") rktest.restore (".rk.date") options (.rk.supress.toc=FALSE) assign("initialized", FALSE, envir=.rktest.tmp.storage) } ## handling of temporary directories # create a temporary directory for the test results # the path to it will be stored in an object in globalenv() and returned rktest.createTempDir <- function(){ temp.dir <- rktest.getTempDir() # if a temp.dir already exists, we will use it! if(is.character(temp.dir)){ return(temp.dir) } else{} new.temp.dir <- tempfile("rktests.") if(!dir.create(new.temp.dir, recursive=TRUE)) { stop(simpleError("Couldn't create temporary directory!")) } else { assign(".rktest.temp.dir", new.temp.dir, envir=.rktest.tmp.storage) return(new.temp.dir) } } # remove the temporary directory that is defined in globalenv() rktest.removeTempDir <- function(){ temp.dir <- rktest.getTempDir() if(is.character(temp.dir)){ unlink(temp.dir, recursive=TRUE) # should the function stop here if unlink() failed? rm(".rktest.temp.dir", envir=.rktest.tmp.storage) return(TRUE) } else { return(FALSE) } } # create a suite directory inside the temp dir # for the actual test files rktest.createTempSuiteDir <- function(suite.id){ # create or get the temp base dir to use temp.dir <- rktest.createTempDir() temp.suite.dir <- file.path(temp.dir, suite.id) # check if this dir already exists, then just return its path if(file_test("-d", temp.suite.dir)){ return(temp.suite.dir) } # if not, try to create it and again return its path else { if(!dir.create(temp.suite.dir, recursive=TRUE)) { stop(simpleError("Couldn't create temporary suite directory!")) } else { return(temp.suite.dir) } } } # remove just the suite temp dir rktest.removeTempSuiteDir <- function(suite.id){ temp.dir <- rktest.getTempDir() if(is.character(temp.dir)){ temp.suite.dir <- file.path(temp.dir, suite.id) # check if this dir exists if(file_test("-d", temp.suite.dir)){ unlink(temp.suite.dir, recursive=TRUE) # if nothing is left in the base tempdir now, remove it as well if(length(list.files(temp.dir)) == 0) { rktest.removeTempDir() } else {} return(TRUE) } # if not, return FALSE else { return(FALSE) } } else { return(FALSE) } } rkward-0.6.4/rkward/rbackend/rpackages/rkwardtests/R/RKTestSuite-class.R0000644000175000017500000000241012455741221025560 0ustar thomasthomas## temporarily turned off most of the roxygen comments ## class docs will remain static until roxygen2 supports "@slot" #' S4 Class RKTestSuite #' #' This class is used to create test suite objects that can be fed to \code{\link[rkwardtests:rktest.makeplugintests]{rktest.makeplugintests}}. #' #' @noRd # @slot id A unique character string to identify a test suite # @slot libraries A charcter vector naming libraries that the test suite depends on. # @slot initCalls A list of functions to be run before any tests, e.g. to load libraries or data objects. # @slot tests A list of the actual plugin tests. # @slot postCalls A list of functions to be run after all tests, e.g. to clean up. #' @name RKTestSuite #' @import methods #' @keywords classes #' @author Thomas Friedrichsmeier \email{thomas.friedrichsmeier@@ruhr-uni-bochum.de} #' @exportClass RKTestSuite # @rdname RKTestSuite-class setClass ("RKTestSuite", representation (id="character", libraries="character", initCalls="list", tests="list", postCalls="list"), prototype(character(0), id=NULL, libraries=character(0), initCalls=list(), tests=list(), postCalls=list ()), validity=function (object) { if (length (object@id) != 1) return (FALSE) if (length (object@tests) < 1) return (FALSE) return (TRUE) } ) rkward-0.6.4/rkward/rbackend/rpackages/rkwardtests/inst/0000755000175000017500000000000012455741221022723 5ustar thomasthomasrkward-0.6.4/rkward/rbackend/rpackages/rkwardtests/inst/CITATION0000644000175000017500000000071612455741221024064 0ustar thomasthomasbibentry("Manual", title="rkwardtests: RKWard Plugin Test Suite Framework", author="Thomas Friedrichsmeier and Meik Michalke", year="2012", note="(Version 0.5.7)", url="http://rkward.kde.org", textVersion = paste("Friedrichsmeier, T. & Michalke, M. (2012). ", "rkwardtests: RKWard Plugin Test Suite Framework (Version 0.5.7). ", "Available from http://rkward.kde.org", sep=""), mheader = "To cite rkwardtests in publications use:") rkward-0.6.4/rkward/rbackend/rpackages/rkwardtests/man/0000755000175000017500000000000012455741221022521 5ustar thomasthomasrkward-0.6.4/rkward/rbackend/rpackages/rkwardtests/man/rktest.makeplugintests.Rd0000644000175000017500000000312012455741221027536 0ustar thomasthomas\name{rktest.makeplugintests} \alias{rktest.makeplugintests} \title{Run RKWard plugin test suite} \usage{ rktest.makeplugintests(testsuites="testsuite.R", testroot=getwd(), outfile="make_plugintests.txt", append=FALSE, test.id=NULL) } \arguments{ \item{testsuites}{A character string or vector naming the test suites to be run.} \item{testroot}{A character string pointing to the root directory where the test suite resides (including its folder with test standards).} \item{outfile}{A character string giving a file name for the result log.} \item{append}{If TRUE, append output to an existing file.} \item{test.id}{Optional character string or vector naming one or more tests of a suite to be run (if NULL, all tests are run).} } \value{ Results are printed to stdout and saved to the defined output file. } \description{ Run a whole RKWard plugin test suite } \details{ The function \code{rktest.makeplugintests} will run a whole test suite that was prepared to check one or several RKWard plugins. } \examples{ \dontrun{ rktest.makeplugintests(testsuites=c("rkward_application_tests.R", "import_export_plugins.R"), testroot=getwd()) rktest.makeplugintests(testsuites="distribution.R", testroot=getwd(), test.id=c("poisson_quantiles", "geom_quantiles")) } } \author{ Thomas Friedrichsmeier \email{thomas.friedrichsmeier@ruhr-uni-bochum.de}, Meik Michalke \email{meik.michalke@uni-duesseldorf.de} } \seealso{ \code{\link[rkwardtests:RKTestSuite]{RKTestSuite-class}}, \code{\link[rkwardtests:RKTestResult]{RKTestResult-class}} } \keyword{utilities} rkward-0.6.4/rkward/rbackend/rpackages/rkwardtests/man/rktest.runRKTestSuite.Rd0000644000175000017500000000202612455741221027236 0ustar thomasthomas\name{rktest.runRKTestSuite} \alias{rktest.runRKTestSuite} \title{Run RKWard plugin test suite} \usage{ rktest.runRKTestSuite(suite, testroot=getwd(), test.id=NULL) } \arguments{ \item{suite}{Character string naming the test suite to run.} \item{testroot}{Defaults to the working directory.} \item{test.id}{An optional character string or vector naming one or more tests of a suite to be run (if NULL, all tests are run).} } \value{ An object of class \code{\link[rkwardtests:RKTestResult]{RKTestResult-class}}. } \description{ Run a single RKWard plugin test suite } \details{ This function can be called to run a single plugin test suite. } \examples{ \dontrun{ result <- rktest.runRKTestSuite() } } \author{ Thomas Friedrichsmeier \email{thomas.friedrichsmeier@ruhr-uni-bochum.de}, Meik Michalke \email{meik.michalke@uni-duesseldorf.de} } \seealso{ \code{\link[rkwardtests:RKTestSuite]{RKTestSuite-class}}, \code{\link[rkwardtests:rktest.makeplugintests]{rktest.makeplugintests}} } \keyword{utilities} rkward-0.6.4/rkward/rbackend/rpackages/rkwardtests/man/rkwardtests-package.Rd0000644000175000017500000000117612455741221026763 0ustar thomasthomas\docType{package} \name{rkwardtests-package} \alias{rkwardtests} \alias{rkwardtests-package} \title{RKWard Plugin Test Suite Framework.} \description{ RKWard Plugin Test Suite Framework. } \details{ \tabular{ll}{ Package: \tab rkwardtests\cr Type: \tab Package\cr Version: \tab 0.5.7\cr Date: \tab 2012-06-03\cr Depends: \tab R (>= 2.9.0),methods\cr Encoding: \tab UTF-8\cr License: \tab GPL (>= 2)\cr LazyLoad: \tab yes\cr URL: \tab http://rkward.kde.org\cr } A set of functions, classes and methods to test plugins that were written for RKWard. } \author{ Thomas Friedrichsmeier, Meik Michalke } \keyword{package} rkward-0.6.4/rkward/rbackend/rpackages/rkwardtests/man/show.Rd0000644000175000017500000000074212455741221023773 0ustar thomasthomas\name{show} \alias{show} \alias{show,RKTestResult-method} \title{show method for objects of class RKTestResult} \arguments{ \item{object}{An object of class RKTestResult} } \description{ show method for S4 objects of class RKTestResult } \details{ Prints a summary of plugin test results. } \examples{ \dontrun{ rktest.makeplugintests("rkward_application_tests.R") } } \author{ Thomas Friedrichsmeier \email{thomas.friedrichsmeier@ruhr-uni-bochum.de} } \keyword{methods} rkward-0.6.4/rkward/rbackend/rpackages/rkwardtests/man/rktest.getTempDir.Rd0000644000175000017500000000116612455741221026373 0ustar thomasthomas\name{rktest.getTempDir} \alias{rktest.getTempDir} \title{Get path to the temporary directory} \usage{ rktest.getTempDir() } \value{ Either a character string, or FALSE. } \description{ Get the path to the recent temporary directory, if one exists. } \details{ This function will return either the local path to the temporary directory where all test results have been saved to, or FALSE if none exitsts. } \examples{ rktest.getTempDir() } \author{ Meik Michalke \email{meik.michalke@uni-duesseldorf.de} } \seealso{ \code{\link[rkwardtests:rktest.makeplugintests]{rktest.makeplugintests}} } \keyword{utilities} rkward-0.6.4/rkward/rbackend/rpackages/rkwardtests/man/RKTest-class.Rd0000644000175000017500000000073612455741221025275 0ustar thomasthomas\name{RKTest-class} \alias{RKTest -class} \alias{RKTest} \title{S4 class RKTest} \description{This class is used internally by \code{\link[rkwardtests:rktest.makeplugintests]{rktest.makeplugintests}}.} \keyword{classes} \author{Thomas Friedrichsmeier \email{thomas.friedrichsmeier@ruhr-uni-bochum.de}} \section{Slots}{\describe{\item{\code{id}:}{}\item{\code{call}:}{}\item{\code{fuzzy_output}:}{}\item{\code{expect_error}:}{}\item{\code{libraries}:}{}\item{\code{files}:}{}}} rkward-0.6.4/rkward/rbackend/rpackages/rkwardtests/man/RKTestResult-class.Rd0000644000175000017500000000106412455741221026467 0ustar thomasthomas\name{RKTestResult-class} \alias{RKTestResult -class} \alias{RKTestResult} \title{S4 class RKTestResult} \description{Class RKTestResult} \details{This class is used internally by \code{\link[rkwardtests:rktest.makeplugintests]{rktest.makeplugintests}}.} \keyword{classes} \author{Thomas Friedrichsmeier \email{thomas.friedrichsmeier@ruhr-uni-bochum.de}} \section{Slots}{\describe{\item{\code{id}:}{}\item{\code{code_match}:}{}\item{\code{output_match}:}{}\item{\code{message_match}:}{}\item{\code{error}:}{}\item{\code{missing_libs}:}{}\item{\code{passed}:}{}}} rkward-0.6.4/rkward/rbackend/rpackages/rkwardtests/man/rktest.replaceRunAgainLink.Rd0000644000175000017500000000211412455741221030177 0ustar thomasthomas\name{rktest.replaceRunAgainLink} \alias{.rk.rerun.plugin.link.replacement} \alias{rktest.replaceRunAgainLink} \title{Replace "Run again" link in RKWard} \usage{ rktest.replaceRunAgainLink(restore=FALSE) } \arguments{ \item{restore}{Logical: If TRUE, restore the original behaviour.} } \value{ Replaces the "Run again" link in RKWard with the code that would have been called, or vice versa. } \description{ Replace "Run again" link in RKWard with code } \details{ You can use this to temporarily replace .rk.rerun.plugin.link (see example below). This way, after running a plugin, you are shown the call needed to run this plugin with those settings, instead of the link. This code can be used in a plugin test suite. } \examples{ rktest.replaceRunAgainLink() } \author{ Thomas Friedrichsmeier \email{thomas.friedrichsmeier@ruhr-uni-bochum.de}, Meik Michalke \email{meik.michalke@uni-duesseldorf.de} } \seealso{ \code{\link[rkwardtests:RKTestSuite]{RKTestSuite-class}}, \code{\link[rkwardtests:rktest.makeplugintests]{rktest.makeplugintests}} } \keyword{utilities} rkward-0.6.4/rkward/rbackend/rpackages/rkwardtests/man/RKTestSuite-class.Rd0000644000175000017500000000102512455741221026277 0ustar thomasthomas\name{RKTestSuite-class} \alias{RKTestSuite -class} \alias{RKTestSuite} \title{S4 class RKTestSuite} \description{Class RKTestSuite} \details{This class is used to create test suite objects that can be fed to \code{\link[rkwardtests:rktest.makeplugintests]{rktest.makeplugintests}}.} \keyword{classes} \author{Thomas Friedrichsmeier \email{thomas.friedrichsmeier@ruhr-uni-bochum.de}} \section{Slots}{\describe{\item{\code{id}:}{}\item{\code{libraries}:}{}\item{\code{initCalls}:}{}\item{\code{tests}:}{}\item{\code{postCalls}:}{}}} rkward-0.6.4/rkward/rbackend/rpackages/rkwardtests/man/rktest.setSuiteStandards.Rd0000644000175000017500000000236312455741221030000 0ustar thomasthomas\name{rktest.setSuiteStandards} \alias{rktest.setSuiteStandards} \title{Set RKWard suite standards} \usage{ rktest.setSuiteStandards(suite, testroot=getwd(), file=TRUE) } \arguments{ \item{suite}{Character string naming the test suite to set standards for.} \item{testroot}{Path to the test root directory, defaults to the working directory.} \item{file}{Logical: If \code{suite} is already a present R object, set this to FALSE. Otherwise it is assumed to be a file and fed to \code{source}.} } \value{ The function simply copies the previously created files from the temporary directory to the directory containing the test standards (inside the testroot). } \description{ Set RKWard plugin test suite standards } \details{ Use this function after you plugin passed all tests to set the resulting code, output and R messages as the standard that will be compared to during following tests. } \examples{ \dontrun{ rktest.setSuiteStandards("rkward_application_tests.R") } } \author{ Thomas Friedrichsmeier \email{thomas.friedrichsmeier@ruhr-uni-bochum.de} } \seealso{ \code{\link[rkwardtests:RKTestSuite]{RKTestSuite-class}}, \code{\link[rkwardtests:rktest.makeplugintests]{rktest.makeplugintests}} } \keyword{utilities} rkward-0.6.4/rkward/rbackend/rpackages/rkwardtests/NAMESPACE0000644000175000017500000000045012455741221023164 0ustar thomasthomasexportClasses(RKTest) exportClasses(RKTestResult) exportClasses(RKTestSuite) exportMethods(show) export(rktest.getTempDir) export(rktest.makeplugintests) export(rktest.replaceRunAgainLink) export(rktest.runRKTestSuite) export(rktest.setSuiteStandards) export(.rktest.tmp.storage) import(methods) rkward-0.6.4/rkward/rbackend/rpackages/rpackage_install.cmake.in0000664000175000017500000000241612633754364024322 0ustar thomasthomasSET(DESTDIR $ENV{DESTDIR}) MESSAGE(STATUS "Installing R support packages") IF(NOT ${BUILD_TIMESTAMP} EQUAL "") SET (TIMESTAMPARG "--built-timestamp=${BUILD_TIMESTAMP}") ENDIF(NOT ${BUILD_TIMESTAMP} EQUAL "") IF(WIN32) SET(R_LIBDIR @R_LIBDIR@) IF(DESTDIR) # strip drive letter STRING(REGEX REPLACE "^.:." "" R_LIBDIR ${R_LIBDIR}) SET(R_LIBDIR "${DESTDIR}/${R_LIBDIR}") FILE(MAKE_DIRECTORY "${R_LIBDIR}") ENDIF(DESTDIR) EXECUTE_PROCESS( COMMAND @R_EXECUTABLE@ CMD INSTALL ${TIMESTAMPARG} -c -l ${R_LIBDIR} "@CMAKE_CURRENT_SOURCE_DIR@/rkward" "@CMAKE_CURRENT_SOURCE_DIR@/rkwardtests" WORKING_DIRECTORY @CMAKE_CURRENT_BINARY_DIR@ RESULT_VARIABLE R_LIB_INSTALL_EXIT_CODE ) ELSE(WIN32) EXECUTE_PROCESS( COMMAND mkdir -p @CMAKE_CURRENT_BINARY_DIR@/tmp ${DESTDIR}/@R_LIBDIR@ ) EXECUTE_PROCESS( COMMAND env TMPDIR=@CMAKE_CURRENT_BINARY_DIR@/tmp @R_EXECUTABLE@ CMD INSTALL ${TIMESTAMPARG} -c -l ${DESTDIR}/@R_LIBDIR@ "@CMAKE_CURRENT_SOURCE_DIR@/rkward" "@CMAKE_CURRENT_SOURCE_DIR@/rkwardtests" WORKING_DIRECTORY @CMAKE_CURRENT_BINARY_DIR@ RESULT_VARIABLE R_LIB_INSTALL_EXIT_CODE ) ENDIF(WIN32) IF(R_LIB_INSTALL_EXIT_CODE) MESSAGE (SEND_ERROR "Failed to install R support libraries. Please make sure you have the required permissions.") ENDIF(R_LIB_INSTALL_EXIT_CODE) rkward-0.6.4/rkward/rbackend/rkrbackendprotocol_shared.h0000664000175000017500000001434212633754364023040 0ustar thomasthomas/*************************************************************************** rkrbackendprotocol - description ------------------- begin : Thu Nov 04 2010 copyright : (C) 2010, 2011, 2013 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKRBACKENDPROTOCOL_SHARED_H #define RKRBACKENDPROTOCOL_SHARED_H #include #include #include "rcommand.h" class RCommandProxy; class RBackendRequest { public: enum RCallbackType { BackendExit, ShowMessage, ShowFiles, ChooseFile, EditFiles, ReadLine, // 5 CommandOut, Started, EvalRequest, CallbackRequest, HistoricalSubstackRequest, // 10 PlainGenericRequest, SetParamsFromBackend, Debugger, CommandLineIn, /**< The next line of the current user command has been submitted in the backend. */ Output, /**< A piece of output. Note: If the backend runs in a single process, output is handled in a pull fashion, instead of using requests. */ //15 Interrupt, /**< Interrupt evaluation. This request type originates in the frontend, not the backend. */ PriorityCommand, /**< Send a command to be run during R's event processing. This request type originates in the frontend, not the backend. */ OutputStartedNotification, /**< Only used in the frontend: Notification that a new bit of output has arrived. Used to trigger flushing after a timeout. */ OtherRequest /**< Any other type of request. Note: which requests are in the enum, and which are not has mostly historical reasons. @see params */ }; RBackendRequest (bool synchronous, RCallbackType type); ~RBackendRequest (); RCommandProxy *takeCommand () { RCommandProxy* ret = command; command = 0; return ret; } ROutputList *takeOutput () { ROutputList* ret = output; output = 0; return ret; } /** Should this request be handled synchronously? False by default. */ bool synchronous; /** For synchronous requests, only: The frontend-thread will set this to true (using completed()), once the request has been "completed". Important: The backend thread MUST NOT touch a request after it has been sent, and before "done" has been set to true. */ bool volatile done; int id; static int _id; RCallbackType type; /** For synchronous requests, only: If the frontend wants any commands to be executed, it will place the next one in this slot. The backend thread should keep executing commands (in a sub-eventloop) while this is non-zero. Also, the backend-thread may place here any command that has just finished. */ RCommandProxy *command; /** Any other parameters, esp. for RCallbackType::OtherRequest. Can be used in both directions. */ QVariantMap params; /** NOTE: only used for separate process backend. See RCallbackType::Output */ ROutputList *output; /** NOTE: this does @em not copy merge the "done" flag. Do that manually, @em after merging (and don't touch the request from the transmitter thread, after that). */ void mergeReply (RBackendRequest *reply); protected: friend class RKRBackendProtocolFrontend; friend class RKRBackendProtocolBackend; void completed () { if (!synchronous) delete this; else done = true; } /** duplicates the request. NOTE: The command, and output, if any are @em taken from the original, and transferred to the copy, not really duplicated. */ RBackendRequest *duplicate (); }; #include /** Simple event class to relay information from the RKRBackend to the main thread. This is basically like QCustomEvent in Qt3*/ class RKRBackendEvent : public QEvent { public: enum EventType { RKWardEvent = QEvent::User + 1 }; explicit RKRBackendEvent (RBackendRequest* data=0) : QEvent ((QEvent::Type) RKWardEvent) { _data = data; }; RKRBackendEvent (); RBackendRequest* data () { return _data; }; private: RBackendRequest* _data; }; /** This is a reduced version of an RCommand, intended for use in the R backend. */ class RCommandProxy : public RData { protected: friend class RCommand; friend class RKRBackend; friend class RKRBackendSerializer; friend class RBackendRequest; RCommandProxy (); ~RCommandProxy (); RCommandProxy (const QString &command, int type); public: // all these are public for technical reasons, only. QString command; int type; int id; int status; int has_been_run_up_to; }; class RKROutputBuffer { public: RKROutputBuffer (); virtual ~RKROutputBuffer (); /** This gets called on normal R output (R_WriteConsole). Used to get at output. returns true, if a *new* piece of output started, i.e. the buffer was empty before this. */ bool handleOutput (const QString &output, int len, ROutput::ROutputType type, bool allow_blocking=true); /** Flushes current output buffer. Meant to be called from RInterface::flushOutput, only. @param forcibly: if true, will always flush the output. If false, will flush the output only if the mutex can be locked without waiting. */ ROutputList flushOutput (bool forcibly=false); protected: /** Function to be called while waiting for downstream threads to catch up. Return false to make the buffer continue, immediately (e.g. to prevent lockups after a crash) */ virtual bool doMSleep (int msecs) = 0; private: /** current output */ ROutputList output_buffer; /** Provides thread-safety for the output_buffer */ QMutex output_buffer_mutex; /** current length of output. If the backlog of output which has not yet been processed by the frontend becomes too long, output will be paused, automatically */ int out_buf_len; }; namespace RKRSharedFunctionality { QString quote (const QString &string); }; #endif rkward-0.6.4/rkward/rbackend/rklocalesupport.cpp0000664000175000017500000002520112633754364021402 0ustar thomasthomas/*************************************************************************** rklocalesupport - description ------------------- begin : Sun Mar 11 2007 copyright : (C) 2007, 2009 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rklocalesupport.h" #include #include #ifndef Q_WS_WIN // see http://sourceforge.net/p/rkward/patches/4/ // seems to be needed for GCC 4.3 as well. # include #endif #include #include #include /* NOTE: The code in this file is an almost literal copy taken from setupLocaleMapper in qtextcodec.cpp in Qt 3.3.8 !*/ QTextCodec *checkForCodec(const char *name) { QTextCodec *c = QTextCodec::codecForName(name); if (!c) { const char *at = strchr(name, '@'); if (at) { QByteArray n(name, at - name); c = QTextCodec::codecForName(n.data()); } } return c; } /* locale names mostly copied from XFree86 */ static const char * const iso8859_2locales[] = { "croatian", "cs", "cs_CS", "cs_CZ","cz", "cz_CZ", "czech", "hr", "hr_HR", "hu", "hu_HU", "hungarian", "pl", "pl_PL", "polish", "ro", "ro_RO", "rumanian", "serbocroatian", "sh", "sh_SP", "sh_YU", "sk", "sk_SK", "sl", "sl_CS", "sl_SI", "slovak", "slovene", "sr_SP", 0 }; static const char * const iso8859_3locales[] = { "eo", 0 }; static const char * const iso8859_4locales[] = { "ee", "ee_EE", 0 }; static const char * const iso8859_5locales[] = { "mk", "mk_MK", "sp", "sp_YU", 0 }; static const char * const cp_1251locales[] = { "be", "be_BY", "bg", "bg_BG", "bulgarian", 0 }; static const char * const pt_154locales[] = { "ba_RU", "ky", "ky_KG", "kk", "kk_KZ", 0 }; static const char * const iso8859_6locales[] = { "ar_AA", "ar_SA", "arabic", 0 }; static const char * const iso8859_7locales[] = { "el", "el_GR", "greek", 0 }; static const char * const iso8859_8locales[] = { "hebrew", "he", "he_IL", "iw", "iw_IL", 0 }; static const char * const iso8859_9locales[] = { "tr", "tr_TR", "turkish", 0 }; static const char * const iso8859_13locales[] = { "lt", "lt_LT", "lv", "lv_LV", 0 }; static const char * const iso8859_15locales[] = { "et", "et_EE", // Euro countries "br_FR", "ca_ES", "de", "de_AT", "de_BE", "de_DE", "de_LU", "en_IE", "es", "es_ES", "eu_ES", "fi", "fi_FI", "finnish", "fr", "fr_FR", "fr_BE", "fr_LU", "french", "ga_IE", "gl_ES", "it", "it_IT", "oc_FR", "nl", "nl_BE", "nl_NL", "pt", "pt_PT", "sv_FI", "wa_BE", 0 }; static const char * const koi8_ulocales[] = { "uk", "uk_UA", "ru_UA", "ukrainian", 0 }; static const char * const tis_620locales[] = { "th", "th_TH", "thai", 0 }; static const char * const tcvnlocales[] = { "vi", "vi_VN", 0 }; static bool try_locale_list( const char * const locale[], const char * lang ) { int i; for( i=0; locale[i] && *locale[i] && strcmp(locale[i], lang); i++ ) ; return locale[i] != 0; } // For the probably_koi8_locales we have to look. the standard says // these are 8859-5, but almost all Russian users use KOI8-R and // incorrectly set $LANG to ru_RU. We'll check tolower() to see what // tolower() thinks ru_RU means. // If you read the history, it seems that many Russians blame ISO and // Perestroika for the confusion. // // The real bug is that some programs break if the user specifies // ru_RU.KOI8-R. static const char * const probably_koi8_rlocales[] = { "ru", "ru_SU", "ru_RU", "russian", 0 }; static QTextCodec * ru_RU_hack( const char * i ) { QTextCodec * ru_RU_codec = 0; QByteArray origlocale(setlocale(LC_CTYPE, i)); // unicode koi8r latin5 name // 0x044E 0xC0 0xEE CYRILLIC SMALL LETTER YU // 0x042E 0xE0 0xCE CYRILLIC CAPITAL LETTER YU int latin5 = tolower( 0xCE ); int koi8r = tolower( 0xE0 ); if ( koi8r == 0xC0 && latin5 != 0xEE ) { ru_RU_codec = QTextCodec::codecForName( "KOI8-R" ); } else if ( koi8r != 0xC0 && latin5 == 0xEE ) { ru_RU_codec = QTextCodec::codecForName( "ISO 8859-5" ); } else { // something else again... let's assume... *throws dice* ru_RU_codec = QTextCodec::codecForName( "KOI8-R" ); qWarning( "QTextCodec: using KOI8-R, probe failed (%02x %02x %s)", koi8r, latin5, i ); } setlocale( LC_CTYPE, origlocale.data() ); return ru_RU_codec; } QTextCodec *RKGetCurrentLocaleCodec () { QTextCodec *localeMapper = 0; #ifdef Q_OS_WIN32 localeMapper = QTextCodec::codecForName( "System" ); #else #if defined (_XOPEN_UNIX) && !defined(Q_OS_QNX6) && !defined(Q_OS_OSF) && !defined(Q_OS_MAC) char *charset = nl_langinfo (CODESET); if ( charset ) localeMapper = QTextCodec::codecForName( charset ); #endif if ( !localeMapper ) { // Very poorly defined and followed standards causes lots of code // to try to get all the cases... // Try to determine locale codeset from locale name assigned to // LC_CTYPE category. // First part is getting that locale name. First try setlocale() which // definitely knows it, but since we cannot fully trust it, get ready // to fall back to environment variables. char * ctype = qstrdup( setlocale( LC_CTYPE, 0 ) ); // Get the first nonempty value from $LC_ALL, $LC_CTYPE, and $LANG // environment variables. char * lang = qstrdup( getenv("LC_ALL") ); if ( !lang || lang[0] == 0 || strcmp( lang, "C" ) == 0 ) { if ( lang ) delete [] lang; lang = qstrdup( getenv("LC_CTYPE") ); } if ( !lang || lang[0] == 0 || strcmp( lang, "C" ) == 0 ) { if ( lang ) delete [] lang; lang = qstrdup( getenv("LANG") ); } // Now try these in order: // 1. CODESET from ctype if it contains a .CODESET part (e.g. en_US.ISO8859-15) // 2. CODESET from lang if it contains a .CODESET part // 3. ctype (maybe the locale is named "ISO-8859-1" or something) // 4. locale (ditto) // 5. check for "@euro" // 6. guess locale from ctype unless ctype is "C" // 7. guess locale from lang // 1. CODESET from ctype if it contains a .CODESET part (e.g. en_US.ISO8859-15) char * codeset = ctype ? strchr( ctype, '.' ) : 0; if ( codeset && *codeset == '.' ) localeMapper = checkForCodec( codeset + 1 ); // 2. CODESET from lang if it contains a .CODESET part codeset = lang ? strchr( lang, '.' ) : 0; if ( !localeMapper && codeset && *codeset == '.' ) localeMapper = checkForCodec( codeset + 1 ); // 3. ctype (maybe the locale is named "ISO-8859-1" or something) if ( !localeMapper && ctype && *ctype != 0 && strcmp (ctype, "C") != 0 ) localeMapper = checkForCodec( ctype ); // 4. locale (ditto) if ( !localeMapper && lang && *lang != 0 ) localeMapper = checkForCodec( lang ); // 5. "@euro" if ( !localeMapper && ctype && strstr( ctype, "@euro" ) || lang && strstr( lang, "@euro" ) ) localeMapper = QTextCodec::codecForName( "ISO 8859-15" ); // 6. guess locale from ctype unless ctype is "C" // 7. guess locale from lang char * try_by_name = ctype; if ( ctype && *ctype != 0 && strcmp (ctype, "C") != 0 ) try_by_name = lang; // Now do the guessing. if ( lang && *lang && !localeMapper && try_by_name && *try_by_name ) { if ( try_locale_list( iso8859_15locales, lang ) ) localeMapper = QTextCodec::codecForName( "ISO 8859-15" ); else if ( try_locale_list( iso8859_2locales, lang ) ) localeMapper = QTextCodec::codecForName( "ISO 8859-2" ); else if ( try_locale_list( iso8859_3locales, lang ) ) localeMapper = QTextCodec::codecForName( "ISO 8859-3" ); else if ( try_locale_list( iso8859_4locales, lang ) ) localeMapper = QTextCodec::codecForName( "ISO 8859-4" ); else if ( try_locale_list( iso8859_5locales, lang ) ) localeMapper = QTextCodec::codecForName( "ISO 8859-5" ); else if ( try_locale_list( iso8859_6locales, lang ) ) localeMapper = QTextCodec::codecForName( "ISO 8859-6" ); else if ( try_locale_list( iso8859_7locales, lang ) ) localeMapper = QTextCodec::codecForName( "ISO 8859-7" ); else if ( try_locale_list( iso8859_8locales, lang ) ) localeMapper = QTextCodec::codecForName( "ISO 8859-8-I" ); else if ( try_locale_list( iso8859_9locales, lang ) ) localeMapper = QTextCodec::codecForName( "ISO 8859-9" ); else if ( try_locale_list( iso8859_13locales, lang ) ) localeMapper = QTextCodec::codecForName( "ISO 8859-13" ); else if ( try_locale_list( tis_620locales, lang ) ) localeMapper = QTextCodec::codecForName( "ISO 8859-11" ); else if ( try_locale_list( koi8_ulocales, lang ) ) localeMapper = QTextCodec::codecForName( "KOI8-U" ); else if ( try_locale_list( cp_1251locales, lang ) ) localeMapper = QTextCodec::codecForName( "CP 1251" ); else if ( try_locale_list( pt_154locales, lang ) ) localeMapper = QTextCodec::codecForName( "PT 154" ); else if ( try_locale_list( probably_koi8_rlocales, lang ) ) localeMapper = ru_RU_hack( lang ); } delete [] ctype; delete [] lang; } if ( localeMapper && localeMapper->mibEnum() == 11 ) localeMapper = QTextCodec::codecForName( "ISO 8859-8-I" ); // If everything failed, we default to 8859-1 // We could perhaps default to 8859-15. if ( !localeMapper ) localeMapper = QTextCodec::codecForName( "ISO 8859-1" ); #endif return localeMapper; } rkward-0.6.4/rkward/rbackend/FindR.cmake0000644000175000017500000001455312455741221017445 0ustar thomasthomas# find the R binary MESSAGE(STATUS "Looking for R executable") IF(R_EXECUTABLE) MESSAGE(STATUS "Specified by user") ENDIF(R_EXECUTABLE) FIND_PROGRAM(R_EXECUTABLE R) IF(R_EXECUTABLE-NOTFOUND) MESSAGE(FATAL_ERROR "Could NOT find R (TODO: name option)") ELSE(R_EXECUTABLE-NOTFOUND) MESSAGE(STATUS "Using R at ${R_EXECUTABLE}") ENDIF(R_EXECUTABLE-NOTFOUND) # find out about R architecture (needed for some paths) EXECUTE_PROCESS( COMMAND ${R_EXECUTABLE} "--slave" "--no-save" "-e" "cat(R.version$arch)" OUTPUT_VARIABLE R_ARCH) MESSAGE (STATUS "R architecture is ${R_ARCH}") # check R version. SET (R_MIN_VERSION "2.8.0") MESSAGE (STATUS "Checking R version") EXECUTE_PROCESS( COMMAND ${R_EXECUTABLE} "--slave" "--no-save" "-e" "cat (paste(R.version$major, R.version$minor, sep='.'))" OUTPUT_VARIABLE R_VERSION) MESSAGE (STATUS "R version is ${R_VERSION}") EXECUTE_PROCESS( COMMAND ${R_EXECUTABLE} "--slave" "--no-save" "-e" "min_ver <- '${R_MIN_VERSION}'; if (compareVersion ('${R_VERSION}', min_ver) < 0) cat ('At least R version', min_ver, 'is required')" OUTPUT_VARIABLE R_VERSION_STATUS) IF (R_VERSION_STATUS) MESSAGE (FATAL_ERROR ${R_VERSION_STATUS}) ENDIF (R_VERSION_STATUS) # find R_HOME MESSAGE(STATUS "Looking for R_HOME") IF(NOT R_HOME) EXECUTE_PROCESS( COMMAND ${R_EXECUTABLE} "--slave" "--no-save" "-e" "cat(R.home())" OUTPUT_VARIABLE R_HOME) ELSE(NOT R_HOME) MESSAGE(STATUS "Specified by user") ENDIF(NOT R_HOME) IF(NOT R_HOME) MESSAGE(FATAL_ERROR "Could NOT determine R_HOME (probably you misspecified the location of R)") ELSE(NOT R_HOME) MESSAGE(STATUS "R_HOME is ${R_HOME}") ENDIF(NOT R_HOME) # find R include dir MESSAGE(STATUS "Looking for R include files") IF(NOT R_INCLUDEDIR) IF(WIN32 OR APPLE) # This version of the test will not work with R < 2.9.0, but the other version (in the else part) will not work on windows or apple (but we do not really need to support ancient versions of R, there). EXECUTE_PROCESS( COMMAND ${R_EXECUTABLE} "--slave" "--no-save" "-e" "cat(R.home('include'))" OUTPUT_VARIABLE R_INCLUDEDIR) ELSE(WIN32 OR APPLE) EXECUTE_PROCESS( COMMAND ${R_EXECUTABLE} CMD sh -c "echo -n $R_INCLUDE_DIR" OUTPUT_VARIABLE R_INCLUDEDIR) ENDIF(WIN32 OR APPLE) ELSE(NOT R_INCLUDEDIR) MESSAGE(STATUS "Location specified by user") ENDIF(NOT R_INCLUDEDIR) IF(NOT R_INCLUDEDIR) SET(R_INCLUDEDIR ${R_HOME}/include) MESSAGE(STATUS "Not findable via R. Guessing") ENDIF(NOT R_INCLUDEDIR) MESSAGE(STATUS "Include files should be at ${R_INCLUDEDIR}. Checking for R.h") FIND_FILE(R_H R.h PATHS ${R_INCLUDEDIR} NO_DEFAULT_PATH) IF(NOT R_H) MESSAGE(FATAL_ERROR "Not found") ELSE(NOT R_H) MESSAGE(STATUS "Found at ${R_H}") GET_FILENAME_COMPONENT(R_INCLUDEDIR ${R_H} PATH) ENDIF(NOT R_H) SET(R_INCLUDEDIR ${R_INCLUDEDIR} ${R_INCLUDEDIR}/${R_ARCH}) # check for existence of libR.so MESSAGE(STATUS "Checking for existence of R shared library") FIND_LIBRARY(LIBR_SO R PATHS ${R_HOME}/lib ${R_SHAREDLIBDIR} ${R_HOME}/bin ${R_HOME}/bin/${R_ARCH} ${R_HOME}/lib/${R_ARCH} NO_DEFAULT_PATH) IF(NOT LIBR_SO) MESSAGE(FATAL_ERROR "Not found. Make sure the location of R was detected correctly, above, and R was compiled with the --enable-R-shlib option") ELSE(NOT LIBR_SO) MESSAGE(STATUS "Exists at ${LIBR_SO}") GET_FILENAME_COMPONENT(R_SHAREDLIBDIR ${LIBR_SO} PATH) SET(R_USED_LIBS R) ENDIF(NOT LIBR_SO) # for at least some versions of R, we seem to have to link against -lRlapack. Else loading some # R packages will fail due to unresolved symbols, or we can't link against -lR. # However, we can't do this unconditionally, # as this is not available in some configurations of R MESSAGE(STATUS "Checking whether we should link against Rlapack library") FIND_LIBRARY(LIBR_LAPACK Rlapack PATHS ${R_SHAREDLIBDIR} NO_DEFAULT_PATH) IF(NOT LIBR_LAPACK) MESSAGE(STATUS "No, it does not exist in ${R_SHAREDLIBDIR}") ELSE(NOT LIBR_LAPACK) MESSAGE(STATUS "Yes, ${LIBR_LAPACK} exists") SET(R_USED_LIBS ${R_USED_LIBS} Rlapack) IF(WIN32 OR APPLE) ELSE(WIN32 OR APPLE) # needed when linking to Rlapack on linux for some unknown reason. # apparently not needed on windows (let's see, when it comes back to bite us, though) # and compiling on windows is hard enough even without requiring libgfortran, too. SET(R_USED_LIBS ${R_USED_LIBS} gfortran) ENDIF(WIN32 OR APPLE) ENDIF(NOT LIBR_LAPACK) # for at least some versions of R, we seem to have to link against -lRlapack. Else loading some # R packages will fail due to unresolved symbols, or we can't link against -lR. # However, we can't do this unconditionally, # as this is not available in some configurations of R MESSAGE(STATUS "Checking whether we should link against Rblas library") FIND_LIBRARY(LIBR_BLAS Rblas PATHS ${R_SHAREDLIBDIR} NO_DEFAULT_PATH) IF(NOT LIBR_BLAS) MESSAGE(STATUS "No, it does not exist in ${R_SHAREDLIBDIR}") ELSE(NOT LIBR_BLAS) MESSAGE(STATUS "Yes, ${LIBR_BLAS} exists") SET(R_USED_LIBS ${R_USED_LIBS} Rblas) ENDIF(NOT LIBR_BLAS) # find R package library location IF(WIN32) SET(PATH_SEP ";") ELSE(WIN32) SET(PATH_SEP ":") ENDIF(WIN32) MESSAGE(STATUS "Checking for R package library location to use") IF(NOT R_LIBDIR) EXECUTE_PROCESS( COMMAND ${R_EXECUTABLE} "--slave" "--no-save" "-e" "cat(paste(unique (c(.Library.site, .Library)), collapse='${PATH_SEP}'))" OUTPUT_VARIABLE R_LIBDIR) ELSE(NOT R_LIBDIR) MESSAGE(STATUS "Location specified by user") ENDIF(NOT R_LIBDIR) # strip whitespace STRING(REGEX REPLACE "[ \n]+" "" R_LIBDIR "${R_LIBDIR}") # strip leading colon(s) STRING(REGEX REPLACE "^${PATH_SEP}+" "" R_LIBDIR "${R_LIBDIR}") # strip trailing colon(s) STRING(REGEX REPLACE "${PATH_SEP}+$" "" R_LIBDIR "${R_LIBDIR}") # find first path STRING(REGEX REPLACE "${PATH_SEP}" " " R_LIBDIR "${R_LIBDIR}") IF(NOT R_LIBDIR) MESSAGE(STATUS "Not reliably determined or specified. Guessing.") SET(R_LIBDIR ${R_HOME}/library) ENDIF(NOT R_LIBDIR) SET(R_LIBDIRS ${R_LIBDIR}) SEPARATE_ARGUMENTS(R_LIBDIRS) SET(R_LIBDIR) FOREACH(CURRENTDIR ${R_LIBDIRS}) IF(NOT USE_R_LIBDIR) IF(EXISTS ${CURRENTDIR}) SET(R_LIBDIR ${CURRENTDIR}) SET(USE_R_LIBDIR 1) ELSE(EXISTS ${CURRENTDIR}) MESSAGE(STATUS "${CURRENTDIR} does not exist. Skipping") ENDIF(EXISTS ${CURRENTDIR}) ENDIF(NOT USE_R_LIBDIR) ENDFOREACH(CURRENTDIR ${R_LIBDIRS}) IF(NOT EXISTS ${R_LIBDIR}) MESSAGE(FATAL_ERROR "No existing library location found") ELSE(NOT EXISTS ${R_LIBDIR}) MESSAGE(STATUS "Will use ${R_LIBDIR}") ENDIF(NOT EXISTS ${R_LIBDIR}) rkward-0.6.4/rkward/rbackend/rkrsupport.h0000664000175000017500000000344212633754364020054 0ustar thomasthomas/*************************************************************************** rkrsupport - description ------------------- begin : Mon Oct 25 2010 copyright : (C) 2010 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKRSUPPORT_H #define RKRSUPPORT_H #include #include #include "rdata.h" #define R_NO_REMAP 1 #include /** Convenience functions for working with R. */ namespace RKRSupport { SEXP callSimpleFun0 (SEXP fun, SEXP env); SEXP callSimpleFun (SEXP fun, SEXP arg, SEXP env); SEXP callSimpleFun2 (SEXP fun, SEXP arg1, SEXP arg2, SEXP env); bool callSimpleBool (SEXP fun, SEXP arg, SEXP env); QStringList SEXPToStringList (SEXP from_exp); SEXP StringListToSEXP (const QStringList &list); QString SEXPToString (SEXP from_exp); RData::IntStorage SEXPToIntArray (SEXP from_exp); int SEXPToInt (SEXP from_exp, int def_value = INT_MIN); RData::RealStorage SEXPToRealArray (SEXP from_exp); RData* SEXPToRData (SEXP from_exp); }; #endif rkward-0.6.4/rkward/rbackend/rcommand.h0000664000175000017500000003255512633754364017430 0ustar thomasthomas/*************************************************************************** rcommand.h - description ------------------- begin : Mon Nov 11 2002 copyright : (C) 2002, 2006, 2007, 2009, 2010, 2013 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RCOMMAND_H #define RCOMMAND_H #include #include #include #include "rdata.h" #define MAX_RECEIVERS_PER_RCOMMAND 3 class RCommandReceiver; class RCommand; class RCommandProxy; /** R Commands can be arranged in a simple chain to make sure they are not interrupted by other commands. * Also, command may need to run sub-commands. @see \ref UsingTheInterfaceToR @see RInterface::startChain @see RInterface::closeChain */ class RCommandChain { public: bool isClosed () const { return closed; }; /** @returns true, if there are no sub-commands or sub-chains waiting in this chain */ bool isEmpty () const { return sub_commands.isEmpty (); }; bool isCommand () const { return is_command; }; RCommandChain* parentChain () const { return parent; }; RCommand *toCommand (); protected: friend class RCommandStack; friend class RCommandStackModel; RCommandChain (bool is_chain=true) : closed (!is_chain), is_command (!is_chain) {}; QList sub_commands; bool closed; bool is_command; RCommandChain *parent; }; /** this struct is used to store the R output to an RCommand. The RCommand basically keeps a list of ROutputString (s). The difference to a normal QString is, that additionally we store information on whether the output was "normal", "warning", or an "error". */ struct ROutput { enum ROutputType { NoOutput, /**< No output. Rarely used. */ Output, /**< normal output */ Warning, /**< R warning */ Error /**< R error */ }; ROutputType type; QString output; }; typedef QList ROutputList; /** Supplies signals for RCommands. * Obtain an instance of this using RCommand::notifier (); * Currently, only a single signal is available: When the command has finished. Further signals may be added, in the future. * * @Note You can also use this in connection with RCommandReceiver-based classes, if interested in RCommandReceiver::cancelOutstandingCommands(). */ class RCommandNotifier : public QObject { Q_OBJECT signals: void commandFinished (RCommand *command); private: friend class RCommand; RCommandNotifier (); ~RCommandNotifier (); void emitFinished (RCommand *command) { emit commandFinished (command); }; }; /** For introductory information on using RCommand, see \ref UsingTheInterfaceToR This class is used to encapsulate an R-command, so it can be easily identified in a chain of commands. It is needed, since communication with R is asynchronous and it is therefore not possible to get the result of an R-call right away. Instead, create an object of this class, specifying the RCommandReceiver that should be called when the command has finished. You can then retrieve all information on the command (including the reply) from the object that is passed to your handler. There are several ways to identify a command when it's finished (needed, if a single RCommandReceiver needs to handle the results of several different commands): - storing the id () (each command is automatically assigned a unique id, TODO: do we need this functionality? Maybe remove it for redundancy) - passing appropriate flags to know how to handle the command - keeping the pointer (CAUTION: don't use that pointer except to compare it with the pointer of an incoming command. Commands get deleted when they are finished, and maybe (in the future) if they become obsolete etc. Hence the pointers you keep may be invalid!) (- checking the command-string) Note that an RCommand carries a whole lot of information around. However, RCommands generally don't get kept around very long, so they should not be a memory issue. *@author Thomas Friedrichsmeier */ class RCommand : public RData, public RCommandChain { public: /** constructs an RCommand. @param command The command (string) to be run in the backend. This may include newlines and ";". The command should be a complete statement. If it is an incomplete statement, the backend will not wait for the rest of the command to come in, but rather the command will fail with RCommand::errorIncomplete. @param type An integer being the result of a bit-wise OR combination of the values in RCommand::CommandTypes. The type-parameter is used to indicate the type of command, and also how the command should retrieve information (as a usual string, or as a data vector). See \ref RCommand::CommandTypes @param rk_equiv Not yet used: a short descriptive string attached to the RCommand, that allows the user to make some sense of what this command is all about. @param receiver The RCommandReceiver this command should be passed on to, when finished. @Note: consider connecting to the notifier(), instead! @param flags A freely assignable integer, that you can use to identify what the command was all about. Only the RCommandReceiver handling the results will have to know what exactly the flags mean. */ explicit RCommand (const QString &command, int type, const QString &rk_equiv = QString (), RCommandReceiver *receiver=0, int flags=0); /** destructor. Note: you should not delete RCommands manually. This is done in RInterface. TODO: make protected */ ~RCommand(); /** @returns the type as specified in RCommand::RCommand */ int type () const { return _type; }; /** @returns the raw command status. @see CommandStatus */ int getStatus () const { return status; }; /** @returns the rk_equiv as specified in RCommand::RCommand */ QString rkEquivalent () const { return _rk_equiv; }; /** @returns the command string (i.e. the input) as specified in RCommand::RCommand */ QString command () const { return _command; }; /** @returns like command(), but for user commands, which have been run, partially, returns only the remaining portion of the command. */ QString remainingCommand () const; /** Each RCommand is assigned a unique integer id (incrementing from 0 to integer overflow) upon creation. This returns this id. @returns the unique id of this command */ int id () const { return _id; }; /* TODO: Adjust these two functions to allow re-getting of output and error-messages from logs */ /** @returns the full output of the command, i.e. all "regular" output, warning messages, and errors, in the order they were encountered. @see RCommand::output @see RCommand::error @see RCommand::warnings */ QString fullOutput () const; /** @returns the "regular" (ROutput::Output) output of the command, if any (e.g. "[1] 1" for "print (1)"). @see RCommand::succeeded @see RCommand::hasOutput */ QString output () const; /** @returns the warning message(s) given by R, if any. @see RCommand::output @see RCommand::error */ QString warnings () const; /** @returns the error message given by R, if any. @see RCommand::failed @see RCommand::hasError */ QString error () const; /** Types of commands (potentially more to come), bitwise or-able, although partially exclusive. See \ref UsingTheInterfaceToR for a overview of what these are used for. TODO: find out, why Canceled is in here, and document that fact. */ enum CommandTypes { User=1, /**< Command was created directly by the user (e.g. in the console or in a command editor window) */ Plugin=1 << 1, /**< Command comes from a plugin */ App=1 << 2, /**< Command comes from the application (e.g. loading / saving the workspace */ Sync=1 << 3, /**< Command is used to sync data to or from R-space. Typically used in the editor classes */ EmptyCommand=1 << 4, /**< Command is empty and will not be processed (an empty command may be used as a "marker") */ Console=1 << 5, /**< Command originated in the console. These commands will get some extra treatment in RKwatch */ Internal=1 << 6, /**< Command is meant to be used in the backend, only. Do not use outside rbackend classes! */ Silent=1 << 7, /**< Command can be interrupted, but is otherwise an internal command. In particular it should not be carbon copied to the output. */ GetIntVector=1 << 8, /**< Try to fetch result as an array of integers */ GetStringVector=1 << 9, /**< Try to fetch result as an array of chars */ GetRealVector=1 << 10, /**< Try to fetch result as an array of doubles */ GetStructuredData=1 << 11, /**< Try to fetch result as an RData structure */ CCOutput=1 << 12, /**< Append command output to the HTML-output file */ CCCommand=1 << 13, /**< Append the command itself to the HTML-output file */ ObjectListUpdate=1 << 14, /**< The command may change the list of objects available. Do an update */ QuitCommand=1 << 15, /**< The R backend should be killed */ PriorityCommand=1 << 16 /**< The command has high priority, should be run during R's event loop processing. In general, PriorityCommands *must* not have side-effects Use only, when absolutely necessary. */ }; enum CommandStatus { Running=1, /**< command is currently running */ WasTried=2, /**< the command has been passed to the backend. */ Failed=4, /**< the command failed */ HasOutput=8, /**< command has a string output retrievable via RCommand::output () */ HasError=16, /**< command has an error-message retrievable via RCommand::error () */ HasWarnings=32, /**< command has warning-message(s) retrievable via RCommand::warnings () */ ErrorIncomplete=512, /**< backend rejected command as being incomplete */ ErrorSyntax=1024, /**< backend rejected command as having a syntax error */ ErrorOther=2048, /**< another error (not incomplete, not syntax error) has occurred while trying to execute the command */ Canceled=8192 /**< Command was cancelled. */ }; /** the command has been passed to the backend. */ bool wasTried () const { return (status & WasTried); }; /** the command failed */ bool failed () const { return (status & Failed); }; /** the command was cancelled before it was executed */ bool wasCanceled () const { return (wasTried () && failed () && (status & Canceled)); } /** the command succeeded (wasTried () && (!failed ()) */ bool succeeded () const { return ((status & WasTried) && !(status & Failed)); }; /** command has a string output retrievable via RCommand::output () */ bool hasOutput () const { return (status & HasOutput); }; /** command has a string output retrievable via RCommand::warnings () */ bool hasWarnings () const { return (status & HasWarnings); }; /** command has an error-message retrievable via RCommand::error () */ bool hasError () const { return (status & HasError); }; /** backend rejected command as being incomplete */ bool errorIncomplete () const { return (status & ErrorIncomplete); }; /** backend rejected command as having a syntax error */ bool errorSyntax () const { return (status & ErrorSyntax); }; /** return the flags associated with the command. Those are the same that you specified in the constructor, RKWard does not touch them. @see RCommand::RCommand */ int getFlags () const { return (_flags); }; /** Add an additional listener to the command */ void addReceiver (RCommandReceiver *receiver); /** Remove a receiver from the list. This may be needed when a listener wants to self-destruct, to make sure we don't try to send any further info there */ void removeReceiver (RCommandReceiver *receiver); void addTypeFlag (int flag) { _type |= flag; }; ROutputList &getOutput () { return output_list; }; /** modify the command string. DO NOT CALL THIS after the command has been submitted! */ void setCommand (const QString &command) { _command = command; }; /** creates a proxy for this RCommand */ RCommandProxy* makeProxy () const; void mergeAndDeleteProxy (RCommandProxy *proxy); /** returns a notifier for this command (creating it, if needed). You can connect to the notifiers signals. */ RCommandNotifier* notifier (); /** same as RObject::rQuote */ static QString rQuote (const QString "ed); private: friend class RInterface; friend class RCommandStack; friend class RCommandStackModel; /** internal function will be called by the backend, as the command gets passed through. Takes care of sending this command (back) to its receiver(s) */ void finished (); /** new output was generated. Pass on to receiver(s) */ void newOutput (ROutput *output); /** next line of command has been transmitted. Pass on to receiver(s). Only called for RCommand::User type commands */ void commandLineIn (); ROutputList output_list; QString _command; int _type; int _flags; int status; int has_been_run_up_to; QString _rk_equiv; int _id; static int next_id; RCommandReceiver *receivers[MAX_RECEIVERS_PER_RCOMMAND]; RCommandNotifier *_notifier; }; #endif rkward-0.6.4/rkward/rbackend/rkrbackendprotocol_backend.cpp0000664000175000017500000001304612633754364023514 0ustar thomasthomas/*************************************************************************** rkrbackendprotocol - description ------------------- begin : Thu Nov 04 2010 copyright : (C) 2010, 2013 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkrbackendprotocol_backend.h" #include "rkrbackend.h" #include "../debug.h" #include #include #include #include #include "rktransmitter.h" #include #include "rkbackendtransmitter.h" #include // mis-used as a random-string generator #include #include extern "C" void RK_setupGettext (const char*); int RK_Debug_Level = 2; int RK_Debug_Flags = DEBUG_ALL; QMutex RK_Debug_Mutex; QTemporaryFile* RK_Debug_File; void RKDebugMessageOutput (QtMsgType type, const char *msg) { RK_Debug_Mutex.lock (); if (type == QtFatalMsg) { fprintf (stderr, "%s\n", msg); } RK_Debug_File->write (msg); RK_Debug_File->write ("\n"); RK_Debug_File->flush (); RK_Debug_Mutex.unlock (); } // NOTE: This function serves no benefit over qDebug() in the backend. But provided for consistency with the frontend. // See the frontend version in main.cpp void RKDebug (int flags, int level, const char *fmt, ...) { Q_UNUSED (flags); Q_UNUSED (level); const int bufsize = 1024*8; char buffer[bufsize]; va_list ap; va_start (ap, fmt); vsnprintf (buffer, bufsize-1, fmt, ap); va_end (ap); RKDebugMessageOutput (QtDebugMsg, buffer); } int main(int argc, char *argv[]) { QCoreApplication app (argc, argv); setvbuf (stdout, NULL, _IONBF, 0); setvbuf (stderr, NULL, _IONBF, 0); RK_Debug_File = new QTemporaryFile (QDir::tempPath () + "/rkward.rbackend"); RK_Debug_File->setAutoRemove (false); if (RK_Debug_File->open ()) qInstallMsgHandler (RKDebugMessageOutput); QString servername, rkd_server_name; QString data_dir, locale_dir; QStringList args = app.arguments (); for (int i = 1; i < args.count (); ++i) { if (args[i].startsWith ("--debug-level")) { RK_Debug_Level = args[i].section ('=', 1).toInt (); } else if (args[i].startsWith ("--server-name")) { servername = args[i].section ('=', 1); } else if (args[i].startsWith ("--data-dir")) { #ifdef __GNUC__ # warning What about paths with spaces?! #endif data_dir = args[i].section ('=', 1); } else if (args[i].startsWith ("--locale-dir")) { locale_dir = args[i].section ('=', 1); } else if (args[i].startsWith ("--rkd-server-name")) { rkd_server_name = args[i].section ('=', 1); } else { printf ("unknown argument %s", qPrintable (args[i])); } } if (servername.isEmpty ()) { printf ("no server to connect to\n"); return 1; } // a simple security token to send to the frontend to make sure that it is really talking to the backend process that it started in the local socket connection. // this token is sent both via stdout and the local socket connection. The frontend simply compares both values. QString token = QUuid::createUuid ().toString (); printf ("%s\n", token.toLocal8Bit ().data ()); fflush (stdout); RKRBackendTransmitter transmitter (servername, token); RKRBackendProtocolBackend backend (data_dir, rkd_server_name); transmitter.start (); RKRBackend::this_pointer->run (locale_dir); transmitter.quit (); transmitter.wait (5000); if (!RKRBackend::this_pointer->isKilled ()) RKRBackend::tryToDoEmergencySave (); } RKRBackendProtocolBackend* RKRBackendProtocolBackend::_instance = 0; RKRBackendProtocolBackend::RKRBackendProtocolBackend (const QString &storage_dir, const QString &_rkd_server_name) { RK_TRACE (RBACKEND); _instance = this; new RKRBackend (); r_thread = QThread::currentThread (); // R thread == main thread #ifndef Q_WS_WIN r_thread_id = QThread::currentThreadId (); #endif data_dir = storage_dir; rkd_server_name = _rkd_server_name; } RKRBackendProtocolBackend::~RKRBackendProtocolBackend () { RK_TRACE (RBACKEND); } void RKRBackendProtocolBackend::sendRequest (RBackendRequest *_request) { RK_TRACE (RBACKEND); RBackendRequest* request = _request; if (!request->synchronous) { request = _request->duplicate (); // the instance we send to the frontend will remain in there, and be deleted, there _request->done = true; // for aesthetics } RKRBackendEvent* event = new RKRBackendEvent (request); RK_ASSERT (request->type != RBackendRequest::Output); qApp->postEvent (RKRBackendTransmitter::instance (), event); } bool RKRBackendProtocolBackend::inRThread () { return (QThread::currentThread () == instance ()->r_thread); } void RKRBackendProtocolBackend::msleep (int delay) { static_cast (RKRBackendTransmitter::instance ())->publicmsleep (delay); } QString RKRBackendProtocolBackend::backendDebugFile () { return RK_Debug_File->fileName (); } rkward-0.6.4/rkward/rbackend/rcommandstack.h0000664000175000017500000001374312633754364020454 0ustar thomasthomas/*************************************************************************** rcommandstack - description ------------------- begin : Mon Sep 6 2004 copyright : (C) 2004-2013 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RCOMMANDSTACK_H #define RCOMMANDSTACK_H #include "rcommand.h" /** * This class represents the top-level RCommandChain, which persists for the entire session. * Having a separate class for this (singleton!) is a bit of a historical left-over. However, it does make a bit of sense, as this essentially provides the API * for manipulation of RCommandChain s, which should be used by RInterface, only. * * The main job of this class is to allow fetching commands in the correct order. * * @author Thomas Friedrichsmeier */ class RCommandStack : public RCommandChain { public: RCommandStack (); ~RCommandStack (); /** add a command to the given chain (static, as it does no matter, which stack the chain belongs to) */ static void issueCommand (RCommand *command, RCommandChain *chain); /** add a sub-chain to the given chain (static, as it does no matter, which stack the chain belongs to) */ static RCommandChain *startChain (RCommandChain *parent); /** close the given chain, i.e. signal the chain may be deleted once its remaining commands are done (static, as it does no matter, which stack the chain belongs to). */ static void closeChain (RCommandChain *chain); /** removes the given RCommand from the stack. */ static void pop (RCommandChain *item); static bool popIfCompleted (RCommandChain *item); static RCommandChain* activeSubItemOf (RCommandChain *item); /** returns a pointer to the current command to be processed. NOTE: This is really non-const. Chains which have been closed might be removed. */ static RCommand* currentCommand (); static QList allCommands (); /** the regular command stack, i.e. not a callback */ static RCommandStack *regular_stack; private: static void issueCommandInternal (RCommandChain *child, RCommandChain *parent); static bool removeFromParent (RCommandChain *child); friend class RCommandStackModel; static void listCommandsRecursive (QList *list, const RCommandChain *chain); }; #include /** The model used to fetch a representation of and signal changes in the RCommandStack. Used for RKControlWindow. - All insertions / removals are signalled to the (single) model - it is ok for the model to be slow. - the model keeps track of (the number of) listeners, and does not do anything unless there are any listeners (including walking the stack) - RControlWindow will only be constructed on show, and destructed on hide, so as not to eat resources @author Thomas Friedrichsmeier */ class RCommandStackModel : public QAbstractItemModel { Q_OBJECT public: explicit RCommandStackModel (QObject *parent); ~RCommandStackModel (); /** implements QAbstractItemModel::index() */ QModelIndex index (int row, int column, const QModelIndex& parent = QModelIndex ()) const; /** implements QAbstractItemModel::parent() */ QModelIndex parent (const QModelIndex& index) const; /** implements QAbstractItemModel::rowCount() */ int rowCount (const QModelIndex& parent = QModelIndex ()) const; /** implements QAbstractItemModel::columnCount(). This is identical for all items */ int columnCount (const QModelIndex& parent = QModelIndex ()) const; /** implements QAbstractItemModel::data() */ QVariant data (const QModelIndex& index, int role = Qt::DisplayRole) const; /** reimplemented from QAbstractItemModel::headerData() to make only commands (not chains/stacks) selectable */ Qt::ItemFlags flags (const QModelIndex& index) const; /** reimplemented from QAbstractItemModel::headerData() to provide column names */ QVariant headerData (int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; /** static pointer to the model. Only one model will ever be around. */ static RCommandStackModel* getModel () { return static_model; }; /** add a listener. The model will do nothing, if there are no listeners. Remember to remove the listener again as soon as possible. @see removeListener() */ void addListener (); /** @see addListener() */ void removeListener (); /** call this, when you are about to remove an item from a command stack/chain, *before* you actually remove the item. When done, call popComplete(). @param parent The parent of the item to be removed */ void aboutToPop (RCommandChain* parent, int index); /** @see aboutToPop () */ void popComplete (); /** call this, when you are about to add an item to a command stack/chain, *before* you actually add the item. When done, call addComplete(). @param parent The parent of the item to be removed */ void aboutToAdd (RCommandChain* parent, int index); /** @see aboutToAdd () */ void addComplete (); /** call this, when you have made changes to an item, that should be reflected in RControlWindow @param item The item that was changed */ void itemChange (RCommandChain* item); private: /** number of listeners. If there are no listeners, the model will do almost nothing at all */ int listeners; static RCommandStackModel* static_model; /** create a model index for the given item */ QModelIndex indexFor (RCommandChain *item); }; #endif rkward-0.6.4/rkward/rbackend/rinterface.cpp0000664000175000017500000010637212633754364020304 0ustar thomasthomas/*************************************************************************** rinterface.cpp - description ------------------- begin : Fri Nov 1 2002 copyright : (C) 2002-2014 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rinterface.h" #include "rcommandstack.h" #include "rkrbackendprotocol_frontend.h" #include "../rkward.h" #include "../rkconsole.h" #include "../settings/rksettingsmoduler.h" #include "../settings/rksettingsmodulegeneral.h" #include "../settings/rksettingsmoduleoutput.h" #include "../settings/rksettingsmodulegraphics.h" #include "../settings/rksettingsmoduleplugins.h" #include "../core/robjectlist.h" #include "../core/renvironmentobject.h" #include "../core/rkmodificationtracker.h" #include "../dialogs/rkloadlibsdialog.h" #include "../dialogs/rkselectlistdialog.h" #include "../dialogs/rkreadlinedialog.h" #include "../dialogs/rkerrordialog.h" #include "../agents/showedittextfileagent.h" #include "../agents/rkeditobjectagent.h" #include "../agents/rkprintagent.h" #include "../agents/rkdebughandler.h" #include "../windows/rcontrolwindow.h" #include "../windows/rkworkplace.h" #include "../windows/rkcommandlog.h" #include "../windows/rkhtmlwindow.h" #include "../plugin/rkcomponentmap.h" #include "../misc/rkcommonfunctions.h" #include "../misc/rkmessagecatalog.h" #include "rksessionvars.h" #include "../windows/rkwindowcatcher.h" #ifndef DISABLE_RKWINDOWCATCHER // putting this here instead of the class-header so I'm able to mess with it often without long recompiles. Fix when it works! RKWindowCatcher *window_catcher; #endif // DISABLE_RKWINDOWCATCHER #include "../rkglobals.h" #include "../version.h" #include "../debug.h" #include #include #include #include #include #include #include // flush new pieces of output after this period of time: #define FLUSH_INTERVAL 100 #define GET_LIB_PATHS 1 #define GET_HELP_BASE 2 #define SET_RUNTIME_OPTS 3 #define STARTUP_PHASE2_COMPLETE 4 #define GET_R_VERSION 5 // statics double RInterface::na_real; int RInterface::na_int; RInterface::RInterface () { RK_TRACE (RBACKEND); #ifndef DISABLE_RKWINDOWCATCHER window_catcher = new RKWindowCatcher (); #endif // DISABLE_RKWINDOWCATCHER // If R_HOME is not set, most certainly the user called the binary without the wrapper script if (!getenv ("R_HOME")) { RK_DEBUG (RBACKEND, DL_ERROR, "No R_HOME environment variable set. RKWard will quit in a moment. Always start rkward in the default way unless you know what you're doing."); } new RCommandStackModel (this); RCommandStack::regular_stack = new RCommandStack (); startup_phase2_error = false; command_logfile_mode = NotRecordingCommands; previously_idle = false; locked = 0; backend_dead = false; num_active_output_record_requests = 0; previous_output_type = ROutput::NoOutput; flush_timer_id = 0; dummy_command_on_stack = 0; // create a fake init command RCommand *fake = new RCommand (i18n ("R Startup"), RCommand::App | RCommand::Sync | RCommand::ObjectListUpdate, i18n ("R Startup"), this, STARTUP_PHASE2_COMPLETE); issueCommand (fake); new RKSessionVars (this); new RKDebugHandler (this); new RKRBackendProtocolFrontend (this); RKRBackendProtocolFrontend::instance ()->setupBackend (); /////// Further initialization commands, which do not necessarily have to run before everything else can be queued, here. /////// // NOTE: will receive the list as a call plain generic request from the backend ("updateInstalledPackagesList") issueCommand (".rk.get.installed.packages()", RCommand::App | RCommand::Sync); } void RInterface::issueCommand (const QString &command, int type, const QString &rk_equiv, RCommandReceiver *receiver, int flags, RCommandChain *chain) { RK_TRACE (RBACKEND); issueCommand (new RCommand (command, type, rk_equiv, receiver, flags), chain); } RInterface::~RInterface(){ RK_TRACE (RBACKEND); if (num_active_output_record_requests) RK_DEBUG (RBACKEND, DL_WARNING, "%d requests for recording output still active on interface shutdown", num_active_output_record_requests); delete window_catcher; } bool RInterface::backendIsIdle () { RK_TRACE (RBACKEND); return (RCommandStack::regular_stack->isEmpty() && (!runningCommand())); } RCommand *RInterface::popPreviousCommand (int id) { RK_TRACE (RBACKEND); RK_ASSERT (!all_current_commands.isEmpty ()); for (int i = all_current_commands.size () - 1; i >= 0; --i) { RCommand *ret = all_current_commands[i]; if (ret->id () == id) { RCommandStack::pop (ret); all_current_commands.removeAt (i); return ret; } } RK_ASSERT (false); return 0; } RCommandChain* RInterface::openSubcommandChain (RCommand* parent_command) { RK_TRACE (RBACKEND); current_commands_with_subcommands.append (parent_command); return RCommandStack::startChain (parent_command); } void RInterface::closeSubcommandChain (RCommand* parent_command) { RK_TRACE (RBACKEND); if (current_commands_with_subcommands.contains (parent_command)) { current_commands_with_subcommands.removeAll (parent_command); doNextCommand (0); } if (parent_command && (parent_command == dummy_command_on_stack)) { all_current_commands.removeAll (dummy_command_on_stack); RCommandStack::pop (dummy_command_on_stack); handleCommandOut (dummy_command_on_stack); dummy_command_on_stack = 0; } } void RInterface::tryNextCommand () { RK_TRACE (RBACKEND); RCommand *command = RCommandStack::currentCommand (); if (command_requests.isEmpty ()) { // if the backend is not requesting anything, only priority commands will be pushed if (!command) return; if (!(command->type () & RCommand::PriorityCommand)) return; if (all_current_commands.contains (command)) return; } bool priority = command && (command->type () & RCommand::PriorityCommand); bool on_top_level = all_current_commands.isEmpty (); if (!(on_top_level && locked && !(priority))) { // do not respect locks for sub-commands if ((!on_top_level) && all_current_commands.contains (command)) { // all sub-commands of the current command have finished, it became the top-most item of the RCommandStack, again closeSubcommandChain (command); return; } if (command) { all_current_commands.append (command); if (command->status & RCommand::Canceled) { // avoid passing cancelled commands to R command->status |= RCommand::Failed; // notify ourselves... RCommand* dummy = popPreviousCommand (command->id ()); RK_ASSERT (dummy == command); handleCommandOut (command); return; } if (previously_idle) RKWardMainWindow::getMain ()->setRStatus (RKWardMainWindow::Busy); previously_idle = false; doNextCommand (command); return; } } if (on_top_level) { if (!previously_idle) RKWardMainWindow::getMain ()->setRStatus (RKWardMainWindow::Idle); previously_idle = true; } } void RInterface::handleCommandOut (RCommand *command) { RK_TRACE (RBACKEND); RK_ASSERT (command); #ifdef RKWARD_DEBUG int dl = DL_WARNING; // failed application commands are an issue worth reporting, failed user commands are not if (command->type () & RCommand::User) dl = DL_DEBUG; if (command->failed ()) { command->status |= RCommand::WasTried | RCommand::Failed; if (command->status & RCommand::ErrorIncomplete) { RK_DEBUG (RBACKEND, dl, "Command failed (incomplete)"); } else if (command->status & RCommand::ErrorSyntax) { RK_DEBUG (RBACKEND, dl, "Command failed (syntax)"); } else if (command->status & RCommand::Canceled) { RK_DEBUG (RBACKEND, dl, "Command failed (interrupted)"); } else { RK_DEBUG (RBACKEND, dl, "Command failed (other)"); } RK_DEBUG (RBACKEND, dl, "failed command was: '%s'", qPrintable (command->command ())); RK_DEBUG (RBACKEND, dl, "- error message was: '%s'", qPrintable (command->error ())); } #endif if (command->status & RCommand::Canceled) { command->status |= RCommand::HasError; ROutput *out = new ROutput; out->type = ROutput::Error; out->output = ("--- interrupted ---"); command->output_list.append (out); command->newOutput (out); } command->finished (); delete command; } void RInterface::doNextCommand (RCommand *command) { RK_TRACE (RBACKEND); RBackendRequest* command_request = currentCommandRequest (); if (!command_request) { if (!(command && (command->type () & RCommand::PriorityCommand))) return; } // importantly, this point is not reached for the fake startup command if (RK_Debug_CommandStep) { QTime t; t.start (); while (t.elapsed () < RK_Debug_CommandStep) {} } flushOutput (true); RCommandProxy *proxy = 0; if (command) { RKWardMainWindow::getMain ()->setWorkspaceMightBeModified (true); proxy = command->makeProxy (); RK_DEBUG (RBACKEND, DL_DEBUG, "running command: %s", command->command ().toLatin1().data ()); command->status |= RCommand::Running; RCommandStackModel::getModel ()->itemChange (command); RKCommandLog::getLog ()->addInput (command); if (command_logfile_mode != NotRecordingCommands) { bool record = true; if (command_logfile_mode != RecordingCommandsUnfiltered) { if (command->type () & (RCommand::Silent | RCommand::Sync)) record = false; } if (record) { command_logfile.write (command->command ().toUtf8 ()); command_logfile.write ("\n"); } } } if (command && (command->type () & RCommand::PriorityCommand)) { RKRBackendProtocolFrontend::sendPriorityCommand (proxy); } else { RK_ASSERT (command_request); command_request->command = proxy; RKRBackendProtocolFrontend::setRequestCompleted (command_request); command_requests.pop_back (); } } void RInterface::rCommandDone (RCommand *command) { RK_TRACE (RBACKEND); if (command->failed ()) { startup_phase2_error = true; return; } if (command->getFlags () == GET_LIB_PATHS) { RK_ASSERT (command->getDataType () == RData::StringVector); RKSettingsModuleRPackages::defaultliblocs += command->stringVector (); RCommandChain *chain = command->parent; RK_ASSERT (chain); RK_ASSERT (!chain->isClosed ()); // apply user configurable run time options QStringList commands = RKSettingsModuleR::makeRRunTimeOptionCommands () + RKSettingsModuleRPackages::makeRRunTimeOptionCommands () + RKSettingsModuleOutput::makeRRunTimeOptionCommands () + RKSettingsModuleGraphics::makeRRunTimeOptionCommands (); for (QStringList::const_iterator it = commands.begin (); it != commands.end (); ++it) { issueCommand (*it, RCommand::App | RCommand::Sync, QString (), this, SET_RUNTIME_OPTS, chain); } // initialize output file issueCommand ("rk.set.output.html.file (\"" + RKSettingsModuleGeneral::filesPath () + "/rk_out.html\")\n", RCommand::App | RCommand::Sync, QString (), this, SET_RUNTIME_OPTS, chain); closeChain (chain); } else if (command->getFlags () == GET_R_VERSION) { RK_ASSERT (command->getDataType () == RData::StringVector); RK_ASSERT (command->getDataLength () == 1); RKSessionVars::setRVersion (command->stringVector ().value (0)); } else if (command->getFlags () == GET_HELP_BASE) { RK_ASSERT (command->getDataType () == RData::StringVector); RK_ASSERT (command->getDataLength () == 1); RKSettingsModuleR::help_base_url = command->stringVector ().value (0); } else if (command->getFlags () == SET_RUNTIME_OPTS) { // no special handling. In case of failures, staturt_fail was set to true, above. } else if (command->getFlags () == STARTUP_PHASE2_COMPLETE) { QString message = startup_errors; if (startup_phase2_error) message.append (i18n ("

\t-An unspecified error occurred that is not yet handled by RKWard. Likely RKWard will not function properly. Please check your setup.

\n")); if (!message.isEmpty ()) { message.prepend (i18n ("

There was a problem starting the R backend. The following error(s) occurred:

\n")); QString details = command->fullOutput().replace('<', "<").replace('\n', "
"); if (!details.isEmpty ()) { // WORKAROUND for stupid KMessageBox behavior. (kdelibs 4.2.3) // If length of details <= 512, it tries to show the details as a QLabel. details = details.leftJustified (513); } KMessageBox::detailedError (0, message, details, i18n ("Error starting R"), KMessageBox::Notify | KMessageBox::AllowLink); } startup_errors.clear (); } } void RInterface::handleRequest (RBackendRequest* request) { RK_TRACE (RBACKEND); if (request->type == RBackendRequest::OutputStartedNotification) { RK_ASSERT (flush_timer_id == 0); flush_timer_id = startTimer (FLUSH_INTERVAL); // calls flushOutput (false); see timerEvent () RKRBackendProtocolFrontend::setRequestCompleted (request); return; } flushOutput (true); if (request->type == RBackendRequest::CommandOut) { RCommandProxy *cproxy = request->takeCommand (); RCommand *command = 0; // NOTE: the order of processing is: first try to submit the next command, then handle the old command. // The reason for doing it this way, instead of the reverse, is that this allows the backend thread / process to continue working, concurrently // NOTE: cproxy should only ever be 0 in the very first cycle if (cproxy) command = popPreviousCommand (cproxy->id); if (request->synchronous) command_requests.append (request); tryNextCommand (); if (cproxy) { RK_ASSERT (command); command->mergeAndDeleteProxy (cproxy); handleCommandOut (command); } tryNextCommand (); } else if (request->type == RBackendRequest::HistoricalSubstackRequest) { command_requests.append (request); processHistoricalSubstackRequest (request->params["call"].toStringList ()); } else if (request->type == RBackendRequest::PlainGenericRequest) { request->params["return"] = QVariant (processPlainGenericRequest (request->params["call"].toStringList ())); RKRBackendProtocolFrontend::setRequestCompleted (request); } else if (request->type == RBackendRequest::Started) { // The backend thread has finished basic initialization, but we still have more to do... startup_errors = request->params["message"].toString (); command_requests.append (request); RCommandChain *chain = openSubcommandChain (runningCommand ()); issueCommand ("paste (R.version[c (\"major\", \"minor\")], collapse=\".\")\n", RCommand::GetStringVector | RCommand::App | RCommand::Sync, QString (), this, GET_R_VERSION, chain); // find out about standard library locations issueCommand (".libPaths ()\n", RCommand::GetStringVector | RCommand::App | RCommand::Sync, QString (), this, GET_LIB_PATHS, chain); // start help server / determined help base url issueCommand (".rk.getHelpBaseUrl ()\n", RCommand::GetStringVector | RCommand::App | RCommand::Sync, QString (), this, GET_HELP_BASE, chain); // NOTE: more initialization commands get run *after* we have determined the standard library locations (see rCommandDone()) } else { processRBackendRequest (request); } } void RInterface::timerEvent (QTimerEvent *) { // do not trace. called periodically flushOutput (false); } void RInterface::flushOutput (bool forced) { // do not trace. called periodically // RK_TRACE (RBACKEND); ROutputList list = RKRBackendProtocolFrontend::instance ()->flushOutput (forced); // this must come _after_ the output has been flushed. if (forced || !list.isEmpty ()) { if (flush_timer_id != 0) { killTimer (flush_timer_id); flush_timer_id = 0; } } foreach (ROutput *output, list) { if (all_current_commands.isEmpty ()) { RK_DEBUG (RBACKEND, DL_WARNING, "output without receiver'%s'", qPrintable (output->output)); delete output; continue; // to delete the other output pointers, too } else { RK_DEBUG (RBACKEND, DL_DEBUG, "output '%s'", qPrintable (output->output)); } if (num_active_output_record_requests) { if (output->type != ROutput::Error) { // NOTE: skip error output. It has already been written as a warning. if (output->type != previous_output_type) { if (!recorded_output.isEmpty ()) recorded_output.append ("\n"); if (output->type == ROutput::Output) recorded_output.append ("
");
					else if (output->type == ROutput::Warning) recorded_output.append ("
");
					else {
						RK_ASSERT (false);
						recorded_output.append ("
");
					}

					previous_output_type = output->type;
				}
				recorded_output.append (Qt::escape (output->output));
			}
		}

		bool first = true;
		foreach (RCommand* command, all_current_commands) {
			ROutput *coutput = output;
			if (!first) {		// this output belongs to several commands at once. So we need to copy it.
				coutput = new ROutput;
				coutput->type = output->type;
				coutput->output = output->output;
			}
			first = false;

			if (coutput->type == ROutput::Output) {
				command->status |= RCommand::HasOutput;
				command->output_list.append (coutput);
			} else if (coutput->type == ROutput::Warning) {
				command->status |= RCommand::HasWarnings;
				command->output_list.append (coutput);
			} else if (coutput->type == ROutput::Error) {
				command->status |= RCommand::HasError;
				// An error output is typically just the copy of the previous output, so merge if possible
				if (command->output_list.isEmpty ()) {
					command->output_list.append (coutput);
				}
				if (command->output_list.last ()->output == coutput->output) {
					command->output_list.last ()->type = ROutput::Error;
					continue;	// don't call command->newOutput(), again!
				}
			}
			command->newOutput (coutput);
		}
	}
}

void RInterface::issueCommand (RCommand *command, RCommandChain *chain) { 
	RK_TRACE (RBACKEND);

	if (command->command ().isEmpty ()) command->_type |= RCommand::EmptyCommand;
	if (RKCarbonCopySettings::shouldCarbonCopyCommand (command)) {
		command->_type |= RCommand::CCCommand;
		if (RKCarbonCopySettings::includeOutputInCarbonCopy ()) command->_type |= RCommand::CCOutput;
	}
	RCommandStack::issueCommand (command, chain);
	tryNextCommand ();
}

RCommandChain *RInterface::startChain (RCommandChain *parent) {
	RK_TRACE (RBACKEND);

	return RCommandStack::startChain (parent);
};

void RInterface::closeChain (RCommandChain *chain) {
	RK_TRACE (RBACKEND);

	RCommandStack::closeChain (chain);
	tryNextCommand ();
};

void RInterface::cancelAll () {
	RK_TRACE (RBACKEND);

	QList all_commands = RCommandStack::regular_stack->allCommands ();
	foreach (RCommand* command, all_commands) cancelCommand (command);
}

bool RInterface::softCancelCommand (RCommand* command) {
	RK_TRACE (RBACKEND);

	if (!(command->type () && RCommand::Running)) {
		cancelCommand (command);
	}
	return command->status && RCommand::Canceled;
}

void RInterface::cancelCommand (RCommand *command) {
	RK_TRACE (RBACKEND);

	if (!(command->type () & RCommand::Sync)) {
		command->status |= RCommand::Canceled;
		if (command->type () && RCommand::Running) {
			if ((RKDebugHandler::instance ()->state () == RKDebugHandler::InDebugPrompt) && (command == RKDebugHandler::instance ()->command ())) {
				RKDebugHandler::instance ()->sendCancel ();
			} else {
				RKRBackendProtocolFrontend::instance ()->interruptCommand (command->id ());
			}
		}
		RCommandStackModel::getModel ()->itemChange (command);
	} else {
		RK_ASSERT (false);
	}
}

void RInterface::pauseProcessing (bool pause) {
	RK_TRACE (RBACKEND);

	if (pause) locked |= User;
	else locked -= locked & User;
}

QStringList RInterface::processPlainGenericRequest (const QStringList &calllist) {
	RK_TRACE (RBACKEND);

	QString call = calllist.value (0);
	if (call == "get.tempfile.name") {
		RK_ASSERT (calllist.count () == 3);
		return (QStringList (RKCommonFunctions::getUseableRKWardSavefileName (calllist.value (1), calllist.value (2))));
	} else if (call == "set.output.file") {
		RK_ASSERT (calllist.count () == 2);
		RKOutputWindowManager::self ()->setCurrentOutputPath (calllist.value (1));
	} else if (call == "getCSSlink") {
		return (QStringList (QString ("\n")));
	} else if (call == "wdChange") {
		// in case of separate processes, apply new working directory in frontend, too.
		QDir::setCurrent (calllist.value (1));
		RKWardMainWindow::getMain ()->updateCWD ();
	} else if (call == "highlightRCode") {
		return (QStringList (RKCommandHighlighter::commandToHTML (calllist.value (1))));
	} else if (call == "quit") {
		RKWardMainWindow::getMain ()->close ();
		// if we're still alive, quitting was cancelled
		return (QStringList ("FALSE"));
	} else if (call == "preLocaleChange") {
		int res = KMessageBox::warningContinueCancel (0, i18n ("A command in the R backend is trying to change the character encoding. While RKWard offers support for this, and will try to adjust to the new locale, this operation may cause subtle bugs, if data windows are currently open. Also the feature is not well tested, yet, and it may be advisable to save your workspace before proceeding.\nIf you have any data editor opened, or in any doubt, it is recommended to close those first (this will probably be auto-detected in later versions of RKWard). In this case, please choose 'Cancel' now, then close the data windows, save, and retry."), i18n ("Locale change"));
		if (res != KMessageBox::Continue) return (QStringList ("FALSE"));
	} else if (call == "listPlugins") {
		RK_ASSERT (calllist.count () == 1);
		return RKComponentMap::getMap ()->listPlugins ();
	} else if (call == "setPluginStatus") {
		QStringList params = calllist.mid (1);
		RK_ASSERT ((params.size () % 3) == 0);
		const int rows = params.size () / 3;
		QStringList ids = params.mid (0, rows);
		QStringList contexts = params.mid (rows, rows);
		QStringList visible = params.mid (rows*2, rows);
		RKComponentMap::getMap ()->setPluginStatus (ids, contexts, visible);
	} else if (call == "loadPluginMaps") {
		bool force = (calllist.value (1) == "force");
		bool reload = (calllist.value (2) == "reload");
		RKSettingsModulePlugins::registerPluginMaps (calllist.mid (3), force, reload);
	} else if (call == "updateInstalledPackagesList") {
		RKSessionVars::instance ()->setInstalledPackages (calllist.mid (1));
	} else if (call == "showHTML") {
		RK_ASSERT (calllist.count () == 2);
		RKWorkplace::mainWorkplace ()->openHelpWindow (calllist.value (1));
	} else if (call == "select.list") {
		QString title = calllist.value (1);
		bool multiple = (calllist.value (2) == "multi");
		int num_preselects = calllist.value (3).toInt ();
		QStringList preselects = calllist.mid (4, num_preselects);
		QStringList choices = calllist.mid (4 + num_preselects);

		QStringList results = RKSelectListDialog::doSelect (0, title, choices, preselects, multiple);
		if (results.isEmpty ()) results.append ("");	// R wants to have it that way
		return (results);
	} else if (call == "commandHistory") {
		if (calllist.value (1) == "get") {
			return (RKConsole::mainConsole ()->commandHistory ());
		} else {
			RKConsole::mainConsole ()->setCommandHistory (calllist.mid (2), calllist.value (1) == "append");
		}
	} else if (call == "getWorkspaceUrl") {
		KUrl url = RKWorkplace::mainWorkplace ()->workspaceURL ();
		if (!url.isEmpty ()) return (QStringList (url.url ()));
	} else if (call == "workplace.layout") {
		if (calllist.value (1) == "set") {
			if (calllist.value (2) == "close") RKWorkplace::mainWorkplace ()->closeAll ();
			QStringList list = calllist.mid (3);
			RKWorkplace::mainWorkplace ()->restoreWorkplace (list);
		} else {
			RK_ASSERT (calllist.value (1) == "get");
			return (RKWorkplace::mainWorkplace ()->makeWorkplaceDescription ());
		}
	} else if (call == "getSessionInfo") {
		// Non-translatable on purpose. This is meant for posting to the bug tracker, mostly.
		QStringList lines ("-- Frontend --");
		lines.append (RKSessionVars::frontendSessionInfo ());
		lines.append (QString ());
		lines.append ("-- Backend --");
		lines.append ("Debug message file (this may contain relevant diagnostic output in case of trouble):");
		lines.append (calllist.value (1));
		lines.append (QString ());
		lines.append ("R version (compile time): " + calllist.value (2));
		return (lines);
	} else if (call == "recordCommands") {
		RK_ASSERT (calllist.count () == 3);
		QString filename = calllist.value (1);
		bool unfiltered = (calllist.value (2) == "include.all");

		if (filename.isEmpty ()) {
			command_logfile_mode = NotRecordingCommands;
			command_logfile.close ();
		} else {
			if (command_logfile_mode != NotRecordingCommands) {
				return (QStringList ("Attempt to start recording, while already recording commands. Ignoring.)"));
			} else {
				command_logfile.setFileName (filename);
				bool ok = command_logfile.open (QIODevice::WriteOnly | QIODevice::Truncate);
				if (ok) {
					if (unfiltered) command_logfile_mode = RecordingCommandsUnfiltered;
					else command_logfile_mode = RecordingCommands;
				} else {
					return (QStringList ("Could not open file for writing. Not recording commands"));
				}
			}
		}
	} else if (call == "recordOutput") {
		// NOTE: requests to record output can overlap (i.e. several can be active at the same time). However, we always clear the buffer, each time a request ends, i.e. then
		// recorded output does NOT overlap.
		if (calllist.value (1) == "end") {
			RK_ASSERT (num_active_output_record_requests > 0);
			--num_active_output_record_requests;
			QString dummy = recorded_output;
			recorded_output.clear ();
			if (!dummy.isEmpty ()) dummy.append ("
\n"); previous_output_type = ROutput::NoOutput; return QStringList (dummy); } else { ++num_active_output_record_requests; } } else if (call == "printPreview") { RKPrintAgent::printPostscript (calllist.value (1), true); } else if (call == "endBrowserContext") { RKDebugHandler::instance ()->endDebug (); } else if (call == "switchLanguage") { RKMessageCatalog::switchLanguage (calllist.value (1)); } else { return (QStringList ("Error: unrecognized request '" + call + "'.")); } // for those calls which were recognized, but do not return anything return QStringList (); } void RInterface::processHistoricalSubstackRequest (const QStringList &calllist) { RK_TRACE (RBACKEND); RCommand *current_command = runningCommand (); RCommandChain *in_chain; if (!current_command) { // This can happen for Tcl events. Create a dummy command on the stack to keep things looping. current_command = new RCommand (QString (), RCommand::App | RCommand::EmptyCommand | RCommand::Sync); RCommandStack::issueCommand (current_command, 0); all_current_commands.append (current_command); dummy_command_on_stack = current_command; // so we can get rid of it again, after it's sub-commands have finished } in_chain = openSubcommandChain (current_command); QString call = calllist.value (0); if (call == "sync") { RK_ASSERT (calllist.count () >= 2); for (int i = 1; i < calllist.count (); ++i) { QString object_name = calllist[i]; RObject *obj = RObjectList::getObjectList ()->findObject (object_name); if (obj) { RK_DEBUG (RBACKEND, DL_DEBUG, "triggering update for symbol %s", object_name.toLatin1 ().data()); obj->markDataDirty (); obj->updateFromR (in_chain); } else { RK_DEBUG (RBACKEND, DL_WARNING, "lookup failed for changed symbol %s", object_name.toLatin1 ().data()); } } } else if (call == "syncenvs") { RK_DEBUG (RBACKEND, DL_DEBUG, "triggering update of object list"); int search_len = calllist.value (1).toInt (); RObjectList::getObjectList ()->updateFromR (in_chain, calllist.mid (2, search_len), calllist.mid (2 + search_len)); } else if (call == "syncglobal") { RK_DEBUG (RBACKEND, DL_DEBUG, "triggering update of globalenv"); RObjectList::getGlobalEnv ()->updateFromR (in_chain, calllist.mid (1)); #ifndef DISABLE_RKWINDOWCATCHER // NOTE: WARNING: When converting these to PlainGenericRequests, the occasional "error, figure margins too large" starts coming up, again. Not sure, why. } else if (call == "startOpenX11") { RK_ASSERT (calllist.count () == 2); window_catcher->start (calllist.value (1).toInt ()); } else if (call == "endOpenX11") { RK_ASSERT (calllist.count () == 2); window_catcher->stop (calllist.value (1).toInt ()); } else if (call == "updateDeviceHistory") { if (calllist.count () >= 2) { window_catcher->updateHistory (calllist.mid (1)); } } else if (call == "killDevice") { RK_ASSERT (calllist.count () == 2); window_catcher->killDevice (calllist.value (1).toInt ()); #endif // DISABLE_RKWINDOWCATCHER } else if (call == "edit") { RK_ASSERT (calllist.count () >= 2); QStringList object_list = calllist.mid (1); new RKEditObjectAgent (object_list, in_chain); } else if (call == "require") { if (calllist.count () >= 2) { QString lib_name = calllist[1]; KMessageBox::information (0, i18n ("The R-backend has indicated that in order to carry out the current task it needs the package '%1', which is not currently installed. We will open the package-management tool, and there you can try to locate and install the needed package.", lib_name), i18n ("Require package '%1'", lib_name)); RKLoadLibsDialog::showInstallPackagesModal (0, in_chain, lib_name); issueCommand (".rk.set.reply (\"\")", RCommand::App | RCommand::Sync, QString (), 0, 0, in_chain); } else { issueCommand (".rk.set.reply (\"Too few arguments in call to require.\")", RCommand::App | RCommand::Sync, QString (), 0, 0, in_chain); } } else if (call == "doPlugin") { if (calllist.count () >= 3) { QString message; bool ok; RKComponentMap::ComponentInvocationMode mode = RKComponentMap::ManualSubmit; if (calllist[2] == "auto") mode = RKComponentMap::AutoSubmit; else if (calllist[2] == "submit") mode = RKComponentMap::AutoSubmitOrFail; ok = RKComponentMap::invokeComponent (calllist[1], calllist.mid (3), mode, &message, in_chain); if (!message.isEmpty ()) { QString type = "warning"; if (!ok) type = "error"; issueCommand (".rk.set.reply (list (type=\"" + type + "\", message=\"" + RKCommonFunctions::escape (message) + "\"))", RCommand::App | RCommand::Sync, QString (), 0, 0, in_chain); } } else { RK_ASSERT (false); } } else { issueCommand ("stop (\"Unrecognized call '" + call + "'. Ignoring\")", RCommand::App | RCommand::Sync, QString (), 0, 0, in_chain); } closeChain (in_chain); } void RInterface::processRBackendRequest (RBackendRequest *request) { RK_TRACE (RBACKEND); // first, copy out the type. Allows for easier typing below RBackendRequest::RCallbackType type = request->type; if (type == RBackendRequest::CommandLineIn) { int id = request->params["commandid"].toInt (); RCommand *command = all_current_commands.value (0, 0); // User command will always be the first. if ((command == 0) || (command->id () != id)) { RK_ASSERT (false); } else { command->commandLineIn (); } } else if (type == RBackendRequest::ShowMessage) { QString caption = request->params["caption"].toString (); QString message = request->params["message"].toString (); QString button_yes = request->params["button_yes"].toString ();; QString button_no = request->params["button_no"].toString ();; QString button_cancel = request->params["button_cancel"].toString ();; KGuiItem button_yes_item = KStandardGuiItem::yes (); if (button_yes != "yes") button_yes_item.setText (button_yes); KGuiItem button_no_item = KStandardGuiItem::no (); if (button_no != "no") button_no_item.setText (button_no); KGuiItem button_cancel_item = KStandardGuiItem::cancel (); if (button_cancel != "cancel") button_cancel_item.setText (button_cancel); KMessageBox::DialogType dialog_type = KMessageBox::QuestionYesNoCancel; if (button_cancel.isEmpty ()) dialog_type = KMessageBox::QuestionYesNo; if (button_no.isEmpty () && button_cancel.isEmpty ()) { dialog_type = KMessageBox::Information; if (!request->synchronous) { // non-modal dialogs are not supported out of the box by KMessageBox; KDialog* dialog = new KDialog (); KMessageBox::createKMessageBox (dialog, QMessageBox::Information, message, QStringList (), QString (), 0, KMessageBox::Notify | KMessageBox::NoExec); dialog->setWindowTitle (caption); dialog->setAttribute (Qt::WA_DeleteOnClose); dialog->setButtons (KDialog::Ok); dialog->show(); RKRBackendProtocolFrontend::setRequestCompleted (request); return; } } int result = KMessageBox::messageBox (0, dialog_type, message, caption, button_yes_item, button_no_item, button_cancel_item); QString result_string; if ((result == KMessageBox::Yes) || (result == KMessageBox::Ok)) result_string = "yes"; else if (result == KMessageBox::No) result_string = "no"; else if (result == KMessageBox::Cancel) result_string = "cancel"; else RK_ASSERT (false); request->params["result"] = result_string; } else if (type == RBackendRequest::ReadLine) { QString result; // yes, readline *can* be called outside of a current command (e.g. from tcl/tk) bool dummy_command = false; RCommand *command = runningCommand (); if (!command) { command = new RCommand ("", RCommand::EmptyCommand); dummy_command = true; } bool ok = RKReadLineDialog::readLine (0, i18n ("R backend requests information"), request->params["prompt"].toString (), command, &result); request->params["result"] = QVariant (result); if (dummy_command) delete command; if (!ok) request->params["cancelled"] = QVariant (true); } else if (type == RBackendRequest::Debugger) { RKDebugHandler::instance ()->debugCall (request, runningCommand ()); return; // request will be closed by the debug handler } else if ((type == RBackendRequest::ShowFiles) || (type == RBackendRequest::EditFiles)) { ShowEditTextFileAgent::showEditFiles (request); return; // we are not done, yet! } else if (type == RBackendRequest::ChooseFile) { QString filename; if (request->params["new"].toBool ()) { filename = KFileDialog::getSaveFileName (); } else { filename = KFileDialog::getOpenFileName (); } request->params["result"] = QVariant (filename); } else if (type == RBackendRequest::SetParamsFromBackend) { na_real = request->params["na_real"].toDouble (); na_int = request->params["na_int"].toInt (); } else if (type == RBackendRequest::BackendExit) { if (request->params.value ("regular", QVariant (false)).toBool ()) backend_dead = true; // regular exit via QuitCommand if (!backend_dead) { backend_dead = true; QString message = request->params["message"].toString (); message += i18n ("\nThe R backend will be shut down immediately. This means, you can not use any more functions that rely on it. I.e. you can do hardly anything at all, not even save the workspace (but if you're lucky, R already did that). What you can do, however, is save any open command-files, the output, or copy data out of open data editors. Quit RKWard after that. Sorry!"); RKErrorDialog::reportableErrorMessage (0, message, QString (), i18n ("R engine has died"), "r_engine_has_died"); } } else { RK_ASSERT (false); } RKRBackendProtocolFrontend::setRequestCompleted (request); } #include "rinterface.moc" rkward-0.6.4/rkward/rbackend/rkrsupport.cpp0000664000175000017500000001576112633754364020416 0ustar thomasthomas/*************************************************************************** rkrsupport - description ------------------- begin : Mon Oct 25 2010 copyright : (C) 2010 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkrsupport.h" #include // needed to detect CHARSXP encoding #define IS_UTF8(x) (Rf_getCharCE(x) == CE_UTF8) #define IS_LATIN1(x) (Rf_getCharCE(x) == CE_LATIN1) #include #include "rkrbackend.h" #include "../debug.h" // This is sort of idiotic, but placing RKWard_RData_Tag into the RKRSupport-namespace somehow confuses the hell out of G++ (4.4.5) SEXP RKWard_RData_Tag; SEXP RKRSupport::callSimpleFun0 (SEXP fun, SEXP env) { SEXP call = Rf_allocVector (LANGSXP, 1); PROTECT (call); SETCAR (call, fun); SEXP ret = Rf_eval (call, env); UNPROTECT (1); /* call */ return ret; } SEXP RKRSupport::callSimpleFun (SEXP fun, SEXP arg, SEXP env) { SEXP call = Rf_allocVector (LANGSXP, 2); PROTECT (call); SETCAR (call, fun); SETCAR (CDR (call), arg); SEXP ret = Rf_eval (call, env); UNPROTECT (1); /* call */ return ret; } SEXP RKRSupport::callSimpleFun2 (SEXP fun, SEXP arg1, SEXP arg2, SEXP env) { SEXP call = Rf_allocVector (LANGSXP, 3); PROTECT (call); SETCAR (call, fun); SETCAR (CDR (call), arg1); SETCAR (CDDR (call), arg2); SEXP ret = Rf_eval (call, env); UNPROTECT (1); /* call */ return ret; } bool RKRSupport::callSimpleBool (SEXP fun, SEXP arg, SEXP env) { SEXP res = callSimpleFun (fun, arg, env); if ((Rf_length (res) < 1) || (TYPEOF (res) != LGLSXP)) { RK_ASSERT (TYPEOF (res) == LGLSXP); RK_ASSERT (Rf_length (res) >= 1); return false; } return ((bool) LOGICAL (res)[0]); } /** converts SEXP to strings, and returns the first string (or QString(), if SEXP contains no strings) */ QString RKRSupport::SEXPToString (SEXP from_exp) { RK_TRACE (RBACKEND); QStringList list = SEXPToStringList (from_exp); if (!list.isEmpty ()) return list[0]; return QString (); } QStringList RKRSupport::SEXPToStringList (SEXP from_exp) { RK_TRACE (RBACKEND); // bad format? coerce the vector first if (TYPEOF (from_exp) != STRSXP) { SEXP strexp; PROTECT (strexp = Rf_coerceVector (from_exp, STRSXP)); QStringList list = SEXPToStringList (strexp); UNPROTECT (1); return list; } // format already good? Avoid coercion (and associated copying) int count = Rf_length (from_exp); QStringList list; #if QT_VERSION >= 0x040700 list.reserve (count); #endif for (int i = 0; i < count; ++i) { SEXP dummy = STRING_ELT (from_exp, i); if (TYPEOF (dummy) != CHARSXP) { list.append (QString ("not defined")); // can this ever happen? } else { if (dummy == NA_STRING) { list.append (QString ()); } else { if (IS_UTF8 (dummy)) { list.append (QString::fromUtf8 ((char *) STRING_PTR (dummy))); } else if (IS_LATIN1 (dummy)) { list.append (QString::fromLatin1 ((char *) STRING_PTR (dummy))); } else { list.append (RKRBackend::this_pointer->current_locale_codec->toUnicode ((char *) STRING_PTR (dummy))); } } } } return list; } SEXP RKRSupport::StringListToSEXP (const QStringList& list) { RK_TRACE (RBACKEND); SEXP ret = Rf_allocVector (STRSXP, list.size ()); for (int i = 0; i < list.size (); ++i) { // TODO: in R 2.13.0 there is Rf_mkCharCE(). This could be used to set unicode strings, directly. But of course, we'd have to check, when exactly this was introduced. SET_STRING_ELT (ret, i, Rf_mkChar (RKRBackend::this_pointer->current_locale_codec->fromUnicode (list[i]).data ())); } return ret; } RData::IntStorage RKRSupport::SEXPToIntArray (SEXP from_exp) { RK_TRACE (RBACKEND); RData::IntStorage integers; // bad format? coerce the vector first if (TYPEOF (from_exp) != INTSXP) { SEXP intexp; PROTECT (intexp = Rf_coerceVector (from_exp, INTSXP)); integers = SEXPToIntArray (intexp); UNPROTECT (1); return integers; } // format already good? Avoid coercion (and associated copying) unsigned int count = Rf_length (from_exp); integers.reserve (count); for (unsigned int i = 0; i < count; ++i) { integers.append (INTEGER (from_exp)[i]); } return integers; } /** converts SEXP to integers, and returns the first int (def_value, if SEXP contains no ints) */ int RKRSupport::SEXPToInt (SEXP from_exp, int def_value) { RK_TRACE (RBACKEND); RData::IntStorage integers = SEXPToIntArray (from_exp); if (!integers.isEmpty ()) return integers[0]; return def_value; } RData::RealStorage RKRSupport::SEXPToRealArray (SEXP from_exp) { RK_TRACE (RBACKEND); RData::RealStorage reals; // bad format? coerce the vector first if (TYPEOF (from_exp) != REALSXP) { SEXP realexp; PROTECT (realexp = Rf_coerceVector (from_exp, REALSXP)); reals = SEXPToRealArray (realexp); UNPROTECT (1); return reals; } // format already good? Avoid coercion (and associated copying) unsigned int count = Rf_length (from_exp); reals.reserve (count); for (unsigned int i = 0; i < count; ++i) { reals.append (REAL (from_exp)[i]); if (R_IsNaN (reals[i]) || R_IsNA (reals[i])) reals[i] = NA_REAL; // for our purposes, treat all non-numbers as missing } return reals; } RData *RKRSupport::SEXPToRData (SEXP from_exp) { RK_TRACE (RBACKEND); RData *data = new RData; int type = TYPEOF (from_exp); switch (type) { case LGLSXP: case INTSXP: data->setData (SEXPToIntArray (from_exp)); break; case REALSXP: data->setData (SEXPToRealArray (from_exp)); break; case VECSXP: { unsigned int count = Rf_length (from_exp); RData::RDataStorage structure_array; structure_array.reserve (count); for (unsigned int i=0; i < count; ++i) { SEXP subexp = VECTOR_ELT (from_exp, i); //PROTECT (subexp); // should already be protected as part of the parent from_exp structure_array.append (SEXPToRData (subexp)); //UNPROTECT (1); } data->setData (structure_array); } break; /* case NILSXP: data->data = 0; data->datatype = RData::NoData; count = 0; break; */ case EXTPTRSXP: if (R_ExternalPtrTag (from_exp) == RKWard_RData_Tag) { // our very own data delete data; data = (RData*) R_ExternalPtrAddr (from_exp); R_ClearExternalPtr (from_exp); break; } case STRSXP: default: data->setData (SEXPToStringList (from_exp)); } return data; } rkward-0.6.4/rkward/rbackend/rkwarddevice/0000755000175000017500000000000012633754364020115 5ustar thomasthomasrkward-0.6.4/rkward/rbackend/rkwarddevice/rkgraphicsdevice_backendtransmitter.cpp0000664000175000017500000000721712633754364030113 0ustar thomasthomas/*************************************************************************** rkgraphicsdevice_backendtransmitter - description ------------------- begin : Mon Mar 18 20:06:08 CET 2013 copyright : (C) 2013 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkgraphicsdevice_backendtransmitter.h" #include #include "../rkrbackendprotocol_backend.h" #include "../rkbackendtransmitter.h" #include "../../debug.h" RKAsyncDataStreamHelper RKGraphicsDeviceBackendTransmitter::streamer; QIODevice* RKGraphicsDeviceBackendTransmitter::connection = 0; QMutex RKGraphicsDeviceBackendTransmitter::mutex; RKGraphicsDeviceBackendTransmitter* RKGraphicsDeviceBackendTransmitter::_instance = 0; RKGraphicsDeviceBackendTransmitter::RKGraphicsDeviceBackendTransmitter (QIODevice* _connection, bool is_q_local_socket) : QThread () { RK_TRACE (GRAPHICS_DEVICE); RK_ASSERT (!connection); RK_ASSERT (_connection); connection = _connection; streamer.setIODevice (connection); alive = true; is_local_socket = is_q_local_socket; start (); } RKGraphicsDeviceBackendTransmitter::~RKGraphicsDeviceBackendTransmitter () { RK_TRACE (GRAPHICS_DEVICE); delete connection; } RKGraphicsDeviceBackendTransmitter* RKGraphicsDeviceBackendTransmitter::instance () { if (_instance) return _instance; RK_TRACE (GRAPHICS_DEVICE); QLocalSocket *con = new QLocalSocket (); con->connectToServer (RKRBackendProtocolBackend::rkdServerName ()); con->waitForConnected (2000); if (con->state () == QLocalSocket::ConnectedState) { con->write (RKRBackendTransmitter::instance ()->connectionToken ().toLocal8Bit ().data ()); con->write ("\n"); con->waitForBytesWritten (1000); _instance = new RKGraphicsDeviceBackendTransmitter (con, true); return _instance; } return 0; } bool RKGraphicsDeviceBackendTransmitter::connectionAlive () { if (!_instance) return false; if (!_instance->is_local_socket) return true; return static_cast (_instance->connection)->state () == QLocalSocket::ConnectedState; } void RKGraphicsDeviceBackendTransmitter::run () { RK_TRACE (GRAPHICS_DEVICE); bool more_left = false; while (alive) { msleep (more_left ? 10 : 50); // it's ok to be lazy. If a request expects a reply, RKGraphicsDataStreamReadGuard will take care of pushing everything, itself. Essentially, this thread's job is simply to make sure we don't lag *too* far behind. // See note in RKRBackend::handleRequest(): sleeping short is CPU-intensive mutex.lock (); connection->waitForBytesWritten (100); more_left = connection->bytesToWrite (); mutex.unlock (); } RK_TRACE (GRAPHICS_DEVICE); } void RKGraphicsDeviceBackendTransmitter::kill () { if (_instance) { RK_TRACE (GRAPHICS_DEVICE); mutex.lock (); _instance->alive = false; mutex.unlock (); _instance->wait (1000); delete _instance; _instance = 0; } } rkward-0.6.4/rkward/rbackend/rkwarddevice/rkgraphicsdevice.cpp0000664000175000017500000003635412633754364024153 0ustar thomasthomas/*************************************************************************** rkgraphicsdevice_backendtransmitter - description ------------------- begin : Mon Mar 18 20:06:08 CET 2013 copyright : (C) 2013, 2014 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkgraphicsdevice.h" #include #include #include #include #include #include #include #include #include #include "rkgraphicsdevice_protocol_shared.h" #include "../rinterface.h" #include "../../rkglobals.h" #include "../../debug.h" #define UPDATE_INTERVAL 100 QHash RKGraphicsDevice::devices; RKGraphicsDevice::RKGraphicsDevice (double width, double height, const QString &title, bool antialias) : QObject (), #ifdef USE_QIMAGE_BUFFER area (qAbs (width) + 1, qAbs (height) + 1, QImage::Format_ARGB32), #else area (qAbs (width) + 1, qAbs (height) + 1), #endif base_title (title) { RK_TRACE (GRAPHICS_DEVICE); interaction_opcode = -1; dialog = 0; view = new QLabel (); view->installEventFilter (this); view->setScaledContents (true); // this is just for preview during scaling. The area will be re-sized and re-drawn from R. view->setFocusPolicy (Qt::StrongFocus); // for receiving key events for R's getGraphicsEvent() connect (view, SIGNAL (destroyed(QObject*)), this, SLOT (viewKilled())); connect (&updatetimer, SIGNAL (timeout()), this, SLOT (updateNow())); updatetimer.setSingleShot (true); clear (); if (antialias) painter.setRenderHints (QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); setActive (true); // sets window title } RKGraphicsDevice::~RKGraphicsDevice () { RK_TRACE (GRAPHICS_DEVICE); painter.end (); stopInteraction (); delete view; } void RKGraphicsDevice::viewKilled () { RK_TRACE (GRAPHICS_DEVICE); view = 0; closeDevice (devices.key (this)); } void RKGraphicsDevice::triggerUpdate () { updatetimer.start (UPDATE_INTERVAL); } void RKGraphicsDevice::updateNow () { if (!view) return; // device windows already killed, but this instance not yet removed. if (painter.isActive ()) painter.end (); #ifdef USE_QIMAGE_BUFFER view->setPixmap (QPixmap::fromImage (area)); #else view->setPixmap (area); #endif if (!view->isVisible ()) { view->resize (area.size ()); view->show (); } checkSize (); painter.begin (&area); } void RKGraphicsDevice::checkSize() { RK_TRACE (GRAPHICS_DEVICE); if (view->size () != area.size ()) { RKGlobals::rInterface ()->issueCommand (new RCommand ("rkward:::RK.resize (" + QString::number (devices.key (this) + 1) + ')', RCommand::PriorityCommand)); } } RKGraphicsDevice* RKGraphicsDevice::newDevice (int devnum, double width, double height, const QString &title, bool antialias) { RK_TRACE (GRAPHICS_DEVICE); if (devices.contains (devnum)) { RK_DEBUG (GRAPHICS_DEVICE, DL_ERROR, "Graphics device number %d already exists while trying to create it", devnum); closeDevice (devnum); } RKGraphicsDevice* dev = new RKGraphicsDevice (width, height, title.isEmpty () ? i18n ("Graphics Device Number %1").arg (QString::number (devnum+1)) : title, antialias); devices.insert (devnum, dev); return (dev); } void RKGraphicsDevice::closeDevice (int devnum) { RK_TRACE (GRAPHICS_DEVICE); RK_ASSERT (devices.contains (devnum)); devices.take (devnum)->deleteLater (); } void RKGraphicsDevice::clear (const QColor& col) { RK_TRACE (GRAPHICS_DEVICE); if (painter.isActive ()) painter.end (); #if QT_VERSION >= 0x040800 if (col.isValid ()) area.fill (col); else area.fill (QColor (255, 255, 255, 255)); #else if (col.isValid ()) area.fill (col.rgb ()); else area.fill (qRgb (255, 255, 255)); #endif updateNow (); setClip (area.rect ()); // R's devX11.c resets clip on clear, so we do this, too. } void RKGraphicsDevice::setAreaSize (const QSize& size) { if (painter.isActive ()) painter.end (); RK_DEBUG (GRAPHICS_DEVICE, DL_INFO, "New Size %d, %d (view size is %d, %d)", size.width (), size.height (), view->width (), view->height ()); #ifdef USE_QIMAGE_BUFFER area = QImage (size.width (), size.height (), QImage::Format_ARGB32); #else area = QPixmap (size.width (), size.height ()); #endif clear (); } void RKGraphicsDevice::setClip (const QRectF& new_clip) { RK_TRACE (GRAPHICS_DEVICE); if (!painter.isActive ()) painter.begin (&area); painter.setClipRect (new_clip); } void RKGraphicsDevice::circle (double x, double y, double r, const QPen& pen, const QBrush& brush) { RK_TRACE (GRAPHICS_DEVICE); painter.setPen (pen); painter.setBrush (brush); painter.drawEllipse (x - r, y - r, r+r, r+r); triggerUpdate (); } void RKGraphicsDevice::line (double x1, double y1, double x2, double y2, const QPen& pen) { RK_TRACE (GRAPHICS_DEVICE); painter.setPen (pen); // HACK: There seems to be a bug in QPainter (Qt 4.8.4), which can shift connected lines (everything but the first polyline) // towards the direction where the previous line came from. The result is that line drawn via drawLine() and drawPolyline() do // not match, exactly. This is particularly evident for the plot frame. // We hack around this, by doing all line drawing via drawPolyline. QPointF points [2]; points[0] = QPointF (x1, y1); points[1] = QPointF (x2, y2); painter.drawPolyline (points, 2); // painter.drawLine (x1, y1, x2, y2); triggerUpdate (); } void RKGraphicsDevice::rect (const QRectF& rec, const QPen& pen, const QBrush& brush) { RK_TRACE (GRAPHICS_DEVICE); painter.setPen (pen); painter.setBrush (brush); painter.drawRect (rec); triggerUpdate (); } QSizeF RKGraphicsDevice::strSize (const QString& text, const QFont& font) { RK_TRACE (GRAPHICS_DEVICE); painter.setFont (font); QSizeF size = painter.boundingRect (QRectF (area.rect ()), text).size (); return size; } void RKGraphicsDevice::text (double x, double y, const QString& text, double rot, double hadj, const QColor& col, const QFont& font) { RK_TRACE (GRAPHICS_DEVICE); painter.save (); QSizeF size = strSize (text, font); // NOTE: side-effect of setting font! // painter.setFont (font); painter.setPen (QPen (col)); painter.translate (x, y); painter.rotate (-rot); painter.drawText (-(hadj * size.width ()), 0, text); painter.restore (); // undo rotation / translation triggerUpdate (); } void RKGraphicsDevice::metricInfo (const QChar& c, const QFont& font, double* ascent, double* descent, double* width) { RK_TRACE (GRAPHICS_DEVICE); // Don't touch! This is the result of a lot of trial and error, and replicates the behavior of X11() on the ?plotmath examples QFontMetricsF fm (font); QRectF rect = fm.boundingRect (c); *ascent = -rect.top (); *descent = rect.bottom (); *width = fm.width (c); } void RKGraphicsDevice::polygon (const QPolygonF& pol, const QPen& pen, const QBrush& brush) { RK_TRACE (GRAPHICS_DEVICE); painter.setPen (pen); painter.setBrush (brush); painter.drawPolygon (pol); triggerUpdate (); } void RKGraphicsDevice::polyline (const QPolygonF& pol, const QPen& pen) { RK_TRACE (GRAPHICS_DEVICE); painter.setPen (pen); painter.drawPolyline (pol); triggerUpdate (); } void RKGraphicsDevice::polypath (const QVector& polygons, bool winding, const QPen& pen, const QBrush& brush) { RK_TRACE (GRAPHICS_DEVICE); painter.setPen (pen); painter.setBrush (brush); QPainterPath path; if (winding) path.setFillRule (Qt::WindingFill); for (int i = 0; i < polygons.size (); ++i) { path.addPolygon (polygons[i]); path.closeSubpath (); } painter.drawPath (path); triggerUpdate (); } void RKGraphicsDevice::image (const QImage& image, const QRectF& target_rect, double rot, bool interpolate) { RK_TRACE (GRAPHICS_DEVICE); painter.save (); QRectF tr = target_rect; painter.translate (tr.x (), tr.y ()); tr.moveTo (0, 0); painter.rotate (-rot); painter.setRenderHint (QPainter::SmoothPixmapTransform, interpolate); painter.drawImage (tr, image, image.rect ()); painter.restore (); triggerUpdate (); } QImage RKGraphicsDevice::capture () const { RK_TRACE (GRAPHICS_DEVICE); #ifdef USE_QIMAGE_BUFFER return area; #else return area.toImage (); #endif } void RKGraphicsDevice::setActive (bool active) { RK_TRACE (GRAPHICS_DEVICE); if (active) view->setWindowTitle (i18n ("%1 (Active)").arg (base_title)); else view->setWindowTitle (i18n ("%1 (Inactive)").arg (base_title)); emit (activeChanged (active)); emit (captionChanged (view->windowTitle ())); } void RKGraphicsDevice::goInteractive (const QString& prompt) { RK_TRACE (GRAPHICS_DEVICE); // The backend does not support trying to resize while it is waiting for a reply, and will produce error message in this case. // To avoid confusion and loads of scary error messages, we disable resizing in the frontend while interactive. // NOTE: It would certainly be possible, but rather cumbersome to support resizing during interaction in the backend. // It does not seem to be important enough, however. view->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed); view->setCursor (Qt::CrossCursor); view->setToolTip (prompt); view->show (); view->raise (); emit (goingInteractive (true, prompt)); } void RKGraphicsDevice::locator () { RK_TRACE (GRAPHICS_DEVICE); RK_ASSERT (interaction_opcode < 0); interaction_opcode = RKDLocator; goInteractive (i18n ("

Locating point(s)

Use left mouse button to select point(s). Any other mouse button to stop.

")); } void RKGraphicsDevice::confirmNewPage () { RK_TRACE (GRAPHICS_DEVICE); RK_ASSERT (interaction_opcode < 0); RK_ASSERT (dialog == 0); interaction_opcode = RKDNewPageConfirm; QString msg = i18n ("

Press Enter to see next plot, or click 'Cancel' to abort.

"); goInteractive (msg); dialog = new KDialog (view); dialog->setCaption (i18n ("Ok to show next plot?")); dialog->setButtons (KDialog::Ok | KDialog::Cancel); dialog->setMainWidget (new QLabel (msg, dialog)); // dialog->setWindowModality (Qt::WindowModal); // not good: Grays out the plot window connect (dialog, SIGNAL (finished(int)), this, SLOT (newPageDialogDone(int))); dialog->show (); } void RKGraphicsDevice::newPageDialogDone (int result) { RK_TRACE (GRAPHICS_DEVICE); RK_ASSERT (dialog); emit (newPageConfirmDone (result == KDialog::Accepted)); interaction_opcode = -1; stopInteraction (); } void RKGraphicsDevice::startGettingEvents (const QString& prompt) { RK_TRACE (GRAPHICS_DEVICE); RK_ASSERT (interaction_opcode < 0); stored_events.clear (); interaction_opcode = RKDStartGettingEvents; goInteractive (prompt); } RKGraphicsDevice::StoredEvent RKGraphicsDevice::fetchNextEvent () { RK_TRACE (GRAPHICS_DEVICE); if (stored_events.isEmpty ()) { StoredEvent ret; ret.event_code = RKDNothing; return ret; } return stored_events.takeFirst (); } void RKGraphicsDevice::stopGettingEvents () { RK_TRACE (GRAPHICS_DEVICE); RK_ASSERT (interaction_opcode == RKDStartGettingEvents); stopInteraction (); } bool RKGraphicsDevice::eventFilter (QObject *watched, QEvent *event) { RK_ASSERT (watched == view); if (interaction_opcode == RKDLocator) { if (event->type () == QEvent::MouseButtonRelease) { QMouseEvent *me = static_cast (event); if (me->button () == Qt::LeftButton) { emit (locatorDone (true, me->x (), me->y ())); interaction_opcode = -1; } stopInteraction (); return true; } } else if (interaction_opcode == RKDStartGettingEvents) { if ((event->type () == QEvent::MouseButtonPress) || (event->type () == QEvent::MouseButtonRelease) || (event->type () == QEvent::MouseMove)) { QMouseEvent *me = static_cast (event); StoredEvent sev; sev.event_code = event->type () == QEvent::MouseButtonPress ? RKDMouseDown : (event->type () == QEvent::MouseButtonRelease ? RKDMouseUp : RKDMouseMove); sev.x = me->x (); sev.y = me->y (); sev.buttons = 0; if (me->buttons () & Qt::LeftButton) sev.buttons |= RKDMouseLeftButton; if (me->buttons () & Qt::MidButton) sev.buttons |= RKDMouseMiddleButton; if (me->buttons () & Qt::RightButton) sev.buttons |= RKDMouseRightButton; // Mouse move event may be generated much faster than R can handle them. We simply lump them together // (unless any other event type (click, release, keypress) has occurred meanwhile, of course. if (!stored_events.isEmpty () && (sev.event_code == RKDMouseMove)) { if (stored_events.last ().event_code == RKDMouseMove) stored_events.pop_back (); } stored_events.append (sev); return (true); } else if (event->type () == QEvent::KeyPress) { QKeyEvent *ke = static_cast (event); StoredEvent sev; sev.event_code = RKDKeyPress; sev.keytext = ke->text (); sev.keycode = ke->key (); sev.modifiers = ke->modifiers (); if (sev.modifiers - (sev.modifiers & Qt::ShiftModifier)) { // well, the text returned in ke->text() is a bit strange, sometimes when modifiers are involved. // This HACK does some sanitizing. Too much sanitizing? Umlauts don't get through this, for one thing. sev.keytext = QKeySequence (sev.modifiers | sev.keycode).toString (); sev.keytext = sev.keytext.right (1); } stored_events.append (sev); return (true); } } else if (interaction_opcode == RKDNewPageConfirm) { if (event->type () == QEvent::KeyPress) { QKeyEvent *ke = static_cast (event); if ((ke->key () == Qt::Key_Return) || (ke->key () == Qt::Key_Enter)) { newPageDialogDone (KDialog::Accepted); return true; } else if (ke->key () == Qt::Key_Escape) { newPageDialogDone (KDialog::Rejected); return true; } } } if (event->type () == QEvent::Resize) triggerUpdate (); return false; } void RKGraphicsDevice::stopInteraction () { RK_TRACE (GRAPHICS_DEVICE); if (interaction_opcode == RKDLocator) { emit (locatorDone (false, 0.0, 0.0)); } else if (interaction_opcode == RKDNewPageConfirm) { RK_ASSERT (dialog); emit (newPageConfirmDone (true)); } else if (interaction_opcode == RKDStartGettingEvents) { // not much to do, fortunately, as getting graphics events is non-blocking stored_events.clear (); StoredEvent sev; sev.event_code = RKDFrontendCancel; stored_events.append (sev); // In case the backend keeps asking (without calling RKDStartGettingEvents again, first), we'll tell it to stop. } if (dialog) { dialog->deleteLater (); dialog = 0; } if (view) { // might already be destroyed view->setCursor (Qt::ArrowCursor); view->setToolTip (QString ()); checkSize (); view->setSizePolicy (QSizePolicy::Preferred, QSizePolicy::Preferred); } emit (goingInteractive (false, QString ())); interaction_opcode = -1; } #include "rkgraphicsdevice.moc" rkward-0.6.4/rkward/rbackend/rkwarddevice/rkgraphicsdevice_backendtransmitter.h0000664000175000017500000000421512633754364027553 0ustar thomasthomas/*************************************************************************** rkgraphicsdevice_backendtransmitter - description ------------------- begin : Mon Mar 18 20:06:08 CET 2013 copyright : (C) 2013 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKGRAPHICSDEVICE_BACKENDTRANSMITTER_H #define RKGRAPHICSDEVICE_BACKENDTRANSMITTER_H #include #include #include #include "rkgraphicsdevice_protocol_shared.h" #include "../rkasyncdatastreamhelper.h" /** This simple class is responsible for handling the backend side of transmitting data / requests for the RKGraphicsDevice Also it provides the namespace for some statics. As the protocol is really quite simple (only the backend send requests, only one request at a time), so is the transmitter. */ class RKGraphicsDeviceBackendTransmitter : public QThread { RKGraphicsDeviceBackendTransmitter (QIODevice *connection, bool is_q_local_socket); ~RKGraphicsDeviceBackendTransmitter (); public: static void kill (); static bool connectionAlive (); static RKGraphicsDeviceBackendTransmitter* instance (); static RKAsyncDataStreamHelper streamer; static QIODevice* connection; static QMutex mutex; private: static RKGraphicsDeviceBackendTransmitter* _instance; bool alive; bool is_local_socket; void run (); }; #endif rkward-0.6.4/rkward/rbackend/rkwarddevice/rkgraphicsdevice_stubs.cpp0000664000175000017500000005024612633754364025367 0ustar thomasthomas/*************************************************************************** rkgraphicsdevice_stubs - description ------------------- begin : Mon Mar 18 20:06:08 CET 2013 copyright : (C) 2013-2014 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ /* NOTE: This file is essentially a split-out from rkgraphicsdevice_setup.cpp, * not a compilation unit of its own. * It is meant to be included, there. */ #include "rkgraphicsdevice_protocol_shared.h" #include "rkgraphicsdevice_backendtransmitter.h" #include "../rkrbackend.h" #include "../rkreventloop.h" #include "../../debug.h" extern "C" { #include } #define RKD_IN_STREAM RKGraphicsDeviceBackendTransmitter::streamer.instream #define RKD_OUT_STREAM RKGraphicsDeviceBackendTransmitter::streamer.outstream static bool rkd_waiting_for_reply = false; static int rkd_suppress_on_exit = 0; /** This class is essentially like QMutexLocker. In addition, the constructor waits until the next chunk of the transmission is ready (and does event processing). * * @note: Never ever call Rf_error(), or any R function that might fail during the lifetime of an RKGraphicsDataStreamReadGuard or * RKGraphicsDataStreamWriteGuard. If R decides to long-jump out, the d'tor will not be called, the mutex will be left locked, and * the next graphics operation will hang, with no way to interrupt. * * At the same time, note that the RKGraphicsDataStreamReadGuard c'tor @em may cause R to long-jump (safely) in case of a user interrupt, * or if the connection was killed. Don't rely on the code following the creation of an RKGraphicsDataStreamReadGuard to be called. */ class RKGraphicsDataStreamReadGuard { public: RKGraphicsDataStreamReadGuard () { RKGraphicsDeviceBackendTransmitter::mutex.lock (); have_lock = true; rkd_waiting_for_reply = true; QIODevice* connection = RKGraphicsDeviceBackendTransmitter::connection; BEGIN_SUSPEND_INTERRUPTS { while (connection->bytesToWrite ()) { if (!connection->waitForBytesWritten (10)) { checkHandleError (); } if (connection->bytesToWrite ()) RKREventLoop::processX11Events (); } while (!RKGraphicsDeviceBackendTransmitter::streamer.readInBuffer ()) { RKREventLoop::processX11Events (); if (!connection->waitForReadyRead (10)) { if (checkHandleInterrupt (connection)) break; checkHandleError (); } } if (R_interrupts_pending) { if (have_lock) { RKGraphicsDeviceBackendTransmitter::mutex.unlock (); have_lock = false; // Will d'tor still be called? We don't rely on it. } rkd_waiting_for_reply = false; } } END_SUSPEND_INTERRUPTS; } ~RKGraphicsDataStreamReadGuard () { if (have_lock) RKGraphicsDeviceBackendTransmitter::mutex.unlock (); rkd_waiting_for_reply = false; } private: bool checkHandleInterrupt (QIODevice *connection) { // NOTE: It would be possible, but not exactly easier to rely on GEonExit() rather than R_interrupts_pending // Might be an option, if R_interrupts_pending gets hidden one day, though if (!R_interrupts_pending) return false; // Tell the frontend to finish whatever it was doing ASAP. Don't process any other events until that has happened RKGraphicsDeviceBackendTransmitter::streamer.outstream << (quint8) RKDCancel << (quint8) 0; RKGraphicsDeviceBackendTransmitter::streamer.writeOutBuffer (); while (connection->bytesToWrite ()) { if (!connection->waitForBytesWritten (10)) { checkHandleError (); } } int loop = 0; while (!RKGraphicsDeviceBackendTransmitter::streamer.readInBuffer ()) { if (!connection->waitForReadyRead (10)) { if (++loop > 500) { connection->close (); // If frontend is unresponsive, kill connection } checkHandleError (); } } return true; } void checkHandleError () { if (!RKGraphicsDeviceBackendTransmitter::connectionAlive ()) { // Don't go into endless loop, if e.g. frontend has crashed if (have_lock) RKGraphicsDeviceBackendTransmitter::mutex.unlock (); have_lock = false; // Will d'tor still be called? We don't rely on it. Rf_error ("RKWard Graphics connection has shut down"); } } bool have_lock; }; /** This class is essentially like QMutexLocker. In addition, the destructor takes care of pushing anything that was written to the protocol buffer during it lifetime to the transmitter. (Does NOT wait for the transmission itself). */ class RKGraphicsDataStreamWriteGuard { public: RKGraphicsDataStreamWriteGuard () { if (rkd_waiting_for_reply) { // For now, the backend does not support any nesting of graphics operations. It would make the protocol more complex. // I believe the only use-case is resizing during interaction, and IMO, that's not a terribly important one to support. // // In case we do want to support nested operations, I think the plan would be basically: // - For every request that awaits a reply (and only those), send a reply token // - In RKGraphicsDataStreamReadGuard () wait for that specific token. If another token arrives, instead of the expected one, // put it on a stack, and continue waiting. // - When waiting for a reply, also check the stack. // What about the mutex? // Well, essentially, during rkd_waiting_for_reply, nothing should attempt to obtain a lock. The transmitter thread can simply pause // during that time. rkd_suppress_on_exit++; Rf_error ("Nested graphics operations are not supported by this device (did you try to resize the device during locator()?)"); } RKGraphicsDeviceBackendTransmitter::mutex.lock (); } ~RKGraphicsDataStreamWriteGuard () { RKGraphicsDeviceBackendTransmitter::streamer.writeOutBuffer (); RKGraphicsDeviceBackendTransmitter::mutex.unlock (); } }; #include #include // This ought to be optimized away by the compiler: #define SAFE_LINE_END(lend) (quint8) (lend == GE_ROUND_CAP ? RoundLineCap : (lend == GE_BUTT_CAP ? ButtLineCap : SquareLineCap)) // This ought to be optimized away by the compiler: #define SAFE_LINE_JOIN(ljoin) (quint8) (ljoin == GE_ROUND_JOIN ? RoundJoin : (ljoin == GE_BEVEL_JOIN ? BevelJoin : MitreJoin)) // I'd love to convert to QColor, directly, but that's in QtGui, not QtCore. QRgb used different byte ordering #define WRITE_COLOR_BYTES(col) \ if (col == NA_INTEGER) RKD_OUT_STREAM << (quint8) 0xFF << (quint8) 0xFF << (quint8) 0xFF << (quint8) 0x00; \ else RKD_OUT_STREAM << (quint8) R_RED (col) << (quint8) R_GREEN (col) << (quint8) R_BLUE (col) << (quint8) R_ALPHA (col) #define WRITE_HEADER_NUM(x,devnum) \ RKD_OUT_STREAM << (qint8) x << (quint8) devnum #define WRITE_HEADER(x,dev) \ WRITE_HEADER_NUM (x,static_cast (dev->deviceSpecific)->devnum) #define WRITE_COL() \ WRITE_COLOR_BYTES (gc->col) #define WRITE_PEN() \ WRITE_COL(); RKD_OUT_STREAM << (double) gc->lwd << (qint32) gc->lty // actually lmitre is only needed if linejoin is GE_MITRE_JOIN, so we could optimize a bit #define WRITE_LINE_ENDS() \ RKD_OUT_STREAM << SAFE_LINE_END (gc->lend) << SAFE_LINE_JOIN (gc->ljoin) << gc->lmitre #define WRITE_FILL() \ WRITE_COLOR_BYTES (gc->fill) #define WRITE_FONT(dev) \ RKD_OUT_STREAM << gc->cex << gc->ps << gc->lineheight << (quint8) gc->fontface << (gc->fontfamily[0] ? QString (gc->fontfamily) : (static_cast (dev->deviceSpecific)->getFontFamily (gc->fontface == 5))) static void RKD_QueryResolution (int *dpix, int *dpiy) { { RKGraphicsDataStreamWriteGuard wguard; WRITE_HEADER_NUM (RKDQueryResolution, 0); } { RKGraphicsDataStreamReadGuard rguard; qint32 _dpix, _dpiy; RKD_IN_STREAM >> _dpix >> _dpiy; *dpix = _dpix; *dpiy = _dpiy; } } static void RKD_Create (double width, double height, pDevDesc dev, const char *title, bool antialias) { RKGraphicsDataStreamWriteGuard guard; WRITE_HEADER (RKDCreate, dev); RKD_OUT_STREAM << width << height << QString::fromUtf8 (title) << antialias; } static void RKD_Size (double *left, double *right, double *top, double *bottom, pDevDesc dev) { // NOTE: This does *not* query the frontend for the current size. This is only done on request *left = dev->left; *top = dev->top; *right = dev->right; *bottom = dev->bottom; } static void RKD_SetSize (pDevDesc dev) { RKGraphicsDataStreamWriteGuard wguard; WRITE_HEADER (RKDSetSize, dev); RKD_OUT_STREAM << QSize (qAbs (dev->right - dev->left) + .2, qAbs (dev->bottom - dev->top) + .2); } SEXP RKD_AdjustSize (SEXP _devnum) { int devnum = Rf_asInteger (_devnum); pGEDevDesc gdev = GEgetDevice (devnum); if (!gdev) Rf_error ("No such device %d", devnum); pDevDesc dev = gdev->dev; { RKGraphicsDataStreamWriteGuard wguard; WRITE_HEADER (RKDGetSize, dev); } QSizeF size; { RKGraphicsDataStreamReadGuard rguard; RKD_IN_STREAM >> size; } if (size.isNull ()) Rf_error ("Could not determine current size of device %d. Not an RK device?", devnum); dev->left = dev->top = 0; dev->right = size.width (); dev->bottom = size.height (); RKD_SetSize (dev); // This adjusts the rendering area in the frontend GEplayDisplayList (gdev); return R_NilValue; } static void RKD_Circle (double x, double y, double r, R_GE_gcontext *gc, pDevDesc dev) { RKGraphicsDataStreamWriteGuard guard; WRITE_HEADER (RKDCircle, dev); RKD_OUT_STREAM << x << y << r; WRITE_PEN (); WRITE_FILL (); } static void RKD_Line (double x1, double y1, double x2, double y2, R_GE_gcontext *gc, pDevDesc dev) { RKGraphicsDataStreamWriteGuard guard; WRITE_HEADER (RKDLine, dev); RKD_OUT_STREAM << x1 << y1 << x2 << y2; WRITE_PEN (); WRITE_LINE_ENDS (); } static void RKD_Polygon (int n, double *x, double *y, R_GE_gcontext *gc, pDevDesc dev) { RKGraphicsDataStreamWriteGuard guard; WRITE_HEADER (RKDPolygon, dev); quint32 _n = qMin (n, 1 << 25); // skip stuff exceeding reasonable limits to keep protocol simple RKD_OUT_STREAM << _n; for (quint32 i = 0; i < _n; ++i) { RKD_OUT_STREAM << x[i] << y[i]; } WRITE_PEN (); WRITE_LINE_ENDS (); WRITE_FILL (); } static void RKD_Polyline (int n, double *x, double *y, R_GE_gcontext *gc, pDevDesc dev) { RKGraphicsDataStreamWriteGuard guard; WRITE_HEADER (RKDPolyline, dev); quint32 _n = qMin (n, 1 << 25); // skip stuff exceeding reasonable limits to keep protocol simple RKD_OUT_STREAM << _n; for (quint32 i = 0; i < _n; ++i) { RKD_OUT_STREAM << x[i] << y[i]; } WRITE_PEN (); WRITE_LINE_ENDS (); } static void RKD_Path (double *x, double *y, int npoly, int *nper, Rboolean winding, R_GE_gcontext *gc, pDevDesc dev) { RKGraphicsDataStreamWriteGuard guard; WRITE_HEADER (RKDPath, dev); quint32 total_points = 0; quint32 _n = qMin (npoly, 1 << 24); // skip stuff exceeding reasonable limits to keep protocol simple RKD_OUT_STREAM << _n; for (quint32 i = 0; i < _n; ++i) { quint32 np = nper[i]; // Actually, a quint8 would probably do? RKD_OUT_STREAM << np; for (quint32 j = 0; j < np; ++j) { RKD_OUT_STREAM << x[total_points] << y[total_points]; total_points++; } } RKD_OUT_STREAM << (bool) winding; WRITE_PEN (); WRITE_LINE_ENDS (); WRITE_FILL (); } static void RKD_Rect (double x0, double y0, double x1, double y1, R_GE_gcontext *gc, pDevDesc dev) { RKGraphicsDataStreamWriteGuard guard; WRITE_HEADER (RKDRect, dev); RKD_OUT_STREAM << QRectF (x0, y0, x1-x0, y1-y0); WRITE_PEN (); WRITE_LINE_ENDS (); WRITE_FILL (); } static void RKD_TextUTF8 (double x, double y, const char *str, double rot, double hadj, R_GE_gcontext *gc, pDevDesc dev) { RKGraphicsDataStreamWriteGuard guard; WRITE_HEADER (RKDTextUTF8, dev); RKD_OUT_STREAM << x << y << QString::fromUtf8 (str) << rot << hadj; // NOTE: yes, even Symbols are sent as UTF-8, here. WRITE_COL (); WRITE_FONT (dev); } static double RKD_StrWidthUTF8 (const char *str, R_GE_gcontext *gc, pDevDesc dev) { { RKGraphicsDataStreamWriteGuard guard; WRITE_HEADER (RKDStrWidthUTF8, dev); RKD_OUT_STREAM << QString::fromUtf8 (str); // NOTE: yes, even Symbols are sent as UTF-8, here. WRITE_FONT (dev); } double ret; { RKGraphicsDataStreamReadGuard guard; RKD_IN_STREAM >> ret; } return ret; } static void RKD_NewPage (R_GE_gcontext *gc, pDevDesc dev) { RKGraphicsDataStreamWriteGuard guard; WRITE_HEADER (RKDNewPage, dev); WRITE_FILL (); } static void RKD_MetricInfo (int c, R_GE_gcontext *gc, double* ascent, double* descent, double* width, pDevDesc dev) { { RKGraphicsDataStreamWriteGuard wguard; WRITE_HEADER (RKDMetricInfo, dev); QChar unichar; if (c < 0) unichar = QChar (-c); else { // correct?! Do we get utf8, here? int inbuf[2]; inbuf[0] = c; inbuf[1] = 0; QString dummy = QString::fromUtf8 ((char*) inbuf); if (!dummy.isEmpty ()) unichar = dummy.at (0); } RKD_OUT_STREAM << unichar; WRITE_FONT (dev); } { RKGraphicsDataStreamReadGuard rguard; RKD_IN_STREAM >> *ascent >> *descent >> *width; } } static void RKD_Close (pDevDesc dev) { RKGraphicsDataStreamWriteGuard guard; WRITE_HEADER (RKDClose, dev); delete static_cast (dev->deviceSpecific); } static void RKD_Activate (pDevDesc dev) { RKGraphicsDataStreamWriteGuard guard; WRITE_HEADER (RKDActivate, dev); } static void RKD_Deactivate (pDevDesc dev) { RKGraphicsDataStreamWriteGuard guard; WRITE_HEADER (RKDDeActivate, dev); } static void RKD_Clip (double left, double right, double top, double bottom, pDevDesc dev) { dev->clipLeft = left; dev->clipRight = right; dev->clipTop = top; dev->clipBottom = bottom; RKGraphicsDataStreamWriteGuard guard; WRITE_HEADER (RKDClip, dev); RKD_OUT_STREAM << QRectF (left, top, right - left, bottom - top); } static void RKD_Mode (int mode, pDevDesc dev) { Q_UNUSED (mode); Q_UNUSED (dev); /* Left empty for now. 1 is start signal, 0 is stop signal. Might be useful for flushing, though. RKGraphicsDataStreamWriteGuard guard; RKD_OUT_STREAM << WRITE_HEADER (RKDMode, dev); connectoin << (qint8) mode; */ } static void RKD_Raster (unsigned int *raster, int w, int h, double x, double y, double width, double height, double rot, Rboolean interpolate, const pGEcontext gc, pDevDesc dev) { Q_UNUSED (gc); RKGraphicsDataStreamWriteGuard wguard; WRITE_HEADER (RKDRaster, dev); int *_raster = reinterpret_cast (raster); // shut up warning in WRITE_COLOR_BYTES. It's just four separate bytes, anyway quint32 _w = qMin (w, 1 << 15); // skip stuff exceeding reasonable limits to keep protocol simple RKD_OUT_STREAM << _w; quint32 _h = qMin (h, 1 << 15); RKD_OUT_STREAM << _h; for (quint32 col = 0; col < _h; ++col) { for (quint32 row = 0; row < _w; ++row) { WRITE_COLOR_BYTES (_raster[(col*_w) + row]); } } RKD_OUT_STREAM << QRectF (x, y, width, height) << rot << (bool) interpolate; } static SEXP RKD_Capture (pDevDesc dev) { { RKGraphicsDataStreamWriteGuard wguard; WRITE_HEADER (RKDCapture, dev); } quint32 w, h; quint32 size; int *buffer; { RKGraphicsDataStreamReadGuard rguard; quint8 r, g, b, a; RKD_IN_STREAM >> w >> h; size = w*h; buffer = new int[size];// Although unlikely, allocVector below could fail. We don't want to be left with a locked mutex (from the rguard) in this case. (Being left with a dead pointer looks benign in comparison; Note that the vector may easily be too large for allocation on the stack) quint32 i = 0; for (quint32 col = 0; col < h; ++col) { for (quint32 row = 0; row < w; ++row) { RKD_IN_STREAM >> r >> g >> b >> a; buffer[i++] = R_RGBA (r, g, b, a); } } } SEXP ret, dim; PROTECT (ret = Rf_allocVector (INTSXP, size)); int* ret_vals = INTEGER (ret); for (quint32 i = 0; i < size; ++i) { ret_vals[i] = buffer[i]; } delete (buffer); // Documentation does not mention it, but cap expects dim information to be returned PROTECT(dim = Rf_allocVector (INTSXP, 2)); INTEGER (dim)[0] = w; INTEGER (dim)[1] = h; Rf_setAttrib (ret, R_DimSymbol, dim); UNPROTECT (2); return ret; } static Rboolean RKD_Locator (double *x, double *y, pDevDesc dev) { { RKGraphicsDataStreamWriteGuard wguard; WRITE_HEADER (RKDLocator, dev); } { RKGraphicsDataStreamReadGuard rguard; bool ok; RKD_IN_STREAM >> ok >> *x >> *y; if (ok) return (Rboolean) TRUE; return (Rboolean) FALSE; } } static Rboolean RKD_NewFrameConfirm (pDevDesc dev) { { RKGraphicsDataStreamWriteGuard wguard; WRITE_HEADER (RKDNewPageConfirm, dev); } bool ok; { RKGraphicsDataStreamReadGuard rguard; RKD_IN_STREAM >> ok; } if (!ok) Rf_error ("Aborted by user"); return (Rboolean) TRUE; // Return value FALSE: Let R ask, instead } #if R_VERSION >= R_Version (2, 12, 0) void RKD_EventHelper (pDevDesc dev, int code) { { RKGraphicsDataStreamWriteGuard wguard; if (code == 1) { QString prompt; if (Rf_isEnvironment (dev->eventEnv)) { SEXP sprompt = Rf_findVar (Rf_install ("prompt"), dev->eventEnv); if (Rf_length (sprompt) == 1) prompt = QString::fromUtf8 (CHAR (Rf_asChar (sprompt))); } WRITE_HEADER (RKDStartGettingEvents, dev); RKD_OUT_STREAM << prompt; return; } else if (code == 0) { WRITE_HEADER (RKDStopGettingEvents, dev); return; } else { WRITE_HEADER (RKDFetchNextEvent, dev); } } RK_ASSERT (code == 2); // NOTE: The event handler functions doKeybd() and doMouseEvent() could conceivably produce errors -> longjump // Thus we need to make sure the read-guard has gone out of scope before that. Thus, we take the somewhat clunky // route of first reading the full reply, then processing it. qint8 event_code; QString text; qint32 keycode, modifiers; double x, y; qint8 buttons; { RKGraphicsDataStreamReadGuard rguard; RKD_IN_STREAM >> event_code; if ((event_code == RKDNothing) || (event_code == RKDFrontendCancel)) { // nothing } else if (event_code == RKDKeyPress) { RKD_IN_STREAM >> text >> keycode >> modifiers; } else { // a mouse event RKD_IN_STREAM >> buttons >> x >> y; } } if (event_code == RKDFrontendCancel) { Rf_error ("Interrupted by user"); return; // not reached } if (event_code == RKDNothing) return; else if (event_code == RKDKeyPress) { if (modifiers - (modifiers & Qt::ShiftModifier)) { // any other modifier than Shift, alone. NOTE: devX11.c and devWindows.c handle Ctrl, only as of R 3.0.0 QString mod_text; if (modifiers & Qt::ControlModifier) mod_text.append ("ctrl-"); if (modifiers & Qt::AltModifier) mod_text.append ("alt-"); if (modifiers & Qt::MetaModifier) mod_text.append ("meta-"); if (text.isEmpty () && (modifiers & Qt::ShiftModifier)) mod_text.append ("shift-"); // don't apply shift when there is text (where it has already been handled) text = mod_text + text.toUpper (); } R_KeyName r_key_name = knUNKNOWN; if (keycode == Qt::Key_Left) r_key_name = knLEFT; else if (keycode == Qt::Key_Right) r_key_name = knRIGHT; else if (keycode == Qt::Key_Up) r_key_name = knUP; else if (keycode == Qt::Key_Down) r_key_name = knDOWN; else if ((keycode >= Qt::Key_F1) && (keycode <= Qt::Key_F12)) r_key_name = (R_KeyName) (knF1 + (keycode - Qt::Key_F1)); else if (keycode == Qt::Key_PageUp) r_key_name = knPGUP; else if (keycode == Qt::Key_PageDown) r_key_name = knPGDN; else if (keycode == Qt::Key_End) r_key_name = knEND; else if (keycode == Qt::Key_Home) r_key_name = knHOME; else if (keycode == Qt::Key_Insert) r_key_name = knINS; else if (keycode == Qt::Key_Delete) r_key_name = knDEL; Rf_doKeybd (dev, r_key_name, text.toUtf8 ()); } else { // all others are mouse events Rf_doMouseEvent (dev, event_code == RKDMouseDown ? meMouseDown : (event_code == RKDMouseUp ? meMouseUp : meMouseMove), buttons, x, y); } } void RKD_onExit (pDevDesc dev) { if (rkd_suppress_on_exit > 0) { --rkd_suppress_on_exit; return; } if (dev->gettingEvent) { RKGraphicsDataStreamWriteGuard wguard; WRITE_HEADER (RKDStopGettingEvents, dev); } dev->gettingEvent = (Rboolean) false; } #endif rkward-0.6.4/rkward/rbackend/rkwarddevice/rkgraphicsdevice_protocol_shared.h0000664000175000017500000001043612633754364027060 0ustar thomasthomas/*************************************************************************** rkgraphicsdevice_protocol_shared - description ------------------- begin : Mon Mar 18 20:06:08 CET 2013 copyright : (C) 2013 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKGRAPHICSDEVICE_PROTOCOL_SHARED_H #define RKGRAPHICSDEVICE_PROTOCOL_SHARED_H /** @page RKGraphicsDeviceProtocol * * The key feature of the RKWard Graphics Device is that it serializes all drawing operations, so they * can be sent to a separate process (the frontend) or even computer (well, not yet). Some notes on the protocol: * * This is not the same protocol as used for other communication between frontend and backend, and not even * the same connection. The key idea behind this protocol here, is that it should have very low overhead, * even when sending many @em small requests. * * All communication is initiated from the backend. The backend sends a request, starting with the size of the * request in bytes (quint32), then an opcode (quint8), then the device number (quint8), then all applicable parameters. * Most requests are asynchronous, but a few await a reply from the frontend. * * At any time, there can only be one request waiting for a reply, and the request waiting for a reply is always the most * recent one. This makes the protocol very simple. * * If the frontend has spontaneous need for communication, it will have to use some separate channel. * * How do we handle cancellation of interactive ops (e.g. locator()) from the backend? If an interrupt is pending * in the backend, _while waiting for the reply_, we push an RKD_Cancel request down the line. This tells the frontend to * send a reply to the last request ASAP (if the frontend has already sent the reply, it will ignore the RKD_Cancel). From * there, we simply process the reply as usual, and leave it to R to actually do the interrupt. If the frontend takes more than * fives seconds to respond at this point, the connection will be killed. * */ /** This enum simply repeats R's line end definitions. It is used to ensure compatibility, without the need to include * any R headers in the frontend. */ enum RKLineEndStyles { RoundLineCap = 1, ButtLineCap = 2, SquareLineCap = 3 }; /** This enum simply repeats R's line join definitions. It is used to ensure compatibility, without the need to include * any R headers in the frontend. */ enum RKLineJoinStyles { RoundJoin = 1, MitreJoin = 2, BevelJoin = 3 }; enum RKDOpcodes { // Asynchronous operations RKDCreate, // 0 RKDCircle, RKDLine, RKDPolygon, RKDPolyline, RKDPath, // 5 RKDRect, RKDTextUTF8, RKDNewPage, RKDClose, RKDActivate, // 10 RKDDeActivate, RKDClip, RKDMode, RKDRaster, RKDSetSize, // 15 RKDStartGettingEvents, RKDStopGettingEvents, // Synchronous operations RKDFetchNextEvent, RKDStrWidthUTF8, RKDMetricInfo, // 20 RKDLocator, RKDNewPageConfirm, RKDCapture, RKDQueryResolution, RKDGetSize, // 25 // Protocol operations RKDCancel }; enum RKDEventCodes { RKDMouseUp = 0, RKDMouseDown = 1, RKDMouseMove = 2, RKDKeyPress = 3, RKDNothing = 4, RKDFrontendCancel = 5, // Mouse buttons, or-able, identical to the corresponding R defines. Note: x1 and x2 buttons are not handled by R RKDMouseLeftButton = 1, RKDMouseMiddleButton = 2, RKDMouseRightButton = 4 // RKDMouseX1Button = 8, // RKDMouseX2Button = 16 }; #include typedef quint32 RKGraphicsDeviceTransmittionLengthType; #endif rkward-0.6.4/rkward/rbackend/rkwarddevice/rkgraphicsdevice.h0000664000175000017500000001151312633754364023606 0ustar thomasthomas/*************************************************************************** rkgraphicsdevice_backendtransmitter - description ------------------- begin : Mon Mar 18 20:06:08 CET 2013 copyright : (C) 2013-2014 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKGRAPHICSDEVICE_H #define RKGRAPHICSDEVICE_H #include #include #include #include #include #ifndef Q_WS_WIN // On Mac, drawing on a pixmap does not work correctly. Probably can only be done inside paint // events. (MacOSX 10.6.8, Qt 4.8.4). // On X11, similar problems seem to occur on some, but not all systems. Only on old versions? // See http://sourceforge.net/p/rkward/bugs/129/ . // Version not working with QPixmap: qt 4.7.0~beta2, libx11 1.3.2, xserver-xorg 7.5 // Version working with QPixmap: qt 4.8.4, libx11 1.5.0, xserver-xorg 7.7 // // Fortunately, a QImage based buffer does not seem to be _that_ much slower // (around 5-10% on X11, on plot (rnorm (100000))) # define USE_QIMAGE_BUFFER #endif #ifdef USE_QIMAGE_BUFFER # include #else # include #endif class KDialog; /** This is the class that actually does all the drawing for the RKGraphicsDevice */ class RKGraphicsDevice : public QObject { Q_OBJECT protected: RKGraphicsDevice (double width, double height, const QString &title, bool antialias); ~RKGraphicsDevice (); public: static RKGraphicsDevice* newDevice (int devnum, double width, double height, const QString &title, bool antialias); static void closeDevice (int devnum); static QHash devices; void circle (double x, double y, double r, const QPen& pen, const QBrush& brush); void line (double x1, double y1, double x2, double y2, const QPen& pen); void rect (const QRectF& rec, const QPen& pen, const QBrush& brush); QSizeF strSize (const QString &text, const QFont& font); void text (double x, double y, const QString &text, double rot, double hadj, const QColor& col, const QFont& font); void metricInfo (const QChar& c, const QFont& font, double *ascent, double *descent, double *width); void setClip (const QRectF& new_clip); void polygon (const QPolygonF& pol, const QPen& pen, const QBrush &brush); void polyline (const QPolygonF& pol, const QPen& pen); void polypath (const QVector& polygons, bool winding, const QPen& pen, const QBrush& brush); void clear (const QColor& col=QColor()); void image (const QImage &image, const QRectF &target_rect, double rot, bool interpolate); QImage capture () const; void setActive (bool active); void triggerUpdate (); void locator (); void confirmNewPage (); // graphics event handling /** Simple struct to keep info about both mouse and keyboard events, so we can store them in a list, until R fetches them. */ struct StoredEvent { StoredEvent () : event_code (0), buttons (0), modifiers (0), keycode (0), x (0), y (0) {}; qint8 event_code; qint8 buttons; qint32 modifiers; qint32 keycode; QString keytext; double x, y; }; void startGettingEvents (const QString &prompt); StoredEvent fetchNextEvent (); void stopGettingEvents (); QWidget* viewPort () const { return view; }; QSizeF currentSize () const { return view->size (); } void setAreaSize (const QSize &size); public slots: void stopInteraction (); signals: void goingInteractive (bool interactive, const QString &prompt); void activeChanged (bool); void locatorDone (bool ok, double x, double y); void newPageConfirmDone (bool accepted); void captionChanged (const QString &caption); private slots: void updateNow (); void newPageDialogDone (int result); void viewKilled (); private: void goInteractive (const QString &prompt); bool eventFilter (QObject *watched, QEvent *event); void checkSize (); QTimer updatetimer; #ifdef USE_QIMAGE_BUFFER QImage area; #else QPixmap area; #endif QPainter painter; QLabel *view; QString base_title; KDialog *dialog; int interaction_opcode; /**< Current interactive operation (from RKDOpcodes enum), or -1 is there is no current interactive operation */ QList stored_events; }; #endif rkward-0.6.4/rkward/rbackend/rkwarddevice/rkgraphicsdevice_frontendtransmitter.h0000664000175000017500000000411412633754364030001 0ustar thomasthomas/*************************************************************************** rkgraphicsdevice_frontendtransmitter - description ------------------- begin : Mon Mar 18 20:06:08 CET 2013 copyright : (C) 2013 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKGRAPHICSDEVICE_FRONTENDTRANSMITTER_H #define RKGRAPHICSDEVICE_FRONTENDTRANSMITTER_H #include "rkgraphicsdevice_protocol_shared.h" #include "../rkasyncdatastreamhelper.h" class QIODevice; class QLocalServer; /** Handles the frontend side of RKWard Graphics Device transmissions. Since the * frontend has a running Qt event loop, We can use simple signals and slots, here. */ class RKGraphicsDeviceFrontendTransmitter : public QObject { Q_OBJECT public: RKGraphicsDeviceFrontendTransmitter (); ~RKGraphicsDeviceFrontendTransmitter (); QString serverName () const { return server_name; }; static double lwdscale; public slots: void newData (); void newConnection (); void locatorDone (bool ok, double x, double y); void newPageConfirmDone (bool accepted); signals: void stopInteraction (); private: void setupServer (); void sendDummyReply (quint8 opcode); QString server_name; QIODevice *connection; QLocalServer *local_server; RKAsyncDataStreamHelper streamer; }; #endif rkward-0.6.4/rkward/rbackend/rkwarddevice/CMakeLists.txt0000644000175000017500000000123512455741221022644 0ustar thomasthomasINCLUDE_DIRECTORIES( ${R_INCLUDEDIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${KDE4_INCLUDE_DIR} ${QT_INCLUDES} ) SET ( rkgraphicsdevice_backend_SRCS rkgraphicsdevice_backendtransmitter.cpp rkgraphicsdevice_setup.cpp # rkgraphicsdevice_stubs.cpp is included by rkgraphicsdevice_setup.cpp ) SET ( rkgraphicsdevice_frontend_SRCS rkgraphicsdevice_frontendtransmitter.cpp rkgraphicsdevice.cpp ) QT4_AUTOMOC(${rkgraphicsdevice_frontend_SRCS}) ADD_LIBRARY(rkgraphicsdevice.frontend STATIC ${rkgraphicsdevice_frontend_SRCS}) QT4_AUTOMOC(${rkgraphicsdevice_backend_SRCS}) ADD_LIBRARY(rkgraphicsdevice.backend STATIC ${rkgraphicsdevice_backend_SRCS}) rkward-0.6.4/rkward/rbackend/rkwarddevice/rkgraphicsdevice_frontendtransmitter.cpp0000664000175000017500000003340212633754364030336 0ustar thomasthomas/*************************************************************************** rkgraphicsdevice_frontendtransmitter - description ------------------- begin : Mon Mar 18 20:06:08 CET 2013 copyright : (C) 2013-2014 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkgraphicsdevice_frontendtransmitter.h" #include #include #include #include #include #include // for screen resolution #include #include #include "../rkfrontendtransmitter.h" #include "../../windows/rkworkplace.h" #include "rkgraphicsdevice.h" #include "../../version.h" #include "../../debug.h" double RKGraphicsDeviceFrontendTransmitter::lwdscale = 72/96; RKGraphicsDeviceFrontendTransmitter::RKGraphicsDeviceFrontendTransmitter () : QObject () { RK_TRACE (GRAPHICS_DEVICE); connection = 0; local_server = 0; setupServer (); } RKGraphicsDeviceFrontendTransmitter::~RKGraphicsDeviceFrontendTransmitter () { RK_TRACE (GRAPHICS_DEVICE); if (connection) connection->close (); } void RKGraphicsDeviceFrontendTransmitter::setupServer () { RK_TRACE (GRAPHICS_DEVICE); RK_ASSERT (!local_server); local_server = new QLocalServer (); RK_ASSERT (local_server->listen ("rkd" + KRandom::randomString (8))); connect (local_server, SIGNAL (newConnection()), this, SLOT (newConnection())); server_name = local_server->fullServerName (); } void RKGraphicsDeviceFrontendTransmitter::newConnection () { RK_TRACE (GRAPHICS_DEVICE); RK_ASSERT (!connection); QLocalSocket *con = local_server->nextPendingConnection (); local_server->close (); // handshake QString token = RKFrontendTransmitter::instance ()->connectionToken (); if (!con->canReadLine ()) con->waitForReadyRead (1000); QString token_c = QString::fromLocal8Bit (con->readLine ()); token_c.chop (1); if (token_c != token) { KMessageBox::detailedError (0, QString ("

%1

").arg (i18n ("There has been an error while trying to connect the on-screen graphics backend. This means, on-screen graphics using the RKWard device will not work in this session.")), i18n ("Expected connection token %1, but read connection token %2").arg (token).arg (token_c), i18n ("Error while connection graphics backend")); con->close (); return; } connection = con; streamer.setIODevice (con); connect (connection, SIGNAL (readyRead()), this, SLOT (newData())); newData (); // might already be available } static QRgb readRgb (QDataStream &instream) { quint8 r, g, b, a; instream >> r >> g >> b >> a; return qRgba (r, g, b, a); } static QColor readColor (QDataStream &instream) { quint8 r, g, b, a; instream >> r >> g >> b >> a; if (a == 0x00) return QColor (); return QColor (r, g, b, a); } static QPen readSimplePen (QDataStream &instream) { QColor col = readColor (instream); double lwd; qint32 lty; instream >> lwd >> lty; if (!col.isValid () || (lty == -1L)) return QPen (Qt::NoPen); lwd = qMax (double(qreal(1.0)), lwd); // minimum 1 px as in X11 device QPen ret; if (lty != 0) { // solid QVector dashes; quint32 nlty = lty; for (int i = 0; i < 8; ++i) { if (!nlty) break; quint8 j = nlty & 0xF; if (j < 1) j = 1; dashes.append ((int) (j * lwd * RKGraphicsDeviceFrontendTransmitter::lwdscale + .5)); nlty >>= 4; } if (!dashes.isEmpty ()) ret.setDashPattern (dashes); } ret.setWidth ((int) (lwd * RKGraphicsDeviceFrontendTransmitter::lwdscale + .5)); ret.setColor (col); return ret; } static QPen readPen (QDataStream &instream) { QPen ret = readSimplePen (instream); quint8 lends, ljoin; double lmitre; instream >> lends >> ljoin >> lmitre; ret.setCapStyle (lends == RoundLineCap ? Qt::RoundCap : (lends == ButtLineCap ? Qt::FlatCap : Qt::SquareCap)); ret.setJoinStyle (ljoin == RoundJoin ? Qt::RoundJoin : (ljoin == BevelJoin ? Qt::BevelJoin : Qt::MiterJoin)); ret.setMiterLimit (lmitre); return ret; } static QBrush readBrush (QDataStream &instream) { QColor col = readColor (instream); if (!col.isValid ()) return QBrush (); return QBrush (col); } static QFont readFont (QDataStream &instream) { double cex, ps, lineheight; quint8 fontface; QString fontfamily; instream >> cex >> ps >> lineheight >> fontface >> fontfamily; #ifdef __GNUC__ # warning TODO deal with line-height #endif QFont ret (fontfamily); if (fontface == 2 || fontface == 4) ret.setWeight (QFont::Bold); if (fontface == 3 || fontface == 4) ret.setItalic (true); ret.setPointSizeF (cex*ps); return ret; } static QVector readPoints (QDataStream &instream) { quint32 n; instream >> n; QVector points; points.reserve (n); for (quint32 i = 0; i < n; ++i) { double x, y; instream >> x >> y; points.append (QPointF (x, y)); } return points; } void RKGraphicsDeviceFrontendTransmitter::newData () { RK_TRACE (GRAPHICS_DEVICE); while (connection->bytesAvailable ()) { if (!streamer.readInBuffer ()) return; // wait for more data to come in quint8 opcode, devnum; streamer.instream >> opcode >> devnum; RK_DEBUG (GRAPHICS_DEVICE, DL_DEBUG, "Received transmission of type %d, devnum %d, size %d", opcode, devnum+1, streamer.inSize ()); RKGraphicsDevice *device = 0; if (devnum && opcode == RKDCreate) { double width, height; QString title; bool antialias; streamer.instream >> width >> height >> title >> antialias; device = RKGraphicsDevice::newDevice (devnum, width, height, title, antialias); RKWorkplace::mainWorkplace ()->newRKWardGraphisWindow (device, devnum+1); connect (device, SIGNAL (locatorDone(bool,double,double)), this, SLOT (locatorDone(bool,double,double))); connect (device, SIGNAL (newPageConfirmDone(bool)), this, SLOT (newPageConfirmDone(bool))); connect (this, SIGNAL (stopInteraction()), device, SLOT (stopInteraction())); continue; } else { if (devnum) device = RKGraphicsDevice::devices.value (devnum); if (!device) { if (opcode == RKDCancel) { RK_DEBUG (GRAPHICS_DEVICE, DL_WARNING, "Graphics operation cancelled"); emit (stopInteraction()); } else if (opcode == RKDQueryResolution) { QDesktopWidget *desktop = QApplication::desktop (); streamer.outstream << (qint32) desktop->physicalDpiX () << (qint32) desktop->physicalDpiY (); RK_DEBUG (GRAPHICS_DEVICE, DL_INFO, "DPI for device %d: %d by %d", devnum+1, desktop->physicalDpiX (), desktop->physicalDpiY ()); streamer.writeOutBuffer (); // Actually, this is only needed once, but where to put it... RKGraphicsDeviceFrontendTransmitter::lwdscale = desktop->physicalDpiX () / 96; // taken from devX11.c } else { if (devnum) RK_DEBUG (GRAPHICS_DEVICE, DL_ERROR, "Received transmission of type %d for unknown device number %d. Skippping.", opcode, devnum+1); sendDummyReply (opcode); } continue; } } /* WARNING: No fixed evaluation order of function arguments. Do not use several readXYZ() calls in the same function call! Use dummy variables, instead. */ if (opcode == RKDCircle) { double x, y, r; streamer.instream >> x >> y >> r; QPen pen = readSimplePen (streamer.instream); device->circle (x, y, r, pen, readBrush (streamer.instream)); } else if (opcode == RKDLine) { double x1, y1, x2, y2; streamer.instream >> x1 >> y1 >> x2 >> y2; device->line (x1, y1, x2, y2, readPen (streamer.instream)); } else if (opcode == RKDPolygon) { QPolygonF pol (readPoints (streamer.instream)); QPen pen = readPen (streamer.instream); device->polygon (pol, pen, readBrush (streamer.instream)); } else if (opcode == RKDPolyline) { QPolygonF pol (readPoints (streamer.instream)); device->polyline (pol, readPen (streamer.instream)); } else if (opcode == RKDPath) { quint32 npol; streamer.instream >> npol; QVector polygons; polygons.reserve (npol); for (quint32 i = 0; i < npol; ++i) { polygons.append (readPoints (streamer.instream)); } bool winding; streamer.instream >> winding; QPen pen = readPen (streamer.instream); device->polypath (polygons, winding, pen, readBrush (streamer.instream)); } else if (opcode == RKDRect) { QRectF rect; streamer.instream >> rect; QPen pen = readPen (streamer.instream); device->rect (rect, pen, readBrush (streamer.instream)); } else if (opcode == RKDStrWidthUTF8) { QString out; streamer.instream >> out; double w = device->strSize (out, readFont (streamer.instream)).width (); streamer.outstream << w; streamer.writeOutBuffer (); } else if (opcode == RKDMetricInfo) { QChar c; double ascent, descent, width; streamer.instream >> c; device->metricInfo (c, readFont (streamer.instream), &ascent, &descent, &width); streamer.outstream << ascent << descent << width; streamer.writeOutBuffer (); } else if (opcode == RKDTextUTF8) { double x, y, rot, hadj; QString out; streamer.instream >> x >> y >> out >> rot >> hadj; QColor col = readColor (streamer.instream); device->text (x, y, out, rot, hadj, col, readFont (streamer.instream)); } else if (opcode == RKDNewPage) { device->clear (readColor (streamer.instream)); } else if (opcode == RKDClose) { RKGraphicsDevice::closeDevice (devnum); } else if (opcode == RKDActivate) { device->setActive (true); } else if (opcode == RKDDeActivate) { device->setActive (false); } else if (opcode == RKDClip) { QRectF clip; streamer.instream >> clip; device->setClip (clip); } else if (opcode == RKDMode) { quint8 m; streamer.instream >> m; if (m == 0) device->triggerUpdate (); } else if (opcode == RKDRaster) { quint32 w, h; streamer.instream >> w >> h; QImage image (w, h, QImage::Format_ARGB32); for (quint32 col = 0; col < h; ++col) { for (quint32 row = 0; row < w; ++row) { image.setPixel (row, col, readRgb (streamer.instream)); } } QRectF target; double rotation; bool interpolate; streamer.instream >> target >> rotation >> interpolate; device->image (image, target.normalized (), rotation, interpolate); } else if (opcode == RKDCapture) { QImage image = device->capture (); quint32 w = image.width (); quint32 h = image.height (); streamer.outstream << w << h; for (quint32 col = 0; col < h; ++col) { for (quint32 row = 0; row < w; ++row) { QRgb pixel = image.pixel (row, col); streamer.outstream << (quint8) qRed (pixel) << (quint8) qGreen (pixel) << (quint8) qBlue (pixel) << (quint8) qAlpha (pixel); } } streamer.writeOutBuffer (); } else if (opcode == RKDGetSize) { streamer.outstream << device->currentSize (); streamer.writeOutBuffer (); } else if (opcode == RKDSetSize) { QSize size; streamer.instream >> size; device->setAreaSize (size); } else if (opcode == RKDLocator) { device->locator (); } else if (opcode == RKDStartGettingEvents) { QString prompt; streamer.instream >> prompt; device->startGettingEvents (prompt); } else if (opcode == RKDStopGettingEvents) { device->stopGettingEvents (); } else if (opcode == RKDFetchNextEvent) { RKGraphicsDevice::StoredEvent ev = device->fetchNextEvent (); streamer.outstream << (qint8) ev.event_code; if (ev.event_code == RKDKeyPress) { streamer.outstream << ev.keytext << (qint32) ev.keycode << (qint32) ev.modifiers; } else if ((ev.event_code == RKDMouseDown) || (ev.event_code == RKDMouseUp) || (ev.event_code == RKDMouseMove)) { streamer.outstream << (qint8) ev.buttons << (double) ev.x << (double) ev.y; } else { RK_ASSERT ((ev.event_code == RKDNothing) || (ev.event_code == RKDFrontendCancel)); } streamer.writeOutBuffer (); } else if (opcode == RKDNewPageConfirm) { device->confirmNewPage (); } else { RK_DEBUG (GRAPHICS_DEVICE, DL_ERROR, "Unhandled operation of type %d for device number %d. Skippping.", opcode, devnum+1); } if (!streamer.instream.atEnd ()) { RK_DEBUG (GRAPHICS_DEVICE, DL_ERROR, "Failed to read all data for operation of type %d on device number %d.", opcode, devnum+1); } } } void RKGraphicsDeviceFrontendTransmitter::sendDummyReply (quint8 opcode) { RK_TRACE (GRAPHICS_DEVICE); if (opcode == RKDLocator) { bool ok = false; double x, y; x = y = 0; streamer.outstream << ok << x << y; } else if (opcode == RKDMetricInfo) { double ascent, descent, width; ascent = descent = width = 0.1; streamer.outstream << ascent << descent << width; } else if (opcode == RKDNewPageConfirm) { bool ok = true; streamer.outstream << ok; } else if (opcode == RKDStrWidthUTF8) { double width = 1; streamer.outstream << width; } else if (opcode == RKDGetSize) { streamer.outstream << QSizeF (); } else if (opcode == RKDFetchNextEvent) { streamer.outstream << (qint8) RKDNothing; } else { return; // nothing to write } streamer.writeOutBuffer (); } void RKGraphicsDeviceFrontendTransmitter::locatorDone (bool ok, double x, double y) { RK_TRACE (GRAPHICS_DEVICE); streamer.outstream << ok << x << y; streamer.writeOutBuffer (); } void RKGraphicsDeviceFrontendTransmitter::newPageConfirmDone (bool accepted) { RK_TRACE (GRAPHICS_DEVICE); streamer.outstream << accepted; streamer.writeOutBuffer (); } #include "rkgraphicsdevice_frontendtransmitter.moc" rkward-0.6.4/rkward/rbackend/rkwarddevice/rkgraphicsdevice_setup.cpp0000664000175000017500000001514112633754364025362 0ustar thomasthomas/*************************************************************************** rkgraphicsdevice_setup - description ------------------- begin : Mon Mar 18 20:06:08 CET 2013 copyright : (C) 2013-2014 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ /******************************* ACKNOWLEDGEMENT *************************** * * Much of the code in this file is based on, or even copied from package qtutils, version 0.1-3 * by Deepayan Sarkar. Package qtutils is available from http://qtinterfaces.r-forge.r-project.org * under GNU LPGL 2 or later. * ***************************************************************************/ #include "../rkrsupport.h" #ifdef TRUE # undef TRUE #endif #ifdef FALSE # undef FALSE #endif #define R_USE_PROTOTPYES 1 extern "C" { #include #include } // rcolor typedef added in R 3.0.0 #ifndef rcolor #define rcolor unsigned int #endif struct RKGraphicsDeviceDesc { bool init (pDevDesc dev, double pointsize, const QStringList &family, rcolor bg); int devnum; double width, height; int dpix, dpiy; QString getFontFamily (bool symbolfont) const { if (symbolfont) return default_symbol_family; return default_family; } QString default_family; QString default_symbol_family; pDevDesc rdevdesc; }; #include "rkgraphicsdevice_stubs.cpp" // No, I do not really understand what this is for. // Mostly trying to mimick the X11 device's behavior, here. #define RKGD_DPI 72.0 void RKStartGraphicsDevice (double width, double height, double pointsize, const QStringList &family, rcolor bg, const char* title, bool antialias) { if (width <= 0 || height <= 0) { Rf_error ("Invalid width or height: (%g, %g)", width, height); } RKGraphicsDeviceDesc *desc = new RKGraphicsDeviceDesc; desc->width = width; desc->height = height; R_GE_checkVersionOrDie (R_GE_version); R_CheckDeviceAvailable (); pDevDesc dev; BEGIN_SUSPEND_INTERRUPTS { /* Allocate and initialize the device driver data */ dev = (pDevDesc) calloc (1, sizeof(DevDesc)); // NOTE: The call to RKGraphicsDeviceBackendTransmitter::instance(), here is important beyond error checking. It might *create* the instance and connection, if this is the first use. if (!(dev && RKGraphicsDeviceBackendTransmitter::instance () && desc->init (dev, pointsize, family, bg))) { free (dev); delete (desc); desc = 0; } else { desc->devnum = 0; // graphics engine will send an Activate-event, before we were even // able to see our own devnum and call RKD_Create. Therefore, initialize // devnum to 0, so as not to confuse the frontend pGEDevDesc gdd = GEcreateDevDesc (dev); gdd->displayList = R_NilValue; GEaddDevice2 (gdd, "RKGraphicsDevice"); } } END_SUSPEND_INTERRUPTS; if (desc) { desc->devnum = curDevice (); RKD_Create (desc->width, desc->height, dev, title, antialias); } else { Rf_error("unable to start device"); } } SEXP RKStartGraphicsDevice (SEXP width, SEXP height, SEXP pointsize, SEXP family, SEXP bg, SEXP title, SEXP antialias) { RKStartGraphicsDevice (Rf_asReal (width), Rf_asReal (height), Rf_asReal (pointsize), RKRSupport::SEXPToStringList (family), R_GE_str2col (CHAR(Rf_asChar(bg))), CHAR(Rf_asChar(title)), Rf_asLogical (antialias)); return R_NilValue; } bool RKGraphicsDeviceDesc::init (pDevDesc dev, double pointsize, const QStringList &family, rcolor bg) { default_family = family.value (0, "Helvetica"); default_symbol_family = family.value (0, "Symbol"); RKD_QueryResolution (&dpix, &dpiy); if (dpix <= 1) dpix = RKGD_DPI; if (dpiy <= 1) dpiy = RKGD_DPI; width *= dpix; height *= dpiy; // Rprintf ("dpi: %d * %d, dims: %f * %f\n", dpix, dpiy, width, height); dev->deviceSpecific = (void *) this; // pointsize? /* * Initial graphical settings */ dev->startfont = 1; dev->startps = pointsize; dev->startcol = R_RGB(0, 0, 0); dev->startfill = bg; dev->startlty = LTY_SOLID; dev->startgamma = 1; /* * Device physical characteristics */ dev->left = dev->clipLeft = 0; dev->right = dev->clipRight = width; dev->bottom = dev->clipBottom = height; dev->top = dev->clipTop = 0; dev->cra[0] = 0.9 * pointsize * (dpix / RKGD_DPI); dev->cra[1] = 1.2 * pointsize * (dpiy / RKGD_DPI); dev->xCharOffset = 0.4900; dev->yCharOffset = 0.3333; dev->yLineBias = 0.2; dev->ipr[0] = 1.0 / dpix; dev->ipr[1] = 1.0 / dpiy; /* * Device capabilities */ dev->canClip = TRUE; dev->canHAdj = 2; dev->canChangeGamma = FALSE; dev->displayListOn = TRUE; dev->hasTextUTF8 = TRUE; dev->textUTF8 = RKD_TextUTF8; dev->strWidthUTF8 = RKD_StrWidthUTF8; dev->wantSymbolUTF8 = TRUE; dev->useRotatedTextInContour = TRUE; #if R_VERSION >= R_Version (2, 14, 0) dev->haveTransparency = 2; dev->haveTransparentBg = 2; // FIXME. Do we really? Check. dev->haveRaster = 2; dev->haveCapture = 2; dev->haveLocator = 2; #endif #if R_VERSION >= R_Version (2, 12, 0) /* * Mouse events */ dev->canGenMouseDown = TRUE; dev->canGenMouseMove = TRUE; dev->canGenMouseUp = TRUE; dev->canGenKeybd = TRUE; // gettingEvent; This is set while getGraphicsEvent is actively // looking for events dev->eventHelper = RKD_EventHelper; dev->onExit = RKD_onExit; #endif /* * Device functions */ dev->activate = RKD_Activate; dev->circle = RKD_Circle; dev->clip = RKD_Clip; dev->close = RKD_Close; dev->deactivate = RKD_Deactivate; dev->locator = RKD_Locator; dev->line = RKD_Line; dev->metricInfo = RKD_MetricInfo; dev->mode = RKD_Mode; dev->newPage = RKD_NewPage; dev->polygon = RKD_Polygon; dev->polyline = RKD_Polyline; #if R_VERSION >= R_Version (2, 12, 0) dev->path = RKD_Path; #endif dev->rect = RKD_Rect; dev->size = RKD_Size; // dev->onexit = RKD_OnExit; Called on user interrupts. NULL is OK. #if R_VERSION >= R_Version (2, 11, 0) dev->raster = RKD_Raster; dev->cap = RKD_Capture; #endif dev->newFrameConfirm = RKD_NewFrameConfirm; return true; } rkward-0.6.4/rkward/rbackend/rkreventloop.cpp0000664000175000017500000001113312633754364020702 0ustar thomasthomas/*************************************************************************** rkreventloop - description ------------------- begin : Tue Apr 23 2013 copyright : (C) 2013 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkreventloop.h" #include "rkrbackend.h" #ifdef Q_WS_WIN # define Win32 # include #else # include #endif #include #include "../debug.h" extern "C" void RK_doIntr (); static void processX11EventsWorker (void *) { // this basically copied from R's unix/sys-std.c (Rstd_ReadConsole) #ifndef Q_WS_WIN for (;;) { fd_set *what; what = R_checkActivityEx(R_wait_usec > 0 ? R_wait_usec : 50, 1, RK_doIntr); R_runHandlers(R_InputHandlers, what); if (what == NULL) break; } /* This seems to be needed to make Rcmdr react to events. Has this always been the case? It was commented out for a long time, without anybody noticing. */ R_PolledEvents (); #else // TODO: correct? // NOTE: We essentially process events while waiting. Perhaps we should simply use the equivalent of "try(sleep(0.01))", instead. R_ProcessEvents(); #endif #if 0 // TODO: The remainder of this function had been commented out since R 2.3.x and is not in Rstd_ReadConsole. Do we still need this? /* I don't really understand what I'm doing here, but apparently this is necessary for Tcl-Tk windows to function properly. */ R_PolledEvents (); /* Maybe we also need to also call R_timeout_handler once in a while? Obviously this is extremely crude code! TODO: verify we really need this. */ if (++timeout_counter > 100) { // extern void (* R_timeout_handler) (); // already defined in Rinferface.h if (R_timeout_handler) R_timeout_handler (); timeout_counter = 0; } #endif } void RKREventLoop::processX11Events() { // do not trace if (!RKRBackend::this_pointer->r_running) return; if (RKRBackend::this_pointer->isKilled ()) return; RKRBackend::RKRBackend::repl_status.eval_depth++; // In case an error (or user interrupt) is caught inside processX11EventsWorker, we don't want to long-jump out. R_ToplevelExec (processX11EventsWorker, 0); RKRBackend::RKRBackend::repl_status.eval_depth--; } static void (* RK_eventHandlerFunction)() = 0; #ifndef Q_OS_WIN static void (* RK_old_R_PolledEvents)(); // NOTE: input-handler-based event loop mechanism is heavily inspired by (but not quite the same as in) package qtbase version 1.0.4 by Michael Lawrence, Deepayan Sarkar. // URL: http://qtinterfaces.r-forge.r-project.org static int ifd = 0; static int ofd = 0; static char buf[16]; static bool rk_event_handler_triggered = false; # include static void RK_eventHandlerWrapper (void *data) { Q_UNUSED (data); rk_event_handler_triggered = false; char buf[16]; bool read_ok = read (ifd, buf, 16); RK_ASSERT (read_ok); RK_eventHandlerFunction (); } static void RK_eventHandlerChain () { if (RK_eventHandlerFunction) RK_eventHandlerFunction (); if (RK_old_R_PolledEvents) RK_old_R_PolledEvents (); } #else void RKREventLoop::winRKEventHandlerWrapper (void) { if (RK_eventHandlerFunction) RK_eventHandlerFunction (); } #endif void RKREventLoop::setRKEventHandler (void (* handler) ()) { RK_TRACE (RBACKEND); RK_ASSERT (!RK_eventHandlerFunction); RK_eventHandlerFunction = handler; #ifndef Q_OS_WIN bool ok = false; int fds[2]; if (!pipe (fds)) { ifd = fds[0]; ofd = fds[1]; addInputHandler (R_InputHandlers, ifd, RK_eventHandlerWrapper, 32); ok = true; } if (ok) return; // if pipe method did not work, fall back to R_PolledEvents RK_old_R_PolledEvents = R_PolledEvents; R_PolledEvents = RK_eventHandlerChain; #endif } void RKREventLoop::wakeRKEventHandler () { #ifndef Q_OS_WIN if (!ofd) return; if (rk_event_handler_triggered) return; rk_event_handler_triggered = true; *buf = 0; bool write_ok = write (ofd, buf, 1); RK_ASSERT (write_ok); #endif } rkward-0.6.4/rkward/rbackend/rkrbackend.cpp0000664000175000017500000017777712633754364020311 0ustar thomasthomas/*************************************************************************** rkrbackend - description ------------------- begin : Sun Jul 25 2004 copyright : (C) 2004 - 2013 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkrbackend.h" #ifdef Q_WS_WIN # include # include # undef ERROR // clashes with R # define Win32 // needed for R includes #else # include # include # include #endif // statics RKRBackend *RKRBackend::this_pointer = 0; RKRBackend::RKReplStatus RKRBackend::repl_status = { QByteArray (), 0, true, 0, 0, RKRBackend::RKReplStatus::NoUserCommand, 0, RKRBackend::RKReplStatus::NotInBrowserContext, false }; void* RKRBackend::default_global_context = 0; #include #include #include #include #include #include "../core/robject.h" #include "../version.h" #include "../debug.h" #include "rkrsupport.h" #include "rkstructuregetter.h" #include "rklocalesupport.h" #include "rksignalsupport.h" #include "rkreventloop.h" #include "../misc/rkcommonfunctions.h" #include #include #include #include extern "C" { #define R_INTERFACE_PTRS 1 // for R_CStackStart/Limit #define CSTACK_DEFNS 1 // keep R from defining tons of aliases #define R_NO_REMAP 1 // What the...? "Conflicting definitions" between stdint.h and Rinterface.h despite the #if in Rinterface.h #define uintptr_t uintptr_t // needed to detect CHARSXP encoding #define IS_UTF8(x) (Rf_getCharCE(x) == CE_UTF8) #define IS_LATIN1(x) (Rf_getCharCE(x) == CE_LATIN1) #include #include #include #include #include #include #include #include #ifdef Q_WS_WIN # include # include structRstart RK_R_Params; // why oh why isn't Rinterface.h available on Windows? LibExtern void* R_GlobalContext; LibExtern uintptr_t R_CStackLimit; LibExtern void R_SaveGlobalEnvToFile(char*); #else # include #endif #ifndef Q_WS_WIN # include // needed for pthread_kill # include // seems to be needed at least on FreeBSD # include // for non-blocking pipes # include #endif ///// i18n #if (defined ENABLE_NLS) // ENABLE_NLS is from Rconfig.h # include # define RK_MSG_DOMAIN "rkward" // define i18n to be compatible with the easiest usage in KDE # define i18n(msgid) QString::fromUtf8(dgettext(RK_MSG_DOMAIN, msgid)) void RK_setupGettext (const char* locale_dir) { bindtextdomain (RK_MSG_DOMAIN, locale_dir); bind_textdomain_codeset (RK_MSG_DOMAIN, "UTF-8"); } #else # define i18n(msgid) QString(msgid) void RK_setupGettext (const char*) {}; #endif ///// interrupting R void RK_scheduleIntr () { RK_DEBUG (RBACKEND, DL_DEBUG, "interrupt scheduled"); RKRBackend::repl_status.interrupted = true; #ifdef Q_WS_WIN UserBreak = 1; #else RKSignalSupport::callOldSigIntHandler (); #endif } void RK_doIntr () { RK_scheduleIntr (); R_CheckUserInterrupt (); } void RKRBackend::scheduleInterrupt () { if (RKRBackendProtocolBackend::inRThread ()) { RK_scheduleIntr (); } else { #ifdef Q_WS_WIN RK_scheduleIntr (); // Thread-safe on windows?! #else pthread_kill ((pthread_t) RKRBackendProtocolBackend::instance ()->r_thread_id, SIGUSR1); // NOTE: SIGUSR1 relays to SIGINT #endif } } void RKRBackend::interruptCommand (int command_id) { RK_TRACE (RBACKEND); QMutexLocker lock (&all_current_commands_mutex); if (all_current_commands.isEmpty ()) return; if ((command_id == -1) || (all_current_commands.last ()->id == command_id)) { if (!too_late_to_interrupt) { RK_DEBUG (RBACKEND, DL_DEBUG, "scheduling interrupt for command id %d", command_id); scheduleInterrupt (); } } else { // if the command to cancel is *not* the topmost command, then do not interrupt, yet. foreach (RCommandProxy *candidate, all_current_commands) { if (candidate->id == command_id) { if (!current_commands_to_cancel.contains (candidate)) { RK_DEBUG (RBACKEND, DL_DEBUG, "scheduling delayed interrupt for command id %d", command_id); current_commands_to_cancel.append (candidate); } } } } } void clearPendingInterrupt_Worker (void *) { R_CheckUserInterrupt (); } void RKRBackend::clearPendingInterrupt () { RK_TRACE (RBACKEND); bool passed = R_ToplevelExec (clearPendingInterrupt_Worker, 0); if (!passed) RK_DEBUG (RBACKEND, DL_DEBUG, "pending interrupt cleared"); } // some functions we need that are not declared #if R_VERSION < R_Version(2,13,0) LibExtern void Rf_PrintWarnings (void); #endif LibExtern void run_Rmainloop (void); #include } #include "rdata.h" extern SEXP RKWard_RData_Tag; // ############## R Standard callback overrides BEGIN #################### Rboolean RKToplevelStatementFinishedCallback (SEXP expr, SEXP value, Rboolean succeeded, Rboolean visible, void *) { RK_TRACE (RBACKEND); Q_UNUSED (expr); Q_UNUSED (value); Q_UNUSED (visible); if ((RKRBackend::repl_status.eval_depth == 0) && (!RKRBackend::repl_status.browser_context)) { // Yes, toplevel-handlers _do_ get called in a browser context! RK_ASSERT (RKRBackend::repl_status.user_command_status = RKRBackend::RKReplStatus::UserCommandRunning); if (succeeded) { RKRBackend::repl_status.user_command_successful_up_to = RKRBackend::repl_status.user_command_parsed_up_to; if (RKRBackend::repl_status.user_command_completely_transmitted) { RKRBackend::repl_status.user_command_status = RKRBackend::RKReplStatus::NoUserCommand; RKRBackend::this_pointer->commandFinished (); } else RKRBackend::repl_status.user_command_status = RKRBackend::RKReplStatus::UserCommandTransmitted; } else { // well, this point of code is never reached with R up to 2.12.0. Instead failed user commands are handled in doError(). RKRBackend::repl_status.user_command_status = RKRBackend::RKReplStatus::UserCommandFailed; } } return (Rboolean) true; } void RKInsertToplevelStatementFinishedCallback (void *) { RK_TRACE (RBACKEND); if (RKRBackend::this_pointer->r_running) { int pos; Rf_addTaskCallback (&RKToplevelStatementFinishedCallback, 0, &RKInsertToplevelStatementFinishedCallback, "_rkward_main_callback", &pos); } } void RKTransmitNextUserCommandChunk (unsigned char* buf, int buflen) { RK_TRACE (RBACKEND); RK_ASSERT (RKRBackend::repl_status.user_command_transmitted_up_to <= RKRBackend::repl_status.user_command_buffer.length ()); // NOTE: QByteArray::length () does not count the trailing '\0' const char* current_buffer = RKRBackend::repl_status.user_command_buffer.data (); current_buffer += RKRBackend::repl_status.user_command_transmitted_up_to; // Skip what we have already transmitted bool reached_eof = false; int pos = 0; const int max_pos = buflen - 2; // one for the termination bool reached_newline = false; while (true) { buf[pos] = *current_buffer; if (*current_buffer == '\n') { reached_newline = true; break; } else if (*current_buffer == ';') break; else if (*current_buffer == '\0') { reached_eof = true; break; } if (pos >= max_pos) break; ++current_buffer; ++pos; } RKRBackend::repl_status.user_command_transmitted_up_to += (pos + 1); if (reached_eof) { buf[pos] = '\n'; RKRBackend::repl_status.user_command_completely_transmitted = true; } buf[++pos] = '\0'; if (reached_newline || reached_eof) { // Making this request synchronous is a bit painful. However, without this, it's extremely difficult to get correct interleaving of output and command lines RBackendRequest req (true, RBackendRequest::CommandLineIn); req.params["commandid"] = RKRBackend::this_pointer->current_command->id; RKRBackend::this_pointer->handleRequest (&req); } } void RCleanUp (SA_TYPE saveact, int status, int RunLast); int RReadConsole (const char* prompt, unsigned char* buf, int buflen, int hist) { RK_TRACE (RBACKEND); RK_ASSERT (buf && buflen); RK_ASSERT (RKRBackend::repl_status.eval_depth >= 0); if (RKRBackend::repl_status.browser_context) { // previously we were in a browser context. Check, whether we've left that. if (RKRBackend::default_global_context == R_GlobalContext) { RKRBackend::repl_status.browser_context = RKRBackend::RKReplStatus::NotInBrowserContext; RKRBackend::this_pointer->handlePlainGenericRequest (QStringList ("endBrowserContext"), false); } } if ((!RKRBackend::repl_status.browser_context) && (RKRBackend::repl_status.eval_depth == 0)) { while (1) { if (RKRBackend::repl_status.user_command_status == RKRBackend::RKReplStatus::NoUserCommand) { RCommandProxy *command = RKRBackend::this_pointer->fetchNextCommand (); if (!command) { #ifdef Q_OS_WIN // Can't easily override R_CleanUp on Windows, so we're calling it manually, here, then force exit if (RKRBackend::this_pointer->killed == RKRBackend::ExitNow) RCleanUp (SA_NOSAVE, 0, 0); else RCleanUp (SA_SUICIDE, 1, 0); exit (0); #endif return 0; // jumps out of the event loop! } if (!(command->type & RCommand::User)) { RKRBackend::this_pointer->runCommand (command); RKRBackend::this_pointer->commandFinished (); } else { // so, we are about to transmit a new user command, which is quite a complex endeavour... /* Some words about running user commands: - User commands can only be run at the top level of execution, not in any sub-stacks. But then, they should never get there, in the first place. - Handling user commands is totally different from all other commands, and relies on R's "REPL" (read-evaluate-print-loop). This is a whole bunch of dedicated code, but there is no other way to achieve handling of commands as if they had been entered on a plain R console (incluing auto-printing, and toplevel handlers). Most importantly, since important symbols are not exported, such as R_Visible. Vice versa, it is not possible to treat all commands like user commands, esp. in substacks. Problems to deal with: - R_ReadConsole serves a lot of different functions, including reading in code, but also handling user input for readline() or browser(). This makes it necessary to carefully track the current status using "repl_status". You will find repl_status to be modified at a couple of different functions. - One difficulty lies in finding out, just when a command has finished (successfully or with an error). RKToplevelStatementFinishCallback(), and doError() handle the respective cases. NOTE; in R 2.12.0 and above, Rf_countContexts() might help to find out when we are back to square 1! */ RKRBackend::repl_status.user_command_transmitted_up_to = 0; RKRBackend::repl_status.user_command_completely_transmitted = false; RKRBackend::repl_status.user_command_parsed_up_to = 0; RKRBackend::repl_status.user_command_successful_up_to = 0; RKRBackend::repl_status.user_command_buffer = RKRBackend::this_pointer->current_locale_codec->fromUnicode (command->command); RKTransmitNextUserCommandChunk (buf, buflen); RKRBackend::repl_status.user_command_status = RKRBackend::RKReplStatus::UserCommandTransmitted; return 1; } } else if (RKRBackend::repl_status.user_command_status == RKRBackend::RKReplStatus::UserCommandTransmitted) { if (RKRBackend::repl_status.user_command_completely_transmitted) { // fully transmitted, but R is still asking for more? This looks like an incomplete statement. // HOWEVER: It may also have been an empty statement such as " ", so let's check whether the prompt looks like a "continue" prompt bool incomplete = false; if (RKRBackend::this_pointer->current_locale_codec->toUnicode (prompt) == RKRSupport::SEXPToString (Rf_GetOption (Rf_install ("continue"), R_BaseEnv))) { incomplete = true; } if (incomplete) RKRBackend::this_pointer->current_command->status |= RCommand::Failed | RCommand::ErrorIncomplete; RKRBackend::repl_status.user_command_status = RKRBackend::RKReplStatus::ReplIterationKilled; if (RKRBackend::RKRBackend::repl_status.user_command_parsed_up_to <= 0) RKRBackend::this_pointer->startOutputCapture (); // HACK: No capture active, but commandFinished() will try to end one Rf_error (""); // to discard the buffer } else { RKTransmitNextUserCommandChunk (buf, buflen); return 1; } } else if (RKRBackend::repl_status.user_command_status == RKRBackend::RKReplStatus::UserCommandSyntaxError) { RKRBackend::this_pointer->current_command->status |= RCommand::Failed | RCommand::ErrorSyntax; RKRBackend::repl_status.user_command_status = RKRBackend::RKReplStatus::NoUserCommand; if (RKRBackend::RKRBackend::repl_status.user_command_parsed_up_to <= 0) RKRBackend::this_pointer->startOutputCapture (); // HACK: No capture active, but commandFinished() will try to end one RKRBackend::this_pointer->commandFinished (); } else if (RKRBackend::repl_status.user_command_status == RKRBackend::RKReplStatus::UserCommandRunning) { // This can mean three different things: // 1) User called readline () // 2) User called browser () // 3) R jumped us back to toplevel behind our backs. // Let's find out, which one it is. if (hist && (RKRBackend::default_global_context != R_GlobalContext)) { break; // this looks like a call to browser(). Will be handled below. } int n_frames = 0; RCommandProxy *dummy = RKRBackend::this_pointer->runDirectCommand ("sys.nframe()", RCommand::GetIntVector); if ((dummy->getDataType () == RData::IntVector) && (dummy->getDataLength () == 1)) { n_frames = dummy->intVector ().at (0); } // What the ??? Why does this simple version always return 0? //int n_frames = RKRSupport::SEXPToInt (RKRSupport::callSimpleFun0 (Rf_install ("sys.nframe"), R_GlobalEnv)); if (n_frames < 1) { // No active frames? This can't be a call to readline(), then, so probably R jumped us back to toplevel, behind our backs. // For safety, let's reset and start over. RKRBackend::this_pointer->current_command->status |= RCommand::Failed | RCommand::ErrorOther; RKRBackend::repl_status.user_command_status = RKRBackend::RKReplStatus::ReplIterationKilled; Rf_error(""); // to discard the buffer } else { // A call to readline(). Will be handled below break; } } else if (RKRBackend::repl_status.user_command_status == RKRBackend::RKReplStatus::UserCommandFailed) { RKRBackend::this_pointer->current_command->status |= RCommand::Failed | RCommand::ErrorOther; RKRBackend::repl_status.user_command_status = RKRBackend::RKReplStatus::NoUserCommand; RKRBackend::this_pointer->commandFinished (); } else { RK_ASSERT (RKRBackend::repl_status.user_command_status == RKRBackend::RKReplStatus::ReplIterationKilled); RKRBackend::repl_status.user_command_status = RKRBackend::RKReplStatus::NoUserCommand; RKRBackend::this_pointer->commandFinished (); } } } // here, we handle readline() calls and such, i.e. not the regular prompt for code // browser() also takes us here. QVariantMap params; RBackendRequest::RCallbackType request_type = RBackendRequest::ReadLine; params["prompt"] = QVariant (prompt); params["cancelled"] = QVariant (false); // add info for browser requests if (hist && (RKRBackend::default_global_context != R_GlobalContext)) { if (RKRBackend::repl_status.browser_context == RKRBackend::RKReplStatus::InBrowserContextPreventRecursion) { qstrncpy ((char *) buf, "n\n", buflen); // skip this, by feeding the browser() a continue return 1; } else { RKRBackend::repl_status.browser_context = RKRBackend::RKReplStatus::InBrowserContextPreventRecursion; RCommandProxy *dummy = RKRBackend::this_pointer->runDirectCommand (".rk.callstack.info()", RCommand::GetStructuredData); request_type = RBackendRequest::Debugger; if ((dummy->getDataType () == RData::StructureVector) && (dummy->getDataLength () >= 4)) { RData::RDataStorage dummy_data = dummy->structureVector (); params["calls"] = QVariant (dummy_data.at (0)->stringVector ()); params["funs"] = QVariant (dummy_data.at (1)->stringVector ()); params["envs"] = QVariant (dummy_data.at (2)->stringVector ()); params["locals"] = QVariant (dummy_data.at (3)->stringVector ()); params["relsrclines"] = QVariant (dummy_data.at (4)->stringVector ()); // hacky: passing a QList is not supported by QVariant } else { RK_ASSERT (false); } RKRBackend::repl_status.browser_context = RKRBackend::RKReplStatus::InBrowserContext; } RK_ASSERT (RKRBackend::repl_status.browser_context == RKRBackend::RKReplStatus::InBrowserContext); } RBackendRequest request (true, request_type); request.params = params; RKRBackend::this_pointer->handleRequest (&request); if (request.params["cancelled"].toBool ()) { if (RKRBackend::this_pointer->current_command) RKRBackend::this_pointer->current_command->status |= RCommand::Canceled; Rf_error ("cancelled"); RK_ASSERT (false); // should not reach this point. } QByteArray localres = RKRBackend::this_pointer->current_locale_codec->fromUnicode (request.params["result"].toString ()); // need to append a newline, here. TODO: theoretically, RReadConsole comes back for more, if \0 was encountered before \n. qstrncpy ((char *) buf, localres.left (buflen - 2).append ('\n').data (), buflen); return 1; } #ifdef Q_WS_WIN int RReadConsoleWin (const char* prompt, char* buf, int buflen, int hist) { return RReadConsole (prompt, (unsigned char*) buf, buflen, hist); } #endif bool RKRBackend::fetchStdoutStderr (bool forcibly) { #ifndef Q_OS_WIN if (!forcibly) { if (!stdout_stderr_mutex.tryLock ()) return false; } else { stdout_stderr_mutex.lock (); } if (stdout_stderr_fd < 0) { stdout_stderr_mutex.unlock (); return false; } // it seems, setting this only once is not always enough. fcntl (stdout_stderr_fd, F_SETFL, fcntl (stdout_stderr_fd, F_GETFL, 0) | O_NONBLOCK); char buffer[1024]; while (true) { int bytes = read (stdout_stderr_fd, buffer, 1023); if (bytes <= 0) break; buffer[bytes] = '\0'; // NOTE: we must not risk blocking inside handleOutput, while the stdout_stderr_mutex is locked! handleOutput (current_locale_codec->toUnicode (buffer, bytes), bytes, ROutput::Warning, false); } stdout_stderr_mutex.unlock (); #endif return true; } void RWriteConsoleEx (const char *buf, int buflen, int type) { RK_TRACE (RBACKEND); RK_DEBUG (RBACKEND, DL_DEBUG, "raw output type %d, size %d: %s", type, buflen, buf); // output while nothing else is running (including handlers?) -> This may be a syntax error. if ((RKRBackend::repl_status.eval_depth == 0) && (!RKRBackend::repl_status.browser_context) && (!RKRBackend::this_pointer->isKilled ())) { if (RKRBackend::repl_status.user_command_status == RKRBackend::RKReplStatus::UserCommandTransmitted) { // status UserCommandTransmitted might have been set from RKToplevelStatementFinishedHandler, too, in which case all is fine // (we're probably inside another task handler at this point, then) if (RKRBackend::repl_status.user_command_parsed_up_to < RKRBackend::repl_status.user_command_transmitted_up_to) { RKRBackend::repl_status.user_command_status = RKRBackend::RKReplStatus::UserCommandSyntaxError; } } else if (RKRBackend::repl_status.user_command_status == RKRBackend::RKReplStatus::ReplIterationKilled) { // purge superfluous newlines and empty output return; } else { RK_ASSERT (RKRBackend::repl_status.user_command_status != RKRBackend::RKReplStatus::NoUserCommand); } } if (RKRBackend::this_pointer->killed == RKRBackend::AlreadyDead) return; // this check is mostly for fork()ed clients if (RKRBackend::repl_status.browser_context == RKRBackend::RKReplStatus::InBrowserContextPreventRecursion) return; RKRBackend::this_pointer->fetchStdoutStderr (true); RKRBackend::this_pointer->handleOutput (RKRBackend::this_pointer->current_locale_codec->toUnicode (buf, buflen), buflen, type == 0 ? ROutput::Output : ROutput::Warning); } /** For R callbacks that we want to disable, entirely */ void RDoNothing () { //RK_TRACE (RBACKEND); } void RCleanUp (SA_TYPE saveact, int status, int RunLast) { RK_TRACE (RBACKEND); Q_UNUSED (RunLast); // R_dot_Last is called while "running" the QuitCommand if (RKRBackend::this_pointer->killed == RKRBackend::AlreadyDead) return; // Nothing to clean up if (!RKRBackend::this_pointer->r_running) return; // prevent recursion (if an error occurs, here, we get jumped to the console repl, again!) R_CheckUserInterrupt (); // if there are any user interrupts pendinging, we want them handled *NOW* RKRBackend::this_pointer->r_running = false; // we could be in a signal handler, and the stack base may have changed. uintptr_t old_lim = R_CStackLimit; R_CStackLimit = (uintptr_t)-1; if ((status != 0) && (RKRBackend::this_pointer->killed != RKRBackend::ExitNow)) RKRBackend::this_pointer->killed = RKRBackend::EmergencySaveThenExit; if (RKRBackend::this_pointer->killed == RKRBackend::EmergencySaveThenExit) { if (R_DirtyImage) { QString filename; QDir dir (RKRBackendProtocolBackend::dataDir ()); int i=0; while (true) { filename = "rkward_recover" + QString::number (i) + ".RData"; if (!dir.exists (filename)) break; i++; } filename = dir.absoluteFilePath (filename); R_SaveGlobalEnvToFile (filename.toLocal8Bit ().data ()); qDebug ("Created emergency save file in %s", qPrintable (filename)); } else { qDebug ("Image not dirty while crashing. No emergency save created."); } } if (saveact != SA_SUICIDE) { if (!RKRBackend::this_pointer->isKilled ()) { RBackendRequest request (true, RBackendRequest::BackendExit); request.params["message"] = QVariant (i18n ("The R engine has shut down with status: %1").arg (status)); RKRBackend::this_pointer->handleRequest (&request); } R_RunExitFinalizers (); Rf_KillAllDevices (); R_CleanTempDir (); } RKRBackend::this_pointer->killed = RKRBackend::AlreadyDead; // just in case R_CStackLimit = old_lim; // well, it should not matter any longer, but... } void RSuicide (const char* message) { RK_TRACE (RBACKEND); if (!RKRBackend::this_pointer->isKilled ()) { RBackendRequest request (true, RBackendRequest::BackendExit); request.params["message"] = QVariant (i18n ("The R engine has encountered a fatal error:\n%1").arg (message)); RKRBackend::this_pointer->handleRequest (&request); RKRBackend::this_pointer->killed = RKRBackend::EmergencySaveThenExit; RCleanUp (SA_SUICIDE, 1, 0); } else { RK_ASSERT (false); } } void RKRBackend::tryToDoEmergencySave () { RK_TRACE (RBACKEND); if (RKRBackendProtocolBackend::inRThread ()) { // If we are in the correct thread, things are easy: RKRBackend::this_pointer->killed = RKRBackend::EmergencySaveThenExit; RCleanUp (SA_SUICIDE, 1, 0); RK_doIntr (); // to jump out of the loop, if needed } else { // If we are in the wrong thread, things are a lot more tricky. We need to cause the R thread to exit, and wait for it to finish saving. // Fortunately, if we are in the wrong thread, that probably means, the R thread did *not* crash, and will thus still be functional this_pointer->killed = EmergencySaveThenExit; RK_scheduleIntr (); for (int i = 0; i < 100; ++i) { // give it up to ten seconds to intterrupt and exit the loop if (!this_pointer->r_running) break; RKRBackendProtocolBackend::msleep (100); } if (!this_pointer->r_running) { for (int i = 0; i < 600; ++i) { // give it up to sixty seconds to finish saving if (this_pointer->killed == AlreadyDead) return; // finished RKRBackendProtocolBackend::msleep (100); } } RK_ASSERT (false); // Too bad, but we seem to be stuck. No chance but to return (and crash) } } QStringList charPArrayToQStringList (const char** chars, int count) { QStringList ret; for (int i = 0; i < count; ++i) { // do we need to do locale conversion, here? ret.append (chars[i]); } return ret; } int RChooseFile (int isnew, char *buf, int len) { RK_TRACE (RBACKEND); RBackendRequest request (true, RBackendRequest::ChooseFile); request.params["new"] = QVariant ((bool) isnew); RKRBackend::this_pointer->handleRequest (&request); QByteArray localres = RKRBackend::this_pointer->current_locale_codec->fromUnicode (request.params["result"].toString ()); qstrncpy ((char *) buf, localres.data (), len); // return length of filename (strlen (buf)) return (qMin (len - 1, localres.size ())); } /* There are about one million possible entry points to editing / showing files. We try to cover them all, using the following bunch of functions (REditFilesHelper() and doShowEditFiles() are helpers, only) */ void REditFilesHelper (QStringList files, QStringList titles, QString wtitle, RBackendRequest::RCallbackType edit, bool delete_files, bool prompt) { RK_TRACE (RBACKEND); RK_ASSERT ((edit == RBackendRequest::ShowFiles) || (edit == RBackendRequest::EditFiles)); RBackendRequest request (edit != RBackendRequest::ShowFiles, edit); // editing is synchronous, showing is asynchronous if (edit == RBackendRequest::ShowFiles) { request.params["delete"] = QVariant (delete_files); } // see ?file.show() for what appears to be the intended meaning of these first three parameters // (which seem to be inconsistently named even in R itself...) request.params["files"] = QVariant (files); request.params["titles"] = QVariant (titles); request.params["wtitle"] = QVariant (wtitle); request.params["prompt"] = QVariant (prompt); RKRBackend::this_pointer->handleRequest (&request); } int REditFiles (int nfile, const char **file, const char **title, const char *wtitle) { RK_TRACE (RBACKEND); REditFilesHelper (charPArrayToQStringList (file, nfile), charPArrayToQStringList (title, nfile), wtitle, RBackendRequest::EditFiles, false, true); // default implementation seems to return 1 if nfile <= 0, else 1. No idea, what for. see unix/std-sys.c return (nfile <= 0); } SEXP doShowEditFiles (SEXP files, SEXP titles, SEXP wtitle, SEXP del, SEXP prompt, RBackendRequest::RCallbackType edit) { RK_TRACE (RBACKEND); QStringList file_strings = RKRSupport::SEXPToStringList (files); QStringList title_strings = RKRSupport::SEXPToStringList (titles); QString wtitle_string = RKRSupport::SEXPToString (wtitle); bool del_files = RKRSupport::SEXPToInt (del, 0) != 0; bool do_prompt = RKRSupport::SEXPToInt (prompt, 0) != 0; RK_ASSERT (file_strings.size () == title_strings.size ()); RK_ASSERT (file_strings.size () >= 1); REditFilesHelper (file_strings, title_strings, wtitle_string, edit, del_files, do_prompt); return (R_NilValue); } SEXP doEditFiles (SEXP files, SEXP titles, SEXP wtitle, SEXP prompt) { return (doShowEditFiles (files, titles, wtitle, R_NilValue, prompt, RBackendRequest::EditFiles)); } int REditFile (const char *buf) { RK_TRACE (RBACKEND); const char *editor = "none"; const char *title = ""; // does not exist in standard R 2.1.0, so no idea what to return. return REditFiles (1, const_cast (&buf), &title, editor); } SEXP doShowFiles (SEXP files, SEXP titles, SEXP wtitle, SEXP delete_files, SEXP prompt) { return (doShowEditFiles (files, titles, wtitle, delete_files, prompt, RBackendRequest::ShowFiles)); } int RShowFiles (int nfile, const char **file, const char **headers, const char *wtitle, Rboolean del, const char */* pager */) { RK_TRACE (RBACKEND); REditFilesHelper (charPArrayToQStringList (file, nfile), charPArrayToQStringList (headers, nfile), QString (wtitle), RBackendRequest::ShowFiles, (bool) del, true); // default implementation seems to returns 1 on success, 0 on failure. see unix/std-sys.c return 1; } /* FROM R_ext/RStartup.h: "Return value here is expected to be 1 for Yes, -1 for No and 0 for Cancel: symbolic constants in graphapp.h" */ int doDialogHelper (QString caption, QString message, QString button_yes, QString button_no, QString button_cancel, bool wait) { RK_TRACE (RBACKEND); RBackendRequest request (wait, RBackendRequest::ShowMessage); request.params["caption"] = QVariant (caption); request.params["message"] = QVariant (message); request.params["button_yes"] = QVariant (button_yes); request.params["button_no"] = QVariant (button_no); request.params["button_cancel"] = QVariant (button_cancel); RKRBackend::this_pointer->handleRequest (&request); if (wait) { QString ret = request.params["result"].toString (); if (ret == "yes") return 1; if (ret == "no") return -1; } return 0; } SEXP doDialog (SEXP caption, SEXP message, SEXP button_yes, SEXP button_no, SEXP button_cancel, SEXP wait) { RK_TRACE (RBACKEND); int result = doDialogHelper (RKRSupport::SEXPToString (caption), RKRSupport::SEXPToString (message), RKRSupport::SEXPToString (button_yes), RKRSupport::SEXPToString (button_no), RKRSupport::SEXPToString (button_cancel), RKRSupport::SEXPToInt (wait)); SEXP ret = Rf_allocVector(INTSXP, 1); INTEGER (ret)[0] = result; return ret; } void RShowMessage (const char* message) { RK_TRACE (RBACKEND); doDialogHelper (i18n ("Message from the R backend"), message, "ok", QString (), QString (), true); } // TODO: currently used on windows, only! int RAskYesNoCancel (const char* message) { RK_TRACE (RBACKEND); if (RKRBackend::this_pointer->killed) return -1; // HACK: At this point R asks whether to save the workspace. We have already handled that. So return -1 for "no" return doDialogHelper (i18n ("Question from the R backend"), message, "yes", "no", "cancel", true); } void RBusy (int busy) { RK_TRACE (RBACKEND); // R_ReplIteration calls R_Busy (1) after reading in code (if needed), successfully parsing it, and right before evaluating it. if (busy) { if (RKRBackend::repl_status.user_command_status == RKRBackend::RKReplStatus::UserCommandTransmitted) { if (RKRBackend::this_pointer->current_command->type & RCommand::CCCommand) { QByteArray chunk = RKRBackend::repl_status.user_command_buffer.mid (RKRBackend::repl_status.user_command_parsed_up_to, RKRBackend::repl_status.user_command_transmitted_up_to - RKRBackend::repl_status.user_command_parsed_up_to); RKRBackend::this_pointer->printCommand (RKRBackend::this_pointer->current_locale_codec->toUnicode (chunk)); } if (RKRBackend::this_pointer->current_command->type & RCommand::CCOutput) { // flush any previous output caputre and start a new one if (RKRBackend::repl_status.user_command_successful_up_to > 0) RKRBackend::this_pointer->printAndClearCapturedMessages (false); RKRBackend::this_pointer->startOutputCapture (); } RKRBackend::repl_status.user_command_parsed_up_to = RKRBackend::repl_status.user_command_transmitted_up_to; RKRBackend::repl_status.user_command_status = RKRBackend::RKReplStatus::UserCommandRunning; } } } // ############## R Standard callback overrides END #################### // NOTE: stdout_stderr_mutex is recursive to support fork()s, better RKRBackend::RKRBackend () : stdout_stderr_mutex (QMutex::Recursive) { RK_TRACE (RBACKEND); current_locale_codec = QTextCodec::codecForLocale (); r_running = false; current_command = 0; pending_priority_command = 0; stdout_stderr_fd = -1; RK_ASSERT (this_pointer == 0); this_pointer = this; } #ifdef Q_WS_WIN void RKRBackend::setupCallbacks () { RK_TRACE (RBACKEND); R_setStartTime(); R_DefParams(&RK_R_Params); // IMPORTANT: see also the #ifndef QS_WS_WIN-portion! RK_R_Params.rhome = get_R_HOME (); RK_R_Params.home = getRUser (); RK_R_Params.CharacterMode = RGui; RK_R_Params.ShowMessage = RShowMessage; RK_R_Params.ReadConsole = RReadConsoleWin; RK_R_Params.WriteConsoleEx = RWriteConsoleEx; RK_R_Params.WriteConsole = 0; RK_R_Params.CallBack = RKREventLoop::winRKEventHandlerWrapper; RK_R_Params.YesNoCancel = RAskYesNoCancel; RK_R_Params.Busy = RBusy; // TODO: callback mechanism(s) for ChosseFile, ShowFiles, EditFiles // TODO: also for RSuicide (Less important, obviously, since this should not be triggered, in normal operation). // NOTE: For RCleanUp see RReadConsole RCleanup? RK_R_Params.R_Quiet = (Rboolean) 0; RK_R_Params.R_Interactive = (Rboolean) 1; } void RKRBackend::connectCallbacks () { RK_TRACE (RBACKEND); R_SetParams(&RK_R_Params); } #else void RKRBackend::setupCallbacks () { RK_TRACE (RBACKEND); } /* SEXP dummyselectlist (SEXP, SEXP, SEXP, SEXP) { qDebug ("got it"); return R_NilValue; }*/ void RKRBackend::connectCallbacks () { RK_TRACE (RBACKEND); // IMPORTANT: see also the #ifdef QS_WS_WIN-portion! // connect R standard callback to our own functions. Important: Don't do so, before our own versions are ready to be used! R_Outputfile = NULL; R_Consolefile = NULL; ptr_R_Suicide = RSuicide; ptr_R_ShowMessage = RShowMessage; // rarely used in R on unix ptr_R_ReadConsole = RReadConsole; ptr_R_WriteConsoleEx = RWriteConsoleEx; ptr_R_WriteConsole = 0; ptr_R_ResetConsole = RDoNothing; ptr_R_FlushConsole = RDoNothing; ptr_R_ClearerrConsole = RDoNothing; ptr_R_Busy = RBusy; ptr_R_CleanUp = RCleanUp; // unfortunately, it seems, we can't safely cancel quitting anymore, here! ptr_R_ShowFiles = RShowFiles; ptr_R_ChooseFile = RChooseFile; // TODO: R devels disabled this for some reason. We set it anyway... ptr_R_EditFile = REditFile; // ptr_R_EditFiles = REditFiles; // undefined reference /* ptr_do_selectlist = dummyselectlist; ptr_do_dataviewer = dummyselectlist;*/ // these two, we won't override // ptr_R_loadhistory = ... // we keep our own history // ptr_R_savehistory = ... // we keep our own history } #endif RKRBackend::~RKRBackend () { RK_TRACE (RBACKEND); } LibExtern int R_interrupts_pending; SEXP doError (SEXP call) { RK_TRACE (RBACKEND); if ((RKRBackend::repl_status.eval_depth == 0) && (!RKRBackend::repl_status.browser_context) && (!RKRBackend::this_pointer->isKilled ()) && (RKRBackend::repl_status.user_command_status != RKRBackend::RKReplStatus::ReplIterationKilled) && (!RKRBackend::repl_status.user_command_status == RKRBackend::RKReplStatus::NoUserCommand)) { RKRBackend::repl_status.user_command_status = RKRBackend::RKReplStatus::UserCommandFailed; } if (RKRBackend::repl_status.interrupted) { // it is unlikely, but possible, that an interrupt signal was received, but the current command failed for some other reason, before processing was acutally interrupted. In this case, R_interrupts_pending if not yet cleared. // NOTE: if R_interrupts_pending stops being exported one day, we might be able to use R_CheckUserInterrupt() inside an R_ToplevelExec() to find out, whether an interrupt was still pending. if (!R_interrupts_pending) { RKRBackend::repl_status.interrupted = false; if (RKRBackend::repl_status.user_command_status != RKRBackend::RKReplStatus::ReplIterationKilled) { // was interrupted only to step out of the repl iteration QMutexLocker lock (&(RKRBackend::this_pointer->all_current_commands_mutex)); foreach (RCommandProxy *command, RKRBackend::this_pointer->all_current_commands) command->status |= RCommand::Canceled; RK_DEBUG (RBACKEND, DL_DEBUG, "interrupted"); } } } else if (RKRBackend::repl_status.user_command_status != RKRBackend::RKReplStatus::ReplIterationKilled) { QString string = RKRSupport::SEXPToString (call); RKRBackend::this_pointer->handleOutput (string, string.length (), ROutput::Error); RK_DEBUG (RBACKEND, DL_DEBUG, "error '%s'", qPrintable (string)); } return R_NilValue; } SEXP doSubstackCall (SEXP call) { RK_TRACE (RBACKEND); R_CheckUserInterrupt (); QStringList list = RKRSupport::SEXPToStringList (call); // handle symbol updates inline if (list.count () == 2) { // schedule symbol update for later if (list[0] == "ws") { // always keep in mind: No current command can happen for tcl/tk events. if ((!RKRBackend::this_pointer->current_command) || (RKRBackend::this_pointer->current_command->type & RCommand::ObjectListUpdate) || (!(RKRBackend::this_pointer->current_command->type & RCommand::Sync))) { // ignore Sync commands that are not flagged as ObjectListUpdate if (!RKRBackend::this_pointer->changed_symbol_names.contains (list[1])) RKRBackend::this_pointer->changed_symbol_names.append (list[1]); } return R_NilValue; } } /* // this is a useful place to sneak in test code for profiling if (list.value (0) == "testit") { for (int i = 10000; i >= 1; --i) { setWarnOption (i); } return R_NilValue; } */ RKRBackend::this_pointer->handleHistoricalSubstackRequest (list); return R_NilValue; } SEXP doPlainGenericRequest (SEXP call, SEXP synchronous) { RK_TRACE (RBACKEND); R_CheckUserInterrupt (); QStringList ret = RKRBackend::this_pointer->handlePlainGenericRequest (RKRSupport::SEXPToStringList (call), RKRSupport::SEXPToInt (synchronous)); return RKRSupport::StringListToSEXP (ret); } void R_CheckStackWrapper (void *) { R_CheckStack (); } SEXP doUpdateLocale () { RK_TRACE (RBACKEND); RK_DEBUG (RBACKEND, DL_WARNING, "Changing locale"); RKRBackend::this_pointer->current_locale_codec = RKGetCurrentLocaleCodec (); RK_DEBUG (RBACKEND, DL_WARNING, "New locale codec is %s", RKRBackend::this_pointer->current_locale_codec->name ().data ()); return R_NilValue; } // returns the MIME-name of the current locale encoding (from Qt) SEXP doLocaleName () { RK_TRACE (RBACKEND); RK_ASSERT (RKRBackend::this_pointer->current_locale_codec); SEXP res = Rf_allocVector(STRSXP, 1); PROTECT (res); SET_STRING_ELT (res, 0, Rf_mkChar (RKRBackend::this_pointer->current_locale_codec->name ().data ())); UNPROTECT (1); return res; } SEXP doGetStructure (SEXP toplevel, SEXP name, SEXP envlevel, SEXP namespacename) { RK_TRACE (RBACKEND); RKStructureGetter getter (false); RData *ret = getter.getStructure (toplevel, name, envlevel, namespacename); return R_MakeExternalPtr (ret, RKWard_RData_Tag, R_NilValue); } SEXP doGetGlobalEnvStructure (SEXP name, SEXP envlevel, SEXP namespacename) { RK_TRACE (RBACKEND); return doGetStructure (Rf_findVar (Rf_install (CHAR (STRING_ELT (name, 0))), R_GlobalEnv), name, envlevel, namespacename); } /** copy a symbol without touching it (esp. not forcing any promises) */ SEXP doCopyNoEval (SEXP name, SEXP fromenv, SEXP toenv) { RK_TRACE (RBACKEND); if(!Rf_isString (name) || Rf_length (name) != 1) Rf_error ("name is not a single string"); if(!Rf_isEnvironment (fromenv)) Rf_error ("fromenv is not an environment"); if(!Rf_isEnvironment (toenv)) Rf_error ("toenv is not an environment"); Rf_defineVar (Rf_install (CHAR (STRING_ELT (name, 0))), Rf_findVar (Rf_install (CHAR (STRING_ELT (name, 0))), fromenv), toenv); return (R_NilValue); } SEXP RKStartGraphicsDevice (SEXP width, SEXP height, SEXP pointsize, SEXP family, SEXP bg, SEXP title, SEXP antialias); SEXP RKD_AdjustSize (SEXP devnum); void doPendingPriorityCommands (); bool RKRBackend::startR () { RK_TRACE (RBACKEND); setupCallbacks (); RKSignalSupport::saveDefaultSignalHandlers (); too_late_to_interrupt = false; r_running = true; int argc = 3; char* argv[3] = { qstrdup ("--slave"), qstrdup ("--no-save"), qstrdup ("--no-restore") }; Rf_initialize_R (argc, argv); #ifdef Q_WS_WIN R_set_command_line_arguments(argc, argv); FlushConsoleInputBuffer(GetStdHandle(STD_INPUT_HANDLE)); #endif #ifndef Q_OS_WIN // re-direct stdout / stderr to a pipe, so we can read output from system() calls int pfd[2]; int error = pipe (pfd); RK_ASSERT (!error); // mostly to silence compile time warning about unused return value dup2 (pfd[1], STDOUT_FILENO); dup2 (pfd[1], STDERR_FILENO); // forward both to a single channel to avoid interleaving hell, for now. close (pfd[1]); stdout_stderr_fd = pfd[0]; #endif #ifndef Q_WS_WIN // It is important to set this *early*, so R does not bail out, if there is an error in .Rprofile. // On windows, set in connectCallbacks() for technical reasons, and that seems sufficient. R_Interactive = (Rboolean) TRUE; #endif setup_Rmainloop (); #ifndef Q_WS_WIN // safety check: If we are beyond the stack boundaries already, we better disable stack checking // this has to come *after* the first setup_Rmainloop ()! Rboolean stack_ok = R_ToplevelExec (R_CheckStackWrapper, (void *) 0); if (!stack_ok) { RK_DEBUG (RBACKEND, DL_WARNING, "R_CheckStack() failed during initialization. Will disable stack checking and try to re-initialize."); RK_DEBUG (RBACKEND, DL_WARNING, "Whether or not things work after this, *please* submit a bug report."); R_CStackStart = (uintptr_t) -1; R_CStackLimit = (uintptr_t) -1; setup_Rmainloop (); } #endif #ifndef Q_WS_WIN // I am not sure, whether it is necessary to repeat this, here. It is not in R 3.0.0. // But historically, it was placed here (after setup_Rmainloop(), and conceivably there // was a reason to that (might have been reset in setup_Rmainloop() in earlier versions // of R. R_Interactive = (Rboolean) TRUE; #endif setlocale (LC_NUMERIC, "C"); // Under some conditions something appears to mess with the locale. R will not work correctly without LC_NUMERIC=C RBackendRequest req (false, RBackendRequest::SetParamsFromBackend); req.params["na_real"] = NA_REAL; // may not be initialized before setup_Rmainloop! req.params["na_int"] = NA_INTEGER; handleRequest (&req); RKWard_RData_Tag = Rf_install ("RKWard_RData_Tag"); RKSignalSupport::installSignalProxies (); // for the crash signals RKSignalSupport::installSigIntAndUsrHandlers (RK_scheduleIntr); // register our functions R_CallMethodDef callMethods [] = { { "rk.do.error", (DL_FUNC) &doError, 1 }, { "rk.do.command", (DL_FUNC) &doSubstackCall, 1 }, { "rk.do.generic.request", (DL_FUNC) &doPlainGenericRequest, 2 }, { "rk.get.structure", (DL_FUNC) &doGetStructure, 4 }, { "rk.get.structure.global", (DL_FUNC) &doGetGlobalEnvStructure, 3 }, { "rk.copy.no.eval", (DL_FUNC) &doCopyNoEval, 3 }, { "rk.edit.files", (DL_FUNC) &doEditFiles, 4 }, { "rk.show.files", (DL_FUNC) &doShowFiles, 5 }, { "rk.dialog", (DL_FUNC) &doDialog, 6 }, { "rk.update.locale", (DL_FUNC) &doUpdateLocale, 0 }, { "rk.locale.name", (DL_FUNC) &doLocaleName, 0 }, { "rk.graphics.device", (DL_FUNC) &RKStartGraphicsDevice, 7}, { "rk.graphics.device.resize", (DL_FUNC) &RKD_AdjustSize, 1}, { 0, 0, 0 } }; R_registerRoutines (R_getEmbeddingDllInfo(), NULL, callMethods, NULL, NULL); connectCallbacks(); RKInsertToplevelStatementFinishedCallback (0); RKREventLoop::setRKEventHandler (doPendingPriorityCommands); default_global_context = R_GlobalContext; // get info on R runtime version RCommandProxy *dummy = runDirectCommand ("as.numeric (R.version$major) * 1000 + as.numeric (R.version$minor) * 10", RCommand::GetIntVector); if ((dummy->getDataType () == RData::IntVector) && (dummy->getDataLength () == 1)) { r_version = dummy->intVector ().at (0); } else { RK_ASSERT (false); r_version = 0; } return true; } #ifndef Q_OS_WIN static bool backend_was_forked = false; void prepareFork () { RK_TRACE (RBACKEND); if (!RKRBackendProtocolBackend::inRThread ()) return; // we need to make sure that the transmitter thread does not hold a lock on the mutex! RKRBackend::this_pointer->stdout_stderr_mutex.lock (); } void completeForkMaster () { RK_TRACE (RBACKEND); if (!RKRBackendProtocolBackend::inRThread ()) return; RKRBackend::this_pointer->stdout_stderr_mutex.unlock (); if (backend_was_forked) return; backend_was_forked = true; // Block SIGCHLD in the main thread from now on. I don't fully understand, why, but otherwise, these signals // interrupt the select() call in the fork()ing code of library(parallel) sigset_t new_set; sigemptyset (&new_set); sigaddset (&new_set, SIGCHLD); pthread_sigmask (SIG_BLOCK, &new_set, NULL); // This was used to show a warning message. Unfortunately, however, forks also occur on every popen (i.e. in system(..., intern=TRUE). // RKRBackend::this_pointer->handlePlainGenericRequest (QStringList ("forkNotification"), false); RK_DEBUG (RBACKEND, DL_WARNING, "Backend process forked (for the first time, this session)"); // NOTE: perhaps we can heuristically differentiate from popen by checking sys.calls() for something with "fork" in it. // esp., in case we discover adverse side-effects of blocking SIGCHLD, we should attempt this } void completeForkChild () { RKRBackendProtocolBackend::instance ()->r_thread_id = QThread::currentThreadId(); RKRBackend::this_pointer->killed = RKRBackend::AlreadyDead; // Not quite accurate, but disables all communication with the frontend } #endif void RKRBackend::enterEventLoop () { RK_TRACE (RBACKEND); #ifndef Q_OS_WIN pthread_atfork (prepareFork, completeForkMaster, completeForkChild); #endif run_Rmainloop (); // NOTE: Do NOT run Rf_endEmbeddedR(). It does more that we want. We rely on RCleanup, instead. } struct SafeParseWrap { SEXP cv; SEXP pr; ParseStatus status; }; void safeParseVector (void *data) { SafeParseWrap *wrap = static_cast (data); wrap->pr = 0; // TODO: Maybe we can use R_ParseGeneral instead. Then we could find the exact character, where parsing fails. Nope: not exported API wrap->pr = R_ParseVector (wrap->cv, -1, &(wrap->status), R_NilValue); } SEXP parseCommand (const QString &command_qstring, RKRBackend::RKWardRError *error) { RK_TRACE (RBACKEND); SafeParseWrap wrap; wrap.status = PARSE_NULL; QByteArray localc = RKRBackend::this_pointer->current_locale_codec->fromUnicode (command_qstring); // needed so the string below does not go out of scope const char *command = localc.data (); PROTECT(wrap.cv=Rf_allocVector(STRSXP, 1)); SET_STRING_ELT(wrap.cv, 0, Rf_mkChar(command)); // Yes, if there is an error in the parse, R does jump back to toplevel! // trying to parse list(""=1) is an example in R 3.1.1 R_ToplevelExec (safeParseVector, &wrap); SEXP pr = wrap.pr; UNPROTECT(1); if ((!pr) || (TYPEOF (pr) == NILSXP)) { // got a null SEXP. This means parse was *not* ok, even if R_ParseVector told us otherwise if (wrap.status == PARSE_OK) { wrap.status = PARSE_ERROR; printf ("weird parse error\n"); } } if (wrap.status != PARSE_OK) { if ((wrap.status == PARSE_INCOMPLETE) || (wrap.status == PARSE_EOF)) { *error = RKRBackend::Incomplete; } else if (wrap.status == PARSE_ERROR) { //extern SEXP parseError (SEXP call, int linenum); //parseError (R_NilValue, 0); *error = RKRBackend::SyntaxError; } else { // PARSE_NULL *error = RKRBackend::OtherError; } pr = R_NilValue; } return pr; } SEXP runCommandInternalBase (SEXP pr, RKRBackend::RKWardRError *error) { RK_TRACE (RBACKEND); SEXP exp; int r_error = 0; exp=R_NilValue; if (TYPEOF(pr)==EXPRSXP && LENGTH(pr)>0) { int bi=0; while (bi= RCommand::GetIntVector) && (datatype <= RCommand::GetStructuredData)); RCommandProxy *c = new RCommandProxy (command, RCommand::App | RCommand::Sync | RCommand::Internal | datatype); runCommand (c); return c; } void setWarnOption (int level) { SEXP s, t; PROTECT (t = s = Rf_allocList (2)); SET_TYPEOF (s, LANGSXP); SETCAR (t, Rf_install ("options")); t = CDR (t); SETCAR (t, Rf_ScalarInteger (level)); SET_TAG (t, Rf_install ("warn")); // The above is rougly equivalent to parseCommand ("options(warn=" + QString::number (level) + ")", &error), but ~100 times faster RKRBackend::RKWardRError error; runCommandInternalBase (s, &error); UNPROTECT (1); } void RKRBackend::runCommand (RCommandProxy *command) { RK_TRACE (RBACKEND); RK_ASSERT (command); RKWardRError error = NoError; int ctype = command->type; // easier typing // running user commands is quite different from all other commands and should have been handled by RReadConsole RK_ASSERT (!(ctype & RCommand::User)); if (ctype & RCommand::CCCommand) printCommand (command->command); if (ctype & RCommand::CCOutput) startOutputCapture (); if (ctype & RCommand::QuitCommand) { R_dot_Last (); // should run while communication with frontend is still possible RBackendRequest req (true, RBackendRequest::BackendExit); req.params["regular"] = QVariant (true); handleRequest (&req); killed = ExitNow; } else if (!(ctype & RCommand::EmptyCommand)) { repl_status.eval_depth++; SEXP parsed = parseCommand (command->command, &error); if (error == NoError) { PROTECT (parsed); SEXP exp; #if R_VERSION >= R_Version(2,13,0) int warn_level = RKRSupport::SEXPToInt (Rf_GetOption1 (Rf_install ("warn")), 0); if (warn_level != 1) setWarnOption (1); #endif PROTECT (exp = runCommandInternalBase (parsed, &error)); #if R_VERSION >= R_Version(2,13,0) if (warn_level != 1) setWarnOption (warn_level); #endif if (error == NoError) { if (ctype & RCommand::GetStringVector) { command->setData (RKRSupport::SEXPToStringList (exp)); } else if (ctype & RCommand::GetRealVector) { command->setData (RKRSupport::SEXPToRealArray (exp)); } else if (ctype & RCommand::GetIntVector) { command->setData (RKRSupport::SEXPToIntArray (exp)); } else if (ctype & RCommand::GetStructuredData) { RData *dummy = RKRSupport::SEXPToRData (exp); command->swallowData (*dummy); delete dummy; } } UNPROTECT (2); // exp, parsed } repl_status.eval_depth--; } // common error/status handling if (error != NoError) { command->status |= RCommand::WasTried | RCommand::Failed; if (error == Incomplete) { command->status |= RCommand::ErrorIncomplete; } else if (error == SyntaxError) { command->status |= RCommand::ErrorSyntax; } else if (!(command->status & RCommand::Canceled)) { command->status |= RCommand::ErrorOther; } } else { command->status |= RCommand::WasTried; } } void RKRBackend::setPriorityCommand (RCommandProxy* command) { RK_TRACE (RBACKEND); QMutexLocker lock (&priority_command_mutex); RK_ASSERT (!(command && pending_priority_command)); // for the time being, we support only one priority command at a time pending_priority_command = command; RKREventLoop::wakeRKEventHandler (); } void doPendingPriorityCommands () { RK_TRACE (RBACKEND); if (RKRBackend::this_pointer->killed) return; RCommandProxy *command = RKRBackend::this_pointer->pending_priority_command; if (command) { RK_DEBUG (RBACKEND, DL_DEBUG, "running priority command %s", qPrintable (command->command)); RKRBackend::this_pointer->setPriorityCommand (0); { QMutexLocker lock (&RKRBackend::this_pointer->all_current_commands_mutex); RKRBackend::this_pointer->all_current_commands.append (command); RKRBackend::this_pointer->current_command = command; } RKRBackend::this_pointer->runCommand (command); RKRBackend::this_pointer->commandFinished (false); // TODO: Oh boy, what a mess. Sending notifications should be split from fetchNextCommand() (which is not appropriate, here) RBackendRequest req (false, RBackendRequest::CommandOut); // NOTE: We do *NOT* want a reply to this one, and in particular, we do *NOT* want to do // (recursive) event processing while handling this. req.command = command; RKRBackend::this_pointer->handleRequest (&req); } } // On Windows, using runDirectCommand (".rk.cat.output ...") is not safe during some places where we call this, e.g. in RBusy. // Not a problem on Linux with R 2.13.0, though void RKRBackend::catToOutputFile (const QString &out) { RK_TRACE (RBACKEND); if (output_file.isEmpty ()) { RK_ASSERT (false); return; } QFile f (output_file); if (!f.open (QIODevice::WriteOnly | QIODevice::Append)) { RK_ASSERT (false); return; } f.write (current_locale_codec->fromUnicode (out)); f.close (); } void RKRBackend::printCommand (const QString &command) { RK_TRACE (RBACKEND); QStringList params ("highlightRCode"); params.append (command); QString highlighted = handlePlainGenericRequest (params, true).value (0); catToOutputFile (highlighted); } void RKRBackend::startOutputCapture () { RK_TRACE (RBACKEND); // TODO: One of those days, we need to revisit output handling. This request could be perfectly async, but unfortunately, in that case, output chunks can sneak in front of it. handlePlainGenericRequest (QStringList ("recordOutput"), true); } void RKRBackend::printAndClearCapturedMessages (bool with_header) { RK_TRACE (RBACKEND); QStringList params ("recordOutput"); params.append ("end"); QString out = handlePlainGenericRequest (params, true).value (0); if (out.isEmpty ()) return; if (with_header) out.prepend ("

Messages, warnings, or errors:

\n"); catToOutputFile (out); } void RKRBackend::run (const QString &locale_dir) { RK_TRACE (RBACKEND); killed = NotKilled; previous_command = 0; initialize (QFile::encodeName (locale_dir)); enterEventLoop (); } void RKRBackend::commandFinished (bool check_object_updates_needed) { RK_TRACE (RBACKEND); RK_DEBUG (RBACKEND, DL_DEBUG, "done running command %s", qPrintable (current_command->command)); { QMutexLocker lock (&all_current_commands_mutex); too_late_to_interrupt = true; } clearPendingInterrupt (); // Mutex must be unlocked for this! if (current_command->type & RCommand::CCOutput) printAndClearCapturedMessages (current_command->type & RCommand::Plugin); current_command->status -= (current_command->status & RCommand::Running); current_command->status |= RCommand::WasTried; if (current_command->type & RCommand::User) { RK_ASSERT (repl_status.eval_depth == 0); // This method may look a bit over-complex, but remember that repl_status.user_command_successful_up_to works on an *encoded* buffer QByteArray remainder_encoded = repl_status.user_command_buffer.mid (repl_status.user_command_successful_up_to); QString remainder = current_locale_codec->toUnicode (remainder_encoded); current_command->has_been_run_up_to = current_command->command.length () - remainder.length (); } if (check_object_updates_needed || (current_command->type & RCommand::ObjectListUpdate)) { checkObjectUpdatesNeeded (current_command->type & (RCommand::User | RCommand::ObjectListUpdate)); } previous_command = current_command; { QMutexLocker lock (&all_current_commands_mutex); all_current_commands.pop_back(); if (!all_current_commands.isEmpty ()) current_command = all_current_commands.last (); too_late_to_interrupt = false; } } RCommandProxy* RKRBackend::handleRequest (RBackendRequest *request, bool mayHandleSubstack) { RK_TRACE (RBACKEND); RK_ASSERT (request); RKRBackendProtocolBackend::instance ()->sendRequest (request); if ((!request->synchronous) && (!isKilled ())) { RK_ASSERT (mayHandleSubstack); // i.e. not called from fetchNextCommand return 0; } int i = 0; while (!request->done) { if (killed) return 0; // NOTE: processX11Events() may, conceivably, lead to new requests, which may also wait for sub-commands! RKREventLoop::processX11Events (); // NOTE: sleeping and waking up again can be surprisingly CPU-intensive (yes: more than the event processing, above. I have profiled it). // However, we really don't want to introduce too much delay, either. // Thus, the logic is this: If there was no reply within 2 seconds, then probably we're waiting for a user event, and can afford some more // latency (not too much, though, as we still need to process events). if (!request->done) RKRBackendProtocolBackend::msleep (++i < 200 ? 10 : 50); } while (pending_priority_command) RKREventLoop::processX11Events (); // Probably not needed, but make sure to process priority commands first at all times. RCommandProxy* command = request->takeCommand (); if (!command) return 0; { QMutexLocker lock (&all_current_commands_mutex); all_current_commands.append (command); current_command = command; } if (!mayHandleSubstack) return command; while (command) { runCommand (command); commandFinished (false); command = fetchNextCommand (); }; { QMutexLocker lock (&all_current_commands_mutex); if (current_commands_to_cancel.contains (current_command)) { RK_DEBUG (RBACKEND, DL_DEBUG, "will now interrupt parent command"); current_commands_to_cancel.removeAll (current_command); scheduleInterrupt (); } } return 0; } RCommandProxy* RKRBackend::fetchNextCommand () { RK_TRACE (RBACKEND); RBackendRequest req (!isKilled (), RBackendRequest::CommandOut); // when killed, we do *not* actually wait for the reply, before the request is deleted. req.command = previous_command; previous_command = 0; return (handleRequest (&req, false)); } void RKRBackend::handleHistoricalSubstackRequest (const QStringList &list) { RK_TRACE (RBACKEND); RBackendRequest request (true, RBackendRequest::HistoricalSubstackRequest); request.params["call"] = list; handleRequest (&request); } QStringList RKRBackend::handlePlainGenericRequest (const QStringList ¶meters, bool synchronous) { RK_TRACE (RBACKEND); RBackendRequest request (synchronous, RBackendRequest::PlainGenericRequest); if (parameters.value (0) == "getSessionInfo") { QStringList dummy = parameters; dummy.append (RKRBackendProtocolBackend::backendDebugFile ()); // NOTE: R_SVN_REVISON used to be a string, but has changed to numeric constant in R 3.0.0. QString::arg() handles both. dummy.append (QString (R_MAJOR "." R_MINOR " " R_STATUS " (" R_YEAR "-" R_MONTH "-" R_DAY " r%1)").arg (R_SVN_REVISION)); request.params["call"] = dummy; } else if (parameters.value (0) == "set.output.file") { output_file = parameters.value (1); if (parameters.length () > 2) { RK_ASSERT (parameters.value (2) == "SILENT"); return QStringList (); // For automated testing. The frontend should not be notified, here } request.params["call"] = parameters; } else { request.params["call"] = parameters; } handleRequest (&request); return request.params.value ("return").toStringList (); } void RKRBackend::initialize (const char *locale_dir) { RK_TRACE (RBACKEND); // in RInterface::RInterface() we have created a fake RCommand to capture all the output/errors during startup. Fetch it repl_status.eval_depth++; fetchNextCommand (); startR (); bool lib_load_fail = false; bool sink_fail = false; if (!runDirectCommand ("library (\"rkward\")\n")) lib_load_fail = true; RK_setupGettext (locale_dir); // must happen *after* package loading, since R will re-set it if (!runDirectCommand (QString ("stopifnot(.rk.app.version==\"%1\")\n").arg (RKWARD_VERSION))) lib_load_fail = true; if (!runDirectCommand (".rk.fix.assignments ()\n")) sink_fail = true; // error/output sink and help browser if (!runDirectCommand ("options (error=quote (.rk.do.error ()))\n")) sink_fail = true; QString error_messages; if (lib_load_fail) { error_messages.append (i18n ("

\t- The 'rkward' R-library either could not be loaded at all, or not in the correct version. This may lead to all sorts of errors, from single missing features to complete failure to function. The most likely cause is that the last installation did not place all files in the correct place. However, in some cases, left-overs from a previous installation that was not cleanly removed may be the cause.

\

You should quit RKWard, now, and fix your installation. For help with that, see http://rkward.kde.org/compiling.

\n")); } if (sink_fail) { error_messages.append (i18n ("

\t-There was a problem setting up the communication with R. Most likely this indicates a broken installation.

\

You should quit RKWard, now, and fix your installation. For help with that, see http://rkward.kde.org/compiling.

\n")); } RBackendRequest req (true, RBackendRequest::Started); req.params["message"] = QVariant (error_messages); // blocks until RKWard is set to go (esp, it has displayed startup error messages, etc.) // in fact, a number of sub-commands are run while handling this request! handleRequest (&req); commandFinished (); // the fake startup command repl_status.eval_depth--; } void RKRBackend::checkObjectUpdatesNeeded (bool check_list) { RK_TRACE (RBACKEND); if (killed) return; /* NOTE: We're keeping separate lists of the items on the search path, and the toplevel symbols in .GlobalEnv here. This info is also present in RObjectList (and it's children). However: a) in a less convenient form, b) in the other thread. To avoid locking, and other complexity, keeping separate lists seems an ok solution. Keep in mind that only the names of only the toplevel objects are kept, here, so the memory overhead should be minimal */ bool search_update_needed = false; bool globalenv_update_needed = false; if (check_list) { // TODO: avoid parsing this over and over again RK_DEBUG (RBACKEND, DL_TRACE, "checkObjectUpdatesNeeded: getting search list"); RCommandProxy *dummy = runDirectCommand ("search ()\n", RCommand::GetStringVector); if (dummy->stringVector () != toplevel_env_names) search_update_needed = true; if (search_update_needed) toplevel_env_names = dummy->stringVector (); delete dummy; // TODO: avoid parsing this over and over again RK_DEBUG (RBACKEND, DL_TRACE, "checkObjectUpdatesNeeded: getting globalenv symbols"); dummy = runDirectCommand ("ls (globalenv (), all.names=TRUE)\n", RCommand::GetStringVector); QStringList new_globalenv_toplevel_names = dummy->stringVector (); if (new_globalenv_toplevel_names.count () != global_env_toplevel_names.count ()) { globalenv_update_needed = true; } else { for (int i = 0; i < new_globalenv_toplevel_names.count (); ++i) { // order is not important in the symbol list if (!global_env_toplevel_names.contains (new_globalenv_toplevel_names[i])) { globalenv_update_needed = true; break; } } } if (globalenv_update_needed) global_env_toplevel_names = new_globalenv_toplevel_names; delete dummy; if (search_update_needed) { // this includes an update of the globalenv, even if not needed QStringList call ("syncenvs"); call.append (QString::number (toplevel_env_names.size ())); call.append (toplevel_env_names); dummy = runDirectCommand ("loadedNamespaces ()\n", RCommand::GetStringVector); call.append (dummy->stringVector ()); delete dummy; handleHistoricalSubstackRequest (call); } if (globalenv_update_needed) { QStringList call = global_env_toplevel_names; call.prepend ("syncglobal"); // should be faster than the reverse handleHistoricalSubstackRequest (call); } } if (search_update_needed || globalenv_update_needed) { RK_DEBUG (RBACKEND, DL_TRACE, "checkObjectUpdatesNeeded: updating watches"); runDirectCommand (".rk.watch.globalenv ()\n"); } if (!changed_symbol_names.isEmpty ()) { QStringList call = changed_symbol_names; call.prepend (QString ("sync")); // should be faster than reverse handleHistoricalSubstackRequest (call); changed_symbol_names.clear (); } } bool RKRBackend::doMSleep (int msecs) { // do not trace! if (isKilled ()) return false; RKRBackendProtocolBackend::msleep (msecs); return true; } rkward-0.6.4/rkward/rbackend/rkstructuregetter.cpp0000664000175000017500000004176512633754364021776 0ustar thomasthomas/*************************************************************************** rkstructuregetter - description ------------------- begin : Wed Apr 11 2007 copyright : (C) 2007, 2009, 2010, 2011 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkstructuregetter.h" #include "rdata.h" #include "rkrsupport.h" #include "rkrbackend.h" #include "rkrbackendprotocol_shared.h" #include "../core/robject.h" #include "../debug.h" #define NAMED_CHILDREN_LIMIT 100000 RKStructureGetter::RKStructureGetter (bool keep_evalled_promises) { RK_TRACE (RBACKEND); RKStructureGetter::keep_evalled_promises = keep_evalled_promises; num_prefetched_funs = 0; meta_attrib = Rf_install (".rk.meta"); PROTECT (meta_attrib); RK_ASSERT (!Rf_isNull (meta_attrib)); class_fun = prefetch_fun ("class"); get_meta_fun = prefetch_fun (".rk.get.meta", false); // Why do we need all these? Because the is.xxx functions may do an internal dispatch, that we do not want to miss, but don't easily get by e.g. calling Rf_isFunction() directly. is_matrix_fun = prefetch_fun ("is.matrix"); is_array_fun = prefetch_fun ("is.array"); is_list_fun = prefetch_fun ("is.list"); is_function_fun = prefetch_fun ("is.function"); is_environment_fun = prefetch_fun ("is.environment"); as_environment_fun = prefetch_fun ("as.environment"); is_factor_fun = prefetch_fun ("is.factor"); is_numeric_fun = prefetch_fun ("is.numeric"); is_character_fun = prefetch_fun ("is.character"); is_logical_fun = prefetch_fun ("is.logical"); double_brackets_fun = prefetch_fun ("[["); dims_fun = prefetch_fun ("dim"); names_fun = prefetch_fun ("names"); length_fun = prefetch_fun ("length"); args_fun = prefetch_fun ("args"); rk_get_slots_fun = prefetch_fun (".rk.get.slots", false); } RKStructureGetter::~RKStructureGetter () { RK_TRACE (RBACKEND); UNPROTECT (num_prefetched_funs + 1); /* all the pre-resolved functions and the meta attribute */ } SEXP RKStructureGetter::prefetch_fun (const char *name, bool from_base) { SEXP ret; if (from_base) { ret = Rf_install (name); } else { ret = Rf_findFun (Rf_install (name), R_GlobalEnv); } PROTECT (ret); RK_ASSERT (!Rf_isNull (ret)); ++num_prefetched_funs; return (ret); } RData *RKStructureGetter::getStructure (SEXP toplevel, SEXP name, SEXP envlevel, SEXP namespacename) { RK_TRACE (RBACKEND); QString name_string = RKRSupport::SEXPToString (name); // resolve namespace, if needed if (Rf_isNull (namespacename)) { with_namespace = false; } else { SEXP as_ns_fun = Rf_findFun (Rf_install (".rk.try.get.namespace"), R_GlobalEnv); PROTECT (as_ns_fun); RK_ASSERT (!Rf_isNull (as_ns_fun)); namespace_envir = RKRSupport::callSimpleFun (as_ns_fun, namespacename, R_GlobalEnv); with_namespace = !Rf_isNull (namespace_envir); UNPROTECT (1); /* as_ns_fun */ } if (with_namespace) PROTECT (namespace_envir); RData *ret = new RData; toplevel_value = toplevel; getStructureSafe (toplevel, name_string, 0, ret, INTEGER (envlevel)[0]); if (with_namespace) UNPROTECT (1); /* namespace_envir */ return ret; } void RKStructureGetter::getStructureSafe (SEXP value, const QString &name, int add_type_flags, RData *storage, int nesting_depth) { RK_TRACE (RBACKEND); GetStructureWorkerArgs args; args.toplevel = value; args.name = name; args.add_type_flags = add_type_flags; args.storage = storage; args.getter = this; args.nesting_depth = nesting_depth; Rboolean ok = R_ToplevelExec ((void (*)(void*)) getStructureWrapper, &args); if (ok != TRUE) { storage->discardData(); Rf_warning ("failure to get object %s", name.toLatin1().data ()); getStructureWorker (R_NilValue, name, add_type_flags, storage, nesting_depth); } } void RKStructureGetter::getStructureWrapper (GetStructureWorkerArgs *data) { RK_TRACE (RBACKEND); data->getter->getStructureWorker (data->toplevel, data->name, data->add_type_flags, data->storage, data->nesting_depth); } SEXP RKStructureGetter::resolvePromise (SEXP from) { RK_TRACE (RBACKEND); SEXP ret = from; if (TYPEOF (from) == PROMSXP) { ret = PRVALUE(from); if (ret == R_UnboundValue) { RK_DEBUG (RBACKEND, DL_DEBUG, "temporarily resolving unbound promise"); PROTECT (from); SET_PRSEEN(from, 1); ret = Rf_eval(PRCODE(from), PRENV(from)); SET_PRSEEN(from, 0); if (keep_evalled_promises) { SET_PRVALUE(from, ret); SET_PRENV(from, R_NilValue); } UNPROTECT (1); RK_DEBUG (RBACKEND, DL_DEBUG, "resolved type is %d", TYPEOF (ret)); } } return ret; } extern "C" { // TODO: split out some of the large blocks into helper functions, to make this easier to read void RKStructureGetter::getStructureWorker (SEXP val, const QString &name, int add_type_flags, RData *storage, int nesting_depth) { RK_TRACE (RBACKEND); bool at_toplevel = (toplevel_value == val); bool is_function = false; bool is_container = false; bool is_environment = false; bool no_recurse = (nesting_depth >= 2); // TODO: should be configurable unsigned int type = 0; RK_DEBUG (RBACKEND, DL_DEBUG, "fetching '%s': %p, s-type %d", name.toLatin1().data(), val, TYPEOF (val)); SEXP value = val; PROTECT_INDEX value_index; PROTECT_WITH_INDEX (value, &value_index); // manually resolve any promises REPROTECT (value = resolvePromise (value), value_index); bool is_s4 = Rf_isS4 (value); SEXP baseenv = R_BaseEnv; if (is_s4) baseenv = R_GlobalEnv; // first field: get name RData *namedata = new RData; namedata->setData (QStringList (name)); // get classes SEXP classes_s; QStringList classes; if ((TYPEOF (value) == LANGSXP) || (TYPEOF (value) == SYMSXP)) { // if it's a call, we should NEVER send it through eval // stripped down and adjusted from R_data_class PROTECT (classes_s = Rf_getAttrib (value, R_ClassSymbol)); if (Rf_length (classes_s)) classes = RKRSupport::SEXPToStringList(classes_s); UNPROTECT (1); if (classes.isEmpty ()) { if (TYPEOF (value) == LANGSXP) { SEXP symb = PROTECT (CAR (value)); QString cl; if (TYPEOF (symb) == SYMSXP) cl = CHAR (PRINTNAME (symb)); UNPROTECT (1); if ((cl != "if") && (cl != "while") && (cl != "for") && (cl != "=") && (cl != "<-") && (cl != "(") && (cl != "{")) cl = "call"; classes = QStringList (cl); } else { classes = QStringList ("name"); } } REPROTECT (value = Rf_coerceVector (value, EXPRSXP), value_index); // make sure the object is safe for everything to come } else { PROTECT (classes_s = RKRSupport::callSimpleFun (class_fun, value, baseenv)); classes = RKRSupport::SEXPToStringList (classes_s); UNPROTECT (1); } // store classes RData *classdata = new RData; classdata->setData (classes); // basic classification for (int i = classes.size () - 1; i >= 0; --i) { #ifdef __GNUC__ # warning: Using is.data.frame() may be more reliable (would need to be called only on List-objects, thus no major performance hit) #endif if (classes[i] == "data.frame") type |= RObject::DataFrame; } if (RKRSupport::callSimpleBool (is_matrix_fun, value, baseenv)) type |= RObject::Matrix; if (RKRSupport::callSimpleBool (is_list_fun, value, baseenv)) type |= RObject::List; if (type != 0) { is_container = true; type |= RObject::Container; } else { if (RKRSupport::callSimpleBool (is_function_fun, value, baseenv)) { is_function = true; type |= RObject::Function; } else if (RKRSupport::callSimpleBool (is_environment_fun, value, baseenv)) { is_container = true; type |= RObject::Environment; is_environment = true; } else { type |= RObject::Variable; if (RKRSupport::callSimpleBool (is_factor_fun, value, baseenv)) type |= RObject::Factor; else if (RKRSupport::callSimpleBool (is_numeric_fun, value, baseenv)) type |= RObject::Numeric; else if (RKRSupport::callSimpleBool (is_character_fun, value, baseenv)) type |= RObject::Character; else if (RKRSupport::callSimpleBool (is_logical_fun, value, baseenv)) type |= RObject::Logical; if (RKRSupport::callSimpleBool (is_array_fun, value, baseenv)) type |= RObject::Array; } } type |= add_type_flags; if (is_container) { if (no_recurse) { type |= RObject::Incomplete; RK_DEBUG (RBACKEND, DL_DEBUG, "Depth limit reached. Will not recurse into %s", name.toLatin1().data ()); } } // get meta data, if any RData *metadata = new RData; if (!Rf_isNull (Rf_getAttrib (value, meta_attrib))) { SEXP meta_s = RKRSupport::callSimpleFun (get_meta_fun, value, R_GlobalEnv); PROTECT (meta_s); metadata->setData (RKRSupport::SEXPToStringList (meta_s)); UNPROTECT (1); /* meta_s */ } else { metadata->setData (QStringList ()); } // get dims RData::IntStorage dims; SEXP dims_s = RKRSupport::callSimpleFun (dims_fun, value, baseenv); if (!Rf_isNull (dims_s)) { dims = RKRSupport::SEXPToIntArray (dims_s); } else { unsigned int len = Rf_length (value); if ((len < 2) && (!is_function)) { // suspicious. Maybe some kind of list SEXP len_s = RKRSupport::callSimpleFun (length_fun, value, baseenv); PROTECT (len_s); if (Rf_isNull (len_s)) { dims.append (len); } else { dims = RKRSupport::SEXPToIntArray (len_s); } UNPROTECT (1); /* len_s */ } else { dims.append (len); } } // store dims RData *dimdata = new RData; dimdata->setData (dims); RData *slotsdata = new RData (); // does it have slots? if (is_s4) { type |= RObject::S4Object; if (no_recurse) { type |= RObject::Incomplete; RK_DEBUG (RBACKEND, DL_DEBUG, "Depth limit reached. Will not recurse into slots of %s", name.toLatin1().data ()); } else { RData::RDataStorage dummy (1, 0); dummy[0] = new RData (); SEXP slots_pseudo_object = RKRSupport::callSimpleFun (rk_get_slots_fun, value, R_GlobalEnv); PROTECT (slots_pseudo_object); getStructureSafe (slots_pseudo_object, "SLOTS", RObject::PseudoObject, dummy[0], nesting_depth); // do not increase depth for this pseudo-object UNPROTECT (1); slotsdata->setData (dummy); } } // store type RData *typedata = new RData; typedata->setData (RData::IntStorage (1, type)); // store everything we have so far int storage_length = RObject::StorageSizeBasicInfo; if (is_container) { storage_length = RObject::StorageSizeBasicInfo + 1; } else if (is_function) { storage_length = RObject::StorageSizeBasicInfo + 2; } RData::RDataStorage res (storage_length, 0); res[RObject::StoragePositionName] = namedata; res[RObject::StoragePositionType] = typedata; res[RObject::StoragePositionClass] = classdata; res[RObject::StoragePositionMeta] = metadata; res[RObject::StoragePositionDims] = dimdata; res[RObject::StoragePositionSlots] = slotsdata; // now add the extra info for containers and functions if (is_container) { bool do_env = (is_environment && (!no_recurse)); bool do_cont = is_container && (!is_environment) && (!no_recurse); // fetch list of child names SEXP childnames_s; if (do_env) { childnames_s = R_lsInternal (value, (Rboolean) 1); } else if (do_cont) { childnames_s = RKRSupport::callSimpleFun (names_fun, value, baseenv); } else { childnames_s = R_NilValue; // dummy } PROTECT (childnames_s); QStringList childnames = RKRSupport::SEXPToStringList (childnames_s); int childcount = childnames.size (); if (childcount > NAMED_CHILDREN_LIMIT) { RK_DEBUG (RBACKEND, DL_WARNING, "object %s has %d named children. Will only retrieve the first %d", name.toLatin1().data (), childcount, NAMED_CHILDREN_LIMIT); childcount = NAMED_CHILDREN_LIMIT; } RData::RDataStorage children (childcount, 0); for (int i = 0; i < childcount; ++i) { children[i] = new RData (); // NOTE: RData-ctor pre-initializes these to empty. Thus, we're safe even if there is an error while fetching one of the children. } if (do_env) { RK_DEBUG (RBACKEND, DL_DEBUG, "recurse into environment %s", name.toLatin1().data ()); if (!Rf_isEnvironment (value)) { // some classes (ReferenceClasses) are identified as envionments by is.environment(), but are not internally ENVSXPs. // For these, Rf_findVar would fail. REPROTECT (value = RKRSupport::callSimpleFun (as_environment_fun, value, R_GlobalEnv), value_index); } for (int i = 0; i < childcount; ++i) { SEXP current_childname = Rf_install(CHAR(STRING_ELT(childnames_s, i))); // ??? Why does simply using STRING_ELT(childnames_i, i) crash? PROTECT (current_childname); SEXP child = Rf_findVar (current_childname, value); PROTECT (child); bool child_misplaced = false; if (at_toplevel && with_namespace && (!RKRBackend::this_pointer->RRuntimeIsVersion (2, 14, 0))) { if (!Rf_isNull (namespace_envir)) { SEXP dummy = Rf_findVarInFrame (namespace_envir, current_childname); if (Rf_isNull (dummy) || (dummy == R_UnboundValue)) child_misplaced = true; } } getStructureSafe (child, childnames[i], child_misplaced ? RObject::Misplaced : 0, children[i], nesting_depth + 1); UNPROTECT (2); /* current_childname, child */ } } else if (do_cont) { RK_DEBUG (RBACKEND, DL_DEBUG, "recurse into list %s", name.toLatin1().data ()); // fewer elements than names() can happen, although I doubt it is supposed to happen. // see http://sourceforge.net/p/rkward/bugs/67/ bool may_be_special = Rf_length (value) < childcount; if (Rf_isList (value) && (!may_be_special)) { // old style list for (int i = 0; i < childcount; ++i) { SEXP child = CAR (value); getStructureSafe (child, childnames[i], 0, children[i], nesting_depth + 1); CDR (value); } } else if (Rf_isNewList (value) && (!may_be_special)) { // new style list for (int i = 0; i < childcount; ++i) { SEXP child = VECTOR_ELT(value, i); getStructureSafe (child, childnames[i], 0, children[i], nesting_depth + 1); } } else { // probably an S4 object disguised as a list SEXP index = Rf_allocVector(INTSXP, 1); PROTECT (index); for (int i = 0; i < childcount; ++i) { INTEGER (index)[0] = (i + 1); SEXP child = RKRSupport::callSimpleFun2 (double_brackets_fun, value, index, baseenv); getStructureSafe (child, childnames[i], 0, children[i], nesting_depth + 1); } UNPROTECT (1); /* index */ } } UNPROTECT (1); /* childnames_s */ RData *childdata = new RData; childdata->setData (children); res[RObject::StoragePositionChildren] = childdata; if (is_environment && at_toplevel && with_namespace) { RData *namespacedata = new RData; if (no_recurse) { type |= RObject::Incomplete; RK_DEBUG (RBACKEND, DL_DEBUG, "Depth limit reached. Will not recurse into namespace of %s", name.toLatin1().data ()); } else { RData::RDataStorage dummy (1, 0); dummy[0] = new RData (); getStructureSafe (namespace_envir, "NAMESPACE", RObject::PseudoObject, dummy[0], nesting_depth+99); // HACK: By default, do not recurse into the children of the namespace, until dealing with the namespace object itself. namespacedata->setData (dummy); } res.insert (RObject::StoragePositionNamespace, namespacedata); } } else if (is_function) { // TODO: getting the formals is still a bit of a bottleneck, but no idea, how to improve on this, any further SEXP formals_s; if (Rf_isPrimitive (value)) formals_s = FORMALS (RKRSupport::callSimpleFun (args_fun, value, baseenv)); // primitives don't have formals, internally else formals_s = FORMALS (value); PROTECT (formals_s); // get the default values QStringList formals = RKRSupport::SEXPToStringList (formals_s); // for the most part, the implicit as.character in SEXPToStringList does a good on the formals (and it's the fastest of many options that I have tried). // Only for naked strings (as in 'function (a="something")'), we're missing the quotes. So we add quotes, after conversion, as needed: SEXP dummy = formals_s; const int formals_len = Rf_length (formals_s); for (int i = 0; i < formals_len; ++i) { if (TYPEOF (CAR (dummy)) == STRSXP) formals[i] = RKRSharedFunctionality::quote (formals[i]); dummy = CDR (dummy); } RData *funargvaluesdata = new RData; funargvaluesdata->setData (formals); // the argument names SEXP names_s = Rf_getAttrib (formals_s, R_NamesSymbol); PROTECT (names_s); RData *funargsdata = new RData; funargsdata->setData (RKRSupport::SEXPToStringList (names_s)); UNPROTECT (2); /* names_s, formals_s */ res[RObject::StoragePositionFunArgs] = funargsdata; res[RObject::StoragePositionFunValues] = funargvaluesdata; } UNPROTECT (1); /* value */ RK_ASSERT (!res.contains (0)); storage->setData (res); } } /* extern "C" */ rkward-0.6.4/rkward/rbackend/rkfrontendtransmitter.cpp0000664000175000017500000001746612633754364022640 0ustar thomasthomas/*************************************************************************** rkfrontendtransmitter - description ------------------- begin : Thu Nov 04 2010 copyright : (C) 2010-2014 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkfrontendtransmitter.h" #include "rkrbackendprotocol_frontend.h" #include "rkwarddevice/rkgraphicsdevice_frontendtransmitter.h" #include "../misc/rkcommonfunctions.h" #include "../settings/rksettingsmodulegeneral.h" #include "../rkglobals.h" #include #include #include #include #include #include #include #include #include "../version.h" #include "../debug.h" QString findBackendAtPath (const QString &path) { QDir dir (path); dir.makeAbsolute (); #ifdef Q_WS_WIN QString ret = dir.filePath ("rkward.rbackend.exe"); #else QString ret = dir.filePath ("rkward.rbackend"); #endif RK_DEBUG (RBACKEND, DL_DEBUG, "Looking for backend at %s", qPrintable (ret)); QFileInfo fi (ret); if (fi.exists () && fi.isExecutable ()) return ret; return QString (); } RKFrontendTransmitter::RKFrontendTransmitter () : RKAbstractTransmitter () { RK_TRACE (RBACKEND); rkd_transmitter = new RKGraphicsDeviceFrontendTransmitter (); start (); } RKFrontendTransmitter::~RKFrontendTransmitter () { RK_TRACE (RBACKEND); delete rkd_transmitter; RK_ASSERT (!server->isListening ()); } void RKFrontendTransmitter::run () { RK_TRACE (RBACKEND); // start server server = new QLocalServer (this); // we add a bit of randomness to the servername, as in general the servername must be unique // there could be conflicts with concurrent or with previous crashed rkward sessions. if (!server->listen ("rkward" + KRandom::randomString (8))) handleTransmissionError ("Failure to start frontend server: " + server->errorString ()); connect (server, SIGNAL (newConnection()), this, SLOT (connectAndEnterLoop()), Qt::QueuedConnection); // start backend backend = new QProcess (this); // Try to synchronize language selection in frontend and backend QStringList env = QProcess::systemEnvironment (); int index = env.indexOf (QRegExp("^LANGUAGE=.*", Qt::CaseInsensitive)); if (index >= 0) env.removeAt (index); env.append ("LANGUAGE=" + KGlobal::locale ()->language ()); backend->setEnvironment (env); QStringList args; args.append ("--debug-level=" + QString::number (RK_Debug_Level)); args.append ("--server-name=" + server->fullServerName ()); args.append ("--rkd-server-name=" + rkd_transmitter->serverName ()); args.append ("--data-dir=" + RKSettingsModuleGeneral::filesPath ()); args.append ("--locale-dir=" + KGlobal::dirs()->findResourceDir ("locale", KGlobal::locale ()->language () + "/LC_MESSAGES/rkward.mo")); connect (backend, SIGNAL (finished(int,QProcess::ExitStatus)), this, SLOT (backendExit(int))); QString backend_executable = findBackendAtPath (QCoreApplication::applicationDirPath ()); if (backend_executable.isEmpty ()) backend_executable = findBackendAtPath (QCoreApplication::applicationDirPath () + "/rbackend"); // for running directly from the build-dir #ifdef Q_WS_MAC if (backend_executable.isEmpty ()) backend_executable = findBackendAtPath (QCoreApplication::applicationDirPath () + "/../../../rbackend"); #endif if (backend_executable.isEmpty ()) handleTransmissionError (i18n ("The backend executable could not be found. This is likely to be a problem with your installation.")); QString debugger = RKGlobals::startup_options["backend-debugger"].toString (); if (!debugger.isEmpty ()) { args.prepend (backend_executable); QStringList l = debugger.split (' '); args = l.mid (1) + args; backend->start (l.first (), args, QIODevice::ReadOnly); } else { backend->start (backend_executable, args, QIODevice::ReadOnly); } if (!backend->waitForStarted ()) { handleTransmissionError (i18n ("The backend executable could not be started. Error message was: %1").arg (backend->errorString ())); } // fetch security token if (!backend->canReadLine ()) backend->waitForReadyRead (); token = QString::fromLocal8Bit (backend->readLine ()).trimmed (); backend->closeReadChannel (QProcess::StandardError); backend->closeReadChannel (QProcess::StandardOutput); exec (); if (!connection) { RK_ASSERT (false); return; } } void RKFrontendTransmitter::connectAndEnterLoop () { RK_TRACE (RBACKEND); RK_ASSERT (server->hasPendingConnections ()); QLocalSocket *con = server->nextPendingConnection (); server->close (); // handshake if (!con->canReadLine ()) con->waitForReadyRead (1000); QString token_c = QString::fromLocal8Bit (con->readLine ()); token_c.chop (1); if (token_c != token) handleTransmissionError (i18n ("Error during handshake with backend process. Expected token '%1', received token '%2'").arg (token).arg (token_c)); if (!con->canReadLine ()) con->waitForReadyRead (1000); QString version_c = QString::fromLocal8Bit (con->readLine ()); version_c.chop (1); if (version_c != RKWARD_VERSION) handleTransmissionError (i18n ("Version mismatch during handshake with backend process. Frontend is version '%1' while backend is '%2'.\nPlease fix your installation.").arg (RKWARD_VERSION).arg (version_c)); setConnection (con); } void RKFrontendTransmitter::requestReceived (RBackendRequest* request) { RK_TRACE (RBACKEND); if (request->type == RBackendRequest::Output) { ROutputList* list = request->output; for (int i = 0; i < list->size (); ++i) { ROutput *out = (*list)[i]; if (handleOutput (out->output, out->output.length (), out->type)) { RKRBackendEvent* event = new RKRBackendEvent (new RBackendRequest (false, RBackendRequest::OutputStartedNotification)); qApp->postEvent (RKRBackendProtocolFrontend::instance (), event); } delete (out); } delete list; request->output = 0; RK_ASSERT (request->synchronous); writeRequest (request); // to tell the backend, that we are keeping up. Also deletes the request. return; } RKRBackendEvent* event = new RKRBackendEvent (request); qApp->postEvent (RKRBackendProtocolFrontend::instance (), event); } void RKFrontendTransmitter::backendExit (int exitcode) { RK_TRACE (RBACKEND); if (!exitcode && token.isEmpty ()) handleTransmissionError (i18n ("The backend process could not be started. Please check your installation.")); else if (token.isEmpty ()) handleTransmissionError (i18n ("The backend process failed to start with exit code %1.", exitcode)); else handleTransmissionError (i18n ("Backend process has exited with code %1.", exitcode)); } void RKFrontendTransmitter::writeRequest (RBackendRequest *request) { RK_TRACE (RBACKEND); transmitRequest (request); connection->flush (); delete request; } void RKFrontendTransmitter::handleTransmissionError (const QString &message) { RK_TRACE (RBACKEND); connection->close (); RBackendRequest* req = new RBackendRequest (false, RBackendRequest::BackendExit); req->params["message"] = message; RKRBackendEvent* event = new RKRBackendEvent (req); qApp->postEvent (RKRBackendProtocolFrontend::instance (), event); exit (); } #include "rkfrontendtransmitter.moc" rkward-0.6.4/rkward/rbackend/rcommandreceiver.cpp0000664000175000017500000000426412633754364021504 0ustar thomasthomas/*************************************************************************** rcommandreceiver - description ------------------- begin : Thu Aug 19 2004 copyright : (C) 2004, 2006, 2007, 2010 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rcommandreceiver.h" #include "../rkglobals.h" #include "rinterface.h" #include "../debug.h" RCommandReceiver::RCommandReceiver () { RK_TRACE (RBACKEND); delete_when_done = false; } RCommandReceiver::~RCommandReceiver () { RK_TRACE (RBACKEND); for (RCommandList::const_iterator it = outstanding_commands.constBegin (); it != outstanding_commands.constEnd (); ++it) { (*it)->removeReceiver (this); } } void RCommandReceiver::cancelOutstandingCommands () { RK_TRACE (RBACKEND); for (RCommandList::const_iterator it = outstanding_commands.constBegin (); it != outstanding_commands.constEnd (); ++it) { RKGlobals::rInterface()->cancelCommand (*it); } } void RCommandReceiver::addCommand (RCommand *command) { RK_TRACE (RBACKEND); outstanding_commands.append (command); } void RCommandReceiver::delCommand (RCommand *command) { RK_TRACE (RBACKEND); outstanding_commands.removeAll (command); if (delete_when_done && outstanding_commands.isEmpty ()) delete this; } void RCommandReceiver::autoDeleteWhenDone () { RK_TRACE (RBACKEND); if (outstanding_commands.isEmpty ()) { delete this; return; } delete_when_done = true; } rkward-0.6.4/rkward/rbackend/rdata.cpp0000664000175000017500000001102612633754364017244 0ustar thomasthomas/*************************************************************************** rdata - description ------------------- begin : Sun Oct 01 2006 copyright : (C) 2006, 2010 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rdata.h" #include #include "../debug.h" RData::RData () { RK_TRACE (RBACKEND); datatype = NoData; data = 0; } RData::~RData () { RK_TRACE (RBACKEND); discardData (); } void RData::doAssert(RData::RDataType requested_type) const { if (this == 0) { RK_DEBUG (RBACKEND, DL_ERROR, "Requested data from a NULL RData"); } else { RK_DEBUG (RBACKEND, DL_ERROR, "Reqeusted data of type %d, while %p has type %d", requested_type, this, datatype); } } void RData::discardData () { RK_TRACE (RBACKEND); if (datatype == StructureVector) { RDataStorage sdata = *(static_cast (data)); for (int i=sdata.size ()-1; i >= 0; --i) { delete (sdata[i]); } delete (static_cast (data)); } else if (datatype == IntVector) { delete (static_cast (data)); } else if (datatype == RealVector) { delete (static_cast (data)); } else if (datatype == StringVector) { delete (static_cast (data)); } else { RK_ASSERT (datatype == NoData); } data = 0; datatype = RData::NoData; } unsigned int RData::getDataLength() const { if (!this) return 0; if (datatype == RealVector) return (static_cast (data)->size ()); if (datatype == IntVector) return (static_cast (data)->size ()); if (datatype == StringVector) return (static_cast (data)->size ()); if (datatype == StructureVector) return (static_cast (data)->size ()); return 0; } void RData::swallowData (RData &from) { data = from.data; datatype = from.datatype; from.data = 0; from.datatype = RData::NoData; } void RData::setData (const RDataStorage &from) { data = new RDataStorage (from); datatype = RData::StructureVector; } void RData::setData (const IntStorage &from) { data = new IntStorage (from); datatype = RData::IntVector; } void RData::setData (const RealStorage &from) { data = new RealStorage (from); datatype = RData::RealVector; } void RData::setData (const StringStorage &from) { data = new StringStorage (from); datatype = RData::StringVector; } void RData::printStructure (const QString &prefix) { if (datatype == NoData) { qDebug ("%s: NoData, length %d", prefix.toLatin1().data(), getDataLength ()); } else if (datatype == IntVector) { qDebug ("%s: IntVector, length %d", prefix.toLatin1().data(), getDataLength ()); IntStorage data = intVector (); for (int i = 0; i < data.size (); ++i) { qDebug ("%s%d: %d", prefix.toLatin1().data(), i, data.at (i)); } } else if (datatype == RealVector) { qDebug ("%s: RealVector, length %d", prefix.toLatin1().data(), getDataLength ()); RealStorage data = realVector (); for (int i = 0; i < data.size (); ++i) { qDebug ("%s%d: %f", prefix.toLatin1().data(), i, data.at (i)); } } else if (datatype == StringVector) { qDebug ("%s: StringVector, length %d", prefix.toLatin1().data(), getDataLength ()); StringStorage data = stringVector (); for (int i = 0; i < data.size (); ++i) { qDebug ("%s%d: %s", prefix.toLatin1().data(), i, qPrintable (data.at (i))); } } else if (datatype == StructureVector) { qDebug ("%s: StructureVector, length %d", prefix.toLatin1().data(), getDataLength ()); RDataStorage data = structureVector (); for (int i = 0; i < data.size (); ++i) { QString sub_prefix = prefix + QString::number (i); data.at (i)->printStructure (sub_prefix); } } else { qDebug ("%s: INVALID %d, length %d", prefix.toLatin1().data(), datatype, getDataLength ()); } qDebug ("%s: END\n\n", prefix.toLatin1 ().data()); } rkward-0.6.4/rkward/rkwardapplication.cpp0000664000175000017500000001473512633754364020130 0ustar thomasthomas/*************************************************************************** rkwardapplication - description ------------------- begin : Sun Nov 26 2006 copyright : (C) 2006, 2007, 2010 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkwardapplication.h" #include "windows/rkmdiwindow.h" #ifdef Q_WS_WIN # include #elif defined Q_WS_MAC # ifdef __GNUC__ # warning "Graph window capturing is not supported on Mac OS native, yet. Consider using X11." # endif #else # include # include # include # include # include //static Atom wm_name_property; #endif //Q_WS_WIN #include #include "debug.h" #ifdef __GNUC__ # warning TODO: We could really use the detection logic from windows for x11, too. It seems much easier. #endif #ifdef Q_WS_WIN #include namespace RKWardApplicationPrivate { QList toplevel_windows; BOOL CALLBACK EnumWindowsCallback (HWND hwnd, LPARAM) { toplevel_windows.append (hwnd); return true; } void updateToplevelWindowList () { RK_TRACE (APP); toplevel_windows.clear (); EnumWindows (EnumWindowsCallback, 0); }; } #endif //Q_WS_WIN //static RKWardApplication *RKWardApplication::rkapp = 0; RKWardApplication::RKWardApplication () : KApplication () { //RK_TRACE (APP); // would be called before initialization of debug-level RK_ASSERT (!rkapp); // Don't complain when linking rkward://-pages from Rd pages KAuthorized::allowUrlAction ("redirect", KUrl ("http://"), KUrl ("rkward://")); // Don't complain when trying to open help pages KAuthorized::allowUrlAction ("redirect", KUrl ("rkward://"), KUrl ("help:")); rkapp = this; detect_x11_creations = false; #ifdef Q_WS_X11 wm_name_property = XInternAtom (QX11Info::display (), "WM_NAME", true); #endif //Q_WS_X11 } RKWardApplication::~RKWardApplication () { RK_TRACE (APP); } RKWardApplication *RKWardApplication::getApp () { RK_TRACE (APP); RK_ASSERT (rkapp == KApplication::kApplication ()); return rkapp; } void RKWardApplication::startWindowCreationDetection () { RK_TRACE (APP); RK_ASSERT (!detect_x11_creations); created_window = 0; detect_x11_creations = true; #ifdef Q_WS_WIN RKWardApplicationPrivate::updateToplevelWindowList (); #elif defined Q_WS_X11 XSelectInput (QX11Info::display (), QX11Info::appRootWindow (), SubstructureNotifyMask); syncX (); // this is to make sure we don't miss out on the window creation (if it happens very early). Testing shows, we really need this. #endif } WId RKWardApplication::endWindowCreationDetection () { RK_TRACE (APP); RK_ASSERT (detect_x11_creations); #ifdef Q_WS_WIN QList old_windows = RKWardApplicationPrivate::toplevel_windows; RKWardApplicationPrivate::updateToplevelWindowList (); QList candidate_windows = RKWardApplicationPrivate::toplevel_windows; detect_x11_creations = false; // remove all windows that existed before the call to startWindowCreationDetection for (int i = 0; i < old_windows.size (); ++i) { candidate_windows.removeAll (old_windows[i]); } // ideally we have a single candidate remaining, now, but sometimes, additional // invisible windows are created somehow (probably by R's graphapp) for (int i = 0; i < candidate_windows.size (); ++i) { HWND hwnd = candidate_windows[i]; if ((!IsWindow(hwnd)) || (!IsWindowVisible(hwnd))) { candidate_windows.removeAt (i); --i; } } // we could do some more checking, e.g. based on whether the window belongs to our // own process, and whether it appears to be of a sane size, but for now, we keep // things simple. if (candidate_windows.size ()) { RK_ASSERT (candidate_windows.size () < 2); return candidate_windows[0]; } return 0; #elif defined Q_WS_X11 if (!created_window) { // we did not see the window, yet? Maybe the event simply hasn't been processed, yet. syncX (); processEvents (); } detect_x11_creations = false; XSelectInput (QX11Info::display (), QX11Info::appRootWindow (), NoEventMask); return created_window; #else return 0; #endif } #ifdef Q_WS_X11 void RKWardApplication::registerNameWatcher (WId watched, RKMDIWindow *watcher) { RK_TRACE (APP); RK_ASSERT (!name_watchers_list.contains (watched)); XSelectInput (QX11Info::display (), watched, PropertyChangeMask); name_watchers_list.insert (watched, watcher); } void RKWardApplication::unregisterNameWatcher (WId watched) { RK_TRACE (APP); RK_ASSERT (name_watchers_list.contains (watched)); XSelectInput (QX11Info::display (), watched, NoEventMask); name_watchers_list.remove (watched); } bool RKWardApplication::x11EventFilter (XEvent *e) { if (detect_x11_creations) { if (e->type == CreateNotify) { if (e->xcreatewindow.parent == QX11Info::appRootWindow ()) { KWindowInfo info = KWindowInfo (e->xcreatewindow.window, NET::WMName | NET::WMWindowType); // at this point, we used to check, whether this window has some name or another. This heuristic allowed to sieve out helper windows of the window manager. However, since R 2.8.0, sometimes the window is mapped, before it has been given a name. // Now we rely on the fact (we hope it *is* a fact), that the device window is always the first one created. if ((info.windowType (0xFFFF) != 0) && (!created_window)) { created_window = e->xcreatewindow.window; return true; } } else { RK_ASSERT (false); } } } if (e->type == PropertyNotify) { if (e->xproperty.atom == wm_name_property) { if (name_watchers_list.contains (e->xproperty.window)) { KWindowInfo wininfo = KWindowInfo (e->xproperty.window, NET::WMName); name_watchers_list[e->xproperty.window]->setCaption (wininfo.name ()); return true; } } } return KApplication::x11EventFilter (e); } #endif // Q_WS_X11 rkward-0.6.4/rkward/ver.R.in0000644000175000017500000000025112455741221015202 0ustar thomasthomas# DO NOT CHANGE THIS FILE MANUALLY! # version number will be updated by cmake, see # rkward/SetVersionNumber.cmake #' @export ".rk.app.version" <- "@RKVERSION_NUMBER@" rkward-0.6.4/rkward/scriptbackends/0000755000175000017500000000000012633754364016671 5ustar thomasthomasrkward-0.6.4/rkward/scriptbackends/qtscripti18n.cpp0000664000175000017500000000260112633754364021747 0ustar thomasthomas/*************************************************************************** qtscripti18n - description ------------------- begin : Wed Oct 29 2014 copyright : (C) 2014 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "qtscripti18n.h" #include #include "../debug.h" void RKMessageCatalogObject::addI18nToScriptEngine (QScriptEngine* engine, const RKMessageCatalog* catalog) { QScriptValue handle = engine->newQObject (new RKMessageCatalogObject (catalog, engine)); engine->globalObject ().setProperty ("_i18n", handle); } #include "qtscripti18n.moc" rkward-0.6.4/rkward/scriptbackends/qtscripti18n.h0000664000175000017500000000446112633754364021422 0ustar thomasthomas/*************************************************************************** qtscripti18n - description ------------------- begin : Wed Oct 29 2014 copyright : (C) 2014 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef QTSCRIPTI18N_H #define QTSCRIPTI18N_H #include "../misc/rkmessagecatalog.h" class QScriptEngine; /** A QObject wrapper around RKMessageCatalog. Meant for use in RKComponentScripting and QtScriptBackend */ class RKMessageCatalogObject : public QObject { Q_OBJECT public: RKMessageCatalogObject (const RKMessageCatalog *_catalog, QObject *parent) : QObject (parent), catalog (_catalog) {}; virtual ~RKMessageCatalogObject () {}; Q_INVOKABLE QString i18n (const QString &msgid) const { return (catalog->translate (msgid)); }; Q_INVOKABLE QString i18nc (const QString &msgctxt, const QString &msgid) const { return (catalog->translate (msgctxt, msgid)); }; Q_INVOKABLE QString i18np (const QString &msgid_singular, const QString &msgid_plural, uint count) const { return (catalog->translate (msgid_singular, msgid_plural, count)); }; Q_INVOKABLE QString i18ncp (const QString &msgctxt, const QString &msgid_singular, const QString &msgid_plural, uint count) const { return (catalog->translate (msgctxt, msgid_singular, msgid_plural, count)); }; /** Add an RKMessageCatalog, and the required glue code to the given QScriptEngine. */ static void addI18nToScriptEngine (QScriptEngine *engine, const RKMessageCatalog *catalog); private: const RKMessageCatalog *catalog; }; #endif rkward-0.6.4/rkward/scriptbackends/qtscriptbackend.h0000664000175000017500000001025712633754364022232 0ustar thomasthomas/*************************************************************************** qtscriptbackend - description ------------------- begin : Mon Sep 28 2009 copyright : (C) 2009, 2012, 2014 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef QTSCRIPTBACKEND_H #define QTSCRIPTBACKEND_H #include "scriptbackend.h" class QtScriptBackendThread; class RKMessageCatalog; /** This class allows to use QtScript as a scripting backend in RKWard. The script itself is run in a separate thread to ensure good performance even for complex scripts. This is especially important for spinboxes, where the value is changes many times in quick succession. Note that this is also the reason not to use Kross, which appears to be not thread safe. TODO: The code is currently based on the old PHPBackend. Once that is truly obsolete, there should be room for redesigning several aspects. */ class QtScriptBackend : public ScriptBackend { Q_OBJECT public: QtScriptBackend (const QString &filename, const RKMessageCatalog *catalog); ~QtScriptBackend (); bool initialize (RKComponentPropertyCode *code_property=0, bool add_headings=true); void destroy (); void preprocess (int flags) { callFunction ("do_preprocess ();\n", flags, Preprocess); }; void calculate (int flags) { callFunction ("do_calculate ();\n", flags, Calculate); }; void printout (int flags) { callFunction ("do_printout ();\n", flags, Printout); }; void preview (int flags) { callFunction ("do_preview ();\n", flags, Preview); }; void writeData (const QVariant &data); public slots: void threadError (const QString &message); void commandDone (const QString &result); void needData (const QString &identifier, const int hint); private: void tryNextFunction (); QtScriptBackendThread *script_thread; const RKMessageCatalog *catalog; bool dead; QString filename; }; #include #include #include class QtScriptBackendThread : public QThread { Q_OBJECT public: QtScriptBackendThread (const QString &commonfile, const QString &scriptfile, QtScriptBackend *parent, const RKMessageCatalog *catalog); ~QtScriptBackendThread (); void setCommand (const QString &command); void setData (const QVariant &data); void kill () { killed = true; }; void goToSleep (bool sleep); signals: void commandDone (const QString &result); void needData (const QString &identifier, const int hint); void error (const QString &error); protected slots: QVariant getValue (const QString &identifier); QVariant getList (const QString &identifier); QVariant getString (const QString &identifier); QVariant getBoolean (const QString &identifier); QVariant getUiLabelPair (const QString &identifier); bool includeFile (const QString &filename); protected: void run (); private: /** for any script error in the last evaluation. If there was an error, a message is generated, and this function return true (and the thread should be made to exit!) */ bool scriptError (); QVariant getValue (const QString &identifier, const int hint); QString _command; QVariant _data; QString _commonfile; QString _scriptfile; QScriptEngine engine; const RKMessageCatalog *catalog; bool killed; QMutex mutex; QMutex sleep_mutex; bool sleeping; }; #if QT_VERSION >= 0x040700 # define USE_Q_SCRIPT_PROGRAM # include namespace RKPrecompiledQtScripts { bool loadCommonScript (QScriptEngine *engine, QString scriptfile); }; #endif #endif rkward-0.6.4/rkward/scriptbackends/rkcomponentscripting.cpp0000664000175000017500000002662512633754364023674 0ustar thomasthomas/*************************************************************************** rkcomponentscripting - description ------------------- begin : Thu Jun 17 2010 copyright : (C) 2010, 2015 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkcomponentscripting.h" #include #include #include #include #include "../plugin/rkcomponent.h" #include "../core/robjectlist.h" #include "../misc/rkcommonfunctions.h" #include "../misc/xmlhelper.h" #include "../rkglobals.h" #include "../rbackend/rinterface.h" #include "qtscriptbackend.h" #include "qtscripti18n.h" #include "../debug.h" RKComponentScriptingProxy::RKComponentScriptingProxy (RKComponent *component) : QObject (component) { RK_TRACE (PHP); RK_ASSERT (component); RKComponentScriptingProxy::component = component; QScriptValue backend_object = engine.newQObject (this); engine.globalObject ().setProperty ("_rkward", backend_object); RKMessageCatalogObject::addI18nToScriptEngine (&engine, component->xmlHelper ()->messageCatalog ()); } RKComponentScriptingProxy::~RKComponentScriptingProxy () { RK_TRACE (PHP); for (int i = 0; i < outstanding_commands.size (); ++i) { RKGlobals::rInterface ()->cancelCommand (outstanding_commands[i].command); } } void RKComponentScriptingProxy::initialize (const QString& file, const QString& command) { RK_TRACE (PHP); QString _command = command; if (!file.isEmpty ()) { _command.prepend ("_rkward.include('" + file + "');\n"); _scriptfile = file; } QDir files_path (RKCommonFunctions::getRKWardDataDir () + "phpfiles/"); #ifdef USE_Q_SCRIPT_PROGRAM if (!RKPrecompiledQtScripts::loadCommonScript (&engine, files_path.absoluteFilePath ("rkcomponentscripting.js"))) { engine.evaluate (i18n ("Error opening script file %1", files_path.absoluteFilePath ("rkcomponentscripting.js"))); } else if (!RKPrecompiledQtScripts::loadCommonScript (&engine, files_path.absoluteFilePath ("common.js"))) { engine.evaluate (i18n ("Error opening script file %1", files_path.absoluteFilePath ("common.js"))); } #else _command.prepend ("_rkward.include('" + files_path.absoluteFilePath ("rkcomponentscripting.js") + "');\n"); _command.prepend ("_rkward.include('" + files_path.absoluteFilePath ("common.js") + "');\n"); #endif evaluate (_command); } void RKComponentScriptingProxy::handleScriptError (const QString& current_file) { RK_TRACE (PHP); QString file = current_file; if (file.isEmpty ()) file = _scriptfile; if (engine.hasUncaughtException ()) { QString message = i18n ("Script Error: %1\n", engine.uncaughtException ().toString ()); KMessageBox::detailedError (0, message, engine.uncaughtExceptionBacktrace ().join ("\n")); engine.clearExceptions (); emit (haveError()); } } void RKComponentScriptingProxy::include (const QString& filename) { RK_TRACE (PHP); QString _filename = filename; if (QFileInfo (filename).isRelative ()) { KUrl script_path = KUrl (QUrl::fromLocalFile (_scriptfile)).upUrl (); script_path.addPath (filename); _filename = script_path.toLocalFile (); } QFile file (_filename); if (!file.open (QIODevice::ReadOnly | QIODevice::Text)) { evaluate (i18n ("error ('The file \"%1\" (needed by \"%2\") could not be found. Please check your installation.');\n", _filename, _scriptfile).toUtf8 ()); return; } evaluate (QString::fromUtf8 (file.readAll())); handleScriptError (_filename); } void RKComponentScriptingProxy::evaluate (const QString &code) { RK_TRACE (PHP); // evaluate in global context engine.currentContext ()->setActivationObject (engine.globalObject ()); QScriptValue result = engine.evaluate (code, _scriptfile); handleScriptError (); } void RKComponentScriptingProxy::addChangeCommand (const QString& changed_id, const QString& command) { RK_TRACE (PHP); QString remainder; RKComponentBase* base = component->lookupComponent (changed_id, &remainder); if (remainder.isEmpty ()) { component_commands.insert (base, command); if (base->isComponent()) { connect (static_cast (base), SIGNAL (componentChanged(RKComponent*)), this, SLOT (componentChanged(RKComponent*))); } else { connect (static_cast (base), SIGNAL (valueChanged(RKComponentPropertyBase*)), this, SLOT (propertyChanged(RKComponentPropertyBase*))); } } else { evaluate (QString ("error ('No such property %1 (failed portion was %2)');\n").arg (changed_id, remainder)); } } QVariant RKComponentScriptingProxy::doRCommand (const QString& command, const QString& callback) { RK_TRACE (PHP); // purge duplicate commands for (int i = 0; i < outstanding_commands.size (); ++i) { const OutstandingCommand &oc = outstanding_commands[i]; if (oc.callback == callback) { if (RKGlobals::rInterface ()->softCancelCommand (oc.command)) { outstanding_commands.removeAt (i); --i; continue; } } } OutstandingCommand com; com.command = new RCommand (command, RCommand::PriorityCommand | RCommand::GetStructuredData | RCommand::Plugin); connect (com.command->notifier (), SIGNAL (commandFinished(RCommand*)), this, SLOT (scriptRCommandFinished(RCommand*))); com.callback = callback; outstanding_commands.append (com); RKGlobals::rInterface ()->issueCommand (com.command); return (QVariant (com.command->id ())); } static QScriptValue marshall (QScriptEngine *engine, RData *data) { RK_TRACE (PHP); if (data->getDataType () == RData::StringVector) { return (qScriptValueFromSequence (engine, data->stringVector())); } else if (data->getDataType () == RData::IntVector) { return (qScriptValueFromSequence (engine, data->intVector())); } else if (data->getDataType () == RData::RealVector) { return (qScriptValueFromSequence (engine, data->realVector())); } else if (data->getDataType () == RData::StructureVector) { const RData::RDataStorage& rs = data->structureVector (); QScriptValue ret = engine->newArray (rs.size ()); for (int i = 0; i < rs.size (); ++i) { ret.setProperty (i, marshall (engine, rs[i])); } return ret; } else { RK_ASSERT (false); } return QScriptValue (); } void RKComponentScriptingProxy::scriptRCommandFinished (RCommand* command) { RK_TRACE (PHP); QString callback; for (int i = 0; i < outstanding_commands.size (); ++i) { const OutstandingCommand& oc = outstanding_commands[i]; if (oc.command == command) { callback = oc.callback; outstanding_commands.removeAt (i); break; } } RK_ASSERT (!callback.isNull ()); if (command->wasCanceled ()) return; if (command->failed ()) RK_DEBUG (PHP, DL_ERROR, "Plugin script R command %s failed. Full output wsa %s", qPrintable (command->command ()), qPrintable (command->fullOutput ())); QScriptValueList args; args.append (marshall (&engine, command)); args.append (QScriptValue (command->id ())); QScriptValue callback_obj = engine.globalObject ().property (callback); callback_obj.call (engine.globalObject (), args); handleScriptError (); } void RKComponentScriptingProxy::componentChanged (RKComponent* changed) { RK_TRACE (PHP); handleChange (changed); } void RKComponentScriptingProxy::propertyChanged (RKComponentPropertyBase* changed) { RK_TRACE (PHP); handleChange (changed); } void RKComponentScriptingProxy::handleChange (RKComponentBase* changed) { RK_TRACE (PHP); QString command = component_commands.value (changed); evaluate (command.toUtf8()); } QVariant RKComponentScriptingProxy::getValue (const QString &id) const { RK_TRACE (PHP); return (component->fetchValue (id, RKComponent::TraditionalValue)); } QVariant RKComponentScriptingProxy::getString (const QString &id) const { RK_TRACE (PHP); return (component->fetchValue (id, RKComponent::StringValue)); } QVariant RKComponentScriptingProxy::getBoolean (const QString &id) const { RK_TRACE (PHP); return (component->fetchValue (id, RKComponent::BooleanValue)); } QVariant RKComponentScriptingProxy::getList (const QString &id) const { RK_TRACE (PHP); return (component->fetchValue (id, RKComponent::StringlistValue)); } void RKComponentScriptingProxy::setValue (const QString &value, const QString &id) { RK_TRACE (PHP); QString modifier; RKComponentBase* resolved = component->lookupComponent (id, &modifier); if (resolved && modifier.isEmpty () && resolved->isProperty ()) { static_cast (resolved)->setValue (value); } else { evaluate (QString ("error ('No such property %1 (failed portion was %2)');\n").arg (id, modifier)); } } void RKComponentScriptingProxy::setListValue (const QStringList& value, const QString& id) { RK_TRACE (PHP); QString modifier; RKComponentBase* resolved = component->lookupComponent (id, &modifier); if (resolved && modifier.isEmpty () && resolved->isProperty ()) { RKComponentPropertyAbstractList *l = dynamic_cast (resolved); if (l) { l->setValueList (value); return; } static_cast (resolved)->setValue (value.join ("\n")); } else { evaluate (QString ("error ('No such property %1 (failed portion was %2)');\n").arg (id, modifier)); } } QVariantList RKComponentScriptingProxy::getObjectInfo (const QString &name) { RK_TRACE (PHP); RObject* object = RObjectList::getObjectList ()->findObject (name); if (object) { QVariantList ret; QVariantList dims; foreach (int dim, object->getDimensions ()) { dims.append (dim); } ret.append (QVariant (dims)); ret.append (QVariant (object->classNames ())); ret.append (object->isType (RObject::DataFrame)); ret.append (object->isType (RObject::Matrix)); ret.append (object->isType (RObject::List)); ret.append (object->isType (RObject::Function)); ret.append (object->isType (RObject::Environment)); if (object->getDataType () == RObject::DataNumeric) ret.append ("numeric"); else if (object->getDataType () == RObject::DataFactor) ret.append ("factor"); else if (object->getDataType () == RObject::DataCharacter) ret.append ("character"); else if (object->getDataType () == RObject::DataLogical) ret.append ("logical"); else ret.append ("unknown"); return (ret); } return (QVariantList ()); } QString RKComponentScriptingProxy::getObjectParent (const QString &name) { RK_TRACE (PHP); RObject* object = RObjectList::getObjectList ()->findObject (name); if (object) { if (object->parentObject ()) return (object->parentObject ()->getFullName ()); } return (QString ()); } QString RKComponentScriptingProxy::getObjectChild (const QString &name) { RK_TRACE (PHP); RObject* object = RObjectList::getObjectList ()->findObject (name); if (object) { if (object->isContainer ()) { RObject* child = static_cast (object)->findChildByName (name); if (child) return (child->getFullName ()); } } return (QString ()); } #include "rkcomponentscripting.moc" rkward-0.6.4/rkward/scriptbackends/scriptbackend.cpp0000664000175000017500000000720312633754364022215 0ustar thomasthomas/*************************************************************************** scriptbackend - description ------------------- begin : Sun Aug 15 2004 copyright : (C) 2004, 2006, 2009, 2011 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "scriptbackend.h" #include #include "../plugin/rkcomponentproperties.h" #include "../debug.h" ScriptBackend::ScriptBackend () : QObject() { busy = false; current_type = Ignore; code_property = 0; } ScriptBackend::~ScriptBackend () { while (command_stack.count ()) { delete command_stack.takeFirst (); } } void ScriptBackend::callFunction (const QString &function, int flags, int type) { RK_TRACE (PHP); RK_DEBUG (PHP, DL_DEBUG, "callFunction %s", function.toLatin1 ().data ()); ScriptCommand *command = new ScriptCommand; command->command = function; command->flags = flags; command->type = type; command->complete = false; if (code_property) { if (type == Preprocess) { code_property->setPreprocess (QString ()); } else if (type == Calculate) { code_property->setCalculate (QString ()); } else if (type == Printout) { code_property->setPrintout (QString ()); } else if (type == Preview) { code_property->setPreview (QString ()); } invalidateCalls (type); } command_stack.append (command); tryNextFunction (); } void ScriptBackend::invalidateCalls (int type) { RK_TRACE (PHP); if (current_type == type) { current_type = Ignore; } QLinkedList::iterator it = command_stack.begin (); while (it != command_stack.end ()) { if ((*it)->type == type) { delete (*it); it = command_stack.erase (it); // it now points to next item } else { ++it; } } } void ScriptBackend::commandFinished (const QString &output) { RK_TRACE (PHP); QString _output = output; if (current_type != Ignore) { if (code_property) { if (_output.isNull ()) _output = ""; // must not be null for the code property! bool add_header = add_headings && (!_output.isEmpty ()); if (current_type == Preprocess) { if (add_header) code_property->setPreprocess (i18n ("## Prepare\n") + _output); else code_property->setPreprocess (_output); } else if (current_type == Calculate) { if (add_header) code_property->setCalculate (i18n ("## Compute\n") + _output); else code_property->setCalculate (_output); } else if (current_type == Printout) { if (add_header) code_property->setPrintout (i18n ("## Print result\n") + _output); else code_property->setPrintout (_output); } else if (current_type == Preview) { // no heading for the preview code (not shown in the code box) code_property->setPreview (_output); } else { emit (commandDone (current_flags)); } } else { emit (commandDone (current_flags)); } } busy = false; tryNextFunction (); if (!busy) { emit (idle ()); } } #include "scriptbackend.moc" rkward-0.6.4/rkward/scriptbackends/simplebackend.h0000664000175000017500000000516712633754364021656 0ustar thomasthomas/*************************************************************************** simplebackend - description ------------------- begin : Thu May 10 2007 copyright : (C) 2007, 2012 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef SIMPLEBACKEND_H #define SIMPLEBACKEND_H #include "scriptbackend.h" #include /** @brief A very simple script backend This class provides a very simple alternative to the PHP backend. Right now it's basically just used as a hack to reduce the overhead of starting a PHP process for each color_chooser component (which is often embedded many times inside a single plugin). This class is very hackish and NOT sure to stay! It might be obsoleted by another scripting solution. @author Thomas Friedrichsmeier */ class SimpleBackend : public ScriptBackend { public: SimpleBackend (); ~SimpleBackend (); void setPreprocessTemplate (const QString &template_string) { preprocess_template = template_string; }; void setCalculateTemplate (const QString &template_string) { calculate_template = template_string; }; void setPrintoutTemplate (const QString &template_string) { printout_template = template_string; }; void setPreviewTemplate (const QString &template_string) { preview_template = template_string; }; bool initialize (RKComponentPropertyCode *code_property=0, bool add_headings=true); void destroy (); void preprocess (int flags); void calculate (int flags); void printout (int flags); void preview (int flags); void writeData (const QVariant &data); void tryNextFunction (); private: QString preprocess_template; QString calculate_template; QString printout_template; QString preview_template; QString current_template; int template_sep; int template_pos; QList current_values; void processCall (); void finishCall (const QString &conditions); }; #endif rkward-0.6.4/rkward/scriptbackends/scriptbackend.h0000664000175000017500000000647712633754364021676 0ustar thomasthomas/*************************************************************************** scriptbackend - description ------------------- begin : Sun Aug 15 2004 copyright : (C) 2004, 2007, 2012 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef SCRIPTBACKEND_H #define SCRIPTBACKEND_H #include #include #include #include "../plugin/rkstandardcomponent.h" class RKComponentPropertyCode; /** Abstract base class for scripting-language backends. Mostly pure virtual functions only + some handling to make sure the processing is asynchronous. @author Thomas Friedrichsmeier */ class ScriptBackend : public QObject { Q_OBJECT public: ScriptBackend (); ~ScriptBackend (); enum CallType { Preprocess = 0, Calculate = 1, Printout = 2, Preview = 4, Ignore = 5, User = 6 }; /** initialize backend @param code_property If you supply a pointer to an RKComponentPropertyCode, The backend will directly set values for this property in response to calls to preproces (), calculate (), printout (), and cleanup (). @param add_headings (Only meaningful, if code_property is not 0). If set to true, heading comments will be added to each section of the code (e.g. "## Do calculations") @returns true on successful initialization, false on errors */ virtual bool initialize (RKComponentPropertyCode *code_property=0, bool add_headings=true) = 0; virtual void destroy () = 0; virtual void preprocess (int flags) = 0; virtual void calculate (int flags) = 0; virtual void printout (int flags) = 0; virtual void preview (int flags) = 0; virtual bool isBusy () { return busy; }; virtual void writeData (const QVariant &data) = 0; signals: void commandDone (int); void idle (); void requestValue (const QString &, const int); void haveError (); protected: RKComponentPropertyCode *code_property; bool add_headings; bool busy; struct ScriptCommand { /// the command string QString command; /// flags attached to this command by the parent int flags; /// internal type (used to find out, if this is a preproces, calculate, printout, or cleanup call) int type; /// whether command has finished bool complete; }; QLinkedList command_stack; int current_flags; int current_type; /** Invalidate all previous calls of the given type */ void invalidateCalls (int type); /** call a function on the current template. */ void callFunction (const QString &function, int flags, int type); void commandFinished (const QString &output); virtual void tryNextFunction () = 0; }; #endif rkward-0.6.4/rkward/scriptbackends/common.js0000644000175000017500000001320012455741221020501 0ustar thomasthomas_script_output = ""; /* NOTE: for compatibility with Kross, we can't write this as 'function echo (text)...', somehow. */ echo = function (text) { _script_output += text; } printIndented = function (indentation, lines) { /** More complex than may seem necessary at first glance. The point is that the final line break in the input should *not* be replaced by line break + indentation. */ echo (indentation + lines.replace (/\n(.)/g, "\n" + indentation + "$1")); } printIndentedUnlessEmpty = function (indentation, lines, pre, post) { if (lines.length <= 0) return; if (typeof (pre) != 'undefined') echo (pre); printIndented (indentation, lines); if (typeof (post) != 'undefined') echo (post); } noquote = function (text) { if (text.noquote) { text.noquote++; return (text); } var ret = new String (text); ret.noquote = 1; return (ret); } quote = function (text) { if (text.noquote) { text.noquote--; return (text); } return ("\"" + text.replace (/\"/g, "\\\"") + "\""); } i18n = function (msgid) { var ret = _i18n.i18n (msgid); for (var i = 1; i < arguments.length; i++) { ret = ret.replace(new RegExp("%" + i, 'g'), arguments[i]); } if (msgid.noquote) { ret.noquote = msgid.noquote; return (ret); } // quote the translated string, as it will most likely be passed to R as a string literal. // at the same time, protect it against additional quoting (esp. when used in makeHeaderCode ()) return (noquote (quote (ret))); } i18nc = function (msgctxt, msgid) { var ret = _i18n.i18nc (msgctxt, msgid); for (var i = 2; i < arguments.length; i++) { ret = ret.replace(new RegExp("%" + (i - 1), 'g'), arguments[i]); } if (msgid.noquote) { ret.noquote = msgid.noquote; return (ret); } return (noquote (quote (ret))); } i18np = function (msgid, msgid_plural, n) { var ret = _i18n.i18np (msgid, msgid_plural, n); for (var i = 3; i < arguments.length; i++) { ret = ret.replace(new RegExp("%" + (i - 1), 'g'), arguments[i]); // start replacing at %2. %1 already handled. } if (msgid.noquote) { ret.noquote = msgid.noquote; return (ret); } return (noquote (quote (ret))); } i18ncp = function (msgctxt, msgid, msgid_plural, n) { var ret = _i18n.i18ncp (msgctxt, msgid, msgid_plural, n); for (var i = 4; i < arguments.length; i++) { ret = ret.replace(new RegExp("%" + (i - 2), 'g'), arguments[i]); } if (msgid.noquote) { ret.noquote = msgid.noquote; return (ret); } return (noquote (quote (ret))); } comment = function (message, indentation) { var args = Array ().slice.call (arguments, 2); args.unshift ("R code comment", noquote (message)); message = i18nc.apply (this, args); if (typeof (indentation) == 'undefined') indentation = ""; printIndented (indentation + "# ", message + "\n"); } makeHeaderCode = function (title, parameters, level, indentation) { if (typeof (indentation) == 'undefined') indentation = ""; echo (indentation + "rk.header (" + quote (title)); if (parameters.length) { echo (", parameters=list("); for (var p = 0; p < parameters.length; p += 2) { if (p) echo (",\n" + indentation + "\t"); echo (quote (parameters[p]) + "=" + quote (parameters[p+1])); } echo (")"); } if (typeof (level) != 'undefined') echo (", level=" + level); echo (")\n"); } Header = function (title, level) { this.title = title; this.level = level; this.parameters = []; this.add = function (caption, value) { this.parameters.push (caption, value); return this; } this.addFromUI = function (elementid, value_override) { this.parameters = this.parameters.concat (_RK_backend.getUiLabelPair (elementid)); if (typeof (value_override) != 'undefined') this.parameters[this.parameters.length - 1] = value_override; return this; } this.print = function (indentation) { makeHeaderCode (this.title, this.parameters, this.level, indentation); } this.extractParameters = function (indentation) { if (typeof (indentation) == 'undefined') indentation = ""; var ret = ""; for (var p = 0; p < this.parameters.length; p += 2) { if (p) ret += ",\n" + indentation + "\t"; ret += quote (this.parameters[p]) + "=" + quote (this.parameters[p+1]); } return ret; } } getValue = function (id) { return (_RK_backend.getValue (id)); } getString = function (id) { // getValue() sometimes returns numeric results (whenever the value is "0"). This variant always returns strings. return (_RK_backend.getString (id)); } getList = function (id) { // will try to return value as list, and produces a warning, if that fails. return (_RK_backend.getList (id)); } getBoolean = function (id) { // will try to return value as logical, and produces a warning, if that fails. return (_RK_backend.getBoolean (id)); } printValue = function (id) { echo (getValue (id)); } include = function (file) { _RK_backend.includeFile (file); } flushOutput = function () { var string = _script_output; _script_output = ""; return (string); } do_preprocess = function () { if (typeof (preprocess) == "undefined") return (""); preprocess (); return (flushOutput ()); } do_calculate = function () { if (typeof (calculate) == "undefined") return (""); calculate (); return (flushOutput ()); } do_printout = function () { if (typeof (printout) == "undefined") return (""); printout (); return (flushOutput ()); } do_preview = function () { if (typeof (preview) == "undefined") return (""); preview (); return (flushOutput ()); } // for compatibility with the converted PHP code trim = function (text) { var ret = text.replace (/^\s*/, "").replace (/\s*$/, ""); return (ret); } split = function (by, text) { return (text.split (by)); } function str_replace (needle, replacement, haystack) { return (haystack.split (needle).join (replacement)); } rkward-0.6.4/rkward/scriptbackends/CMakeLists.txt0000644000175000017500000000162512455741221021423 0ustar thomasthomasINCLUDE_DIRECTORIES( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${KDE4_INCLUDE_DIR} ${QT_INCLUDES} ) ########### next target ############### SET(scriptbackends_STAT_SRCS scriptbackend.cpp simplebackend.cpp qtscriptbackend.cpp qtscripti18n.cpp rkcomponentscripting.cpp ) QT4_AUTOMOC(${scriptbackends_STAT_SRCS}) ADD_LIBRARY(scriptbackends STATIC ${scriptbackends_STAT_SRCS}) ########### install files ############### INSTALL(FILES common.js rkcomponentscripting.js DESTINATION ${DATA_INSTALL_DIR}/rkward/phpfiles ) #original Makefile.am contents follow: #INCLUDES = $(all_includes) #METASOURCES = AUTO #noinst_LIBRARIES = libscriptbackends.a #noinst_HEADERS = phpbackend.h scriptbackend.h simplebackend.h #libscriptbackends_a_SOURCES = phpbackend.cpp scriptbackend.cpp simplebackend.cpp #phpfilesdir = $(kde_datadir)/rkward/phpfiles #phpfiles_DATA = common.php php.ini # rkward-0.6.4/rkward/scriptbackends/rkcomponentscripting.js0000644000175000017500000001033512455741221023501 0ustar thomasthomas/*************************************************************************** rkcomponentscripting - description ------------------- begin : Thu Jun 17 2010 copyright : (C) 2010, 2013 by Thomas Friedrichsmeier email : tfry@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ // _rkward = Handler object set from RKWard function Component(id) { this._id = id; // this one is mostly for internal use this.absoluteId = function (id) { if (this._id == "") return (id); if (id) return (this._id + "." + id); return (this._id); } this.getValue = function (id) { return (_rkward.getValue (this.absoluteId (id))); } this.getString = function (id) { return (_rkward.getString (this.absoluteId (id))); } this.getBoolean = function (id) { return (_rkward.getBoolean (this.absoluteId (id))); } this.getList = function (id) { return (_rkward.getList (this.absoluteId (id))); } this.setValue = function (id, value) { return (_rkward.setValue (value, this.absoluteId (id))); } this.setListValue = function (id, value) { return (_rkward.setListValue (value, this.absoluteId (id))); } this.getChild = function (id) { return (new Component (this.absoluteId (id))); } this.addChangeCommand = function (id, command) { _rkward.addChangeCommand (this.absoluteId (id), command); } } makeComponent = function (id) { return (new Component (id)); } gui = new Component (""); doRCommand = function (command, callback) { return (_rkward.doRCommand (command, callback)); } function RObject(objectname) { this.objectname = objectname; // for internal use this.initialize = function () { info = _rkward.getObjectInfo (this.objectname); this._dimensions = info.shift (); this._classes = info.shift (); this._isDataFrame = info.shift (); this._isMatrix = info.shift (); this._isList = info.shift (); this._isFunction = info.shift (); this._isEnvironment = info.shift (); this._datatype = info.shift (); } this.initialize(); this.getName = function () { return (this.objectname); } this.exists = function () { return (typeof (this._dimensions) != "undefined"); } this.dimensions = function () { return (this._dimensions); } this.classes = function () { return (this._classes); } this.isClass = function (classname) { return (this._classes.indexOf(classname) != -1); } this.isDataFrame = function () { return (this._isDataFrame); } this.isMatrix = function () { return (this._isMatrix); } this.isList = function () { return (this._isList); } this.isFunction = function () { return (this._isFunction); } this.isEnvironment = function () { return (this._isEnvironment); } this.isDataNumeric = function () { return (this._datatype == "numeric"); } this.isDataFactor = function () { return (this._datatype == "factor"); } this.isDataCharacter = function () { return (this._datatype == "character"); } this.isDataLogical = function () { return (this._datatype == "logical"); } this.parent = function () { return (new RObject (_rkward.getObjectParent (this._name))); } this.child = function (childname) { return (new RObject (_rkward.getObjectChild (this._name, childname))); } } makeRObject = function (objectname) { return (new RObject (objectname)); } function RObjectArray(names) { this._objects = new Array (); objs = names.split ('\n'); while (objs.count > 0) { this._objects.push (new RObject (objs.shift ())); } } makeRObjectArray = function (objectnames) { return (new RObjectArray (objectnames)); } rkward-0.6.4/rkward/scriptbackends/simplebackend.cpp0000664000175000017500000001126412633754364022204 0ustar thomasthomas/*************************************************************************** simplebackend - description ------------------- begin : Thu May 10 2007 copyright : (C) 2007, 2012 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "simplebackend.h" #include "../debug.h" SimpleBackend::SimpleBackend () : ScriptBackend () { RK_TRACE (PHP); } SimpleBackend::~SimpleBackend () { RK_TRACE (PHP); } bool SimpleBackend::initialize (RKComponentPropertyCode *code_property, bool add_headings) { RK_TRACE (PHP); SimpleBackend::code_property = code_property; SimpleBackend::add_headings = add_headings; template_pos = 0; return true; } void SimpleBackend::destroy () { RK_TRACE (PHP); deleteLater (); } void SimpleBackend::preprocess (int flags) { RK_TRACE (PHP); callFunction (QString (), flags, Preprocess); } void SimpleBackend::calculate (int flags) { RK_TRACE (PHP); callFunction (QString (), flags, Calculate); } void SimpleBackend::printout (int flags) { RK_TRACE (PHP); callFunction (QString (), flags, Printout); } void SimpleBackend::preview (int flags) { RK_TRACE (PHP); callFunction (QString (), flags, Preview); } void SimpleBackend::writeData (const QVariant &data) { RK_TRACE (PHP); current_values.append (data); processCall (); } void SimpleBackend::tryNextFunction () { RK_TRACE (PHP); if ((!busy) && (!command_stack.isEmpty ())) { // clean up previous command if applicable if (command_stack.first ()->complete) { delete command_stack.first (); command_stack.pop_front (); if (!command_stack.count ()) return; } busy = true; command_stack.first ()->complete = true; current_flags = command_stack.first ()->flags; current_type = command_stack.first ()->type; current_values.clear (); template_pos = 0; if (current_type == Preprocess) current_template = preprocess_template; else if (current_type == Printout) current_template = printout_template; else if (current_type == Calculate) current_template = calculate_template; else if (current_type == Preview) current_template = preview_template; template_sep = current_template.indexOf ("!!!"); if (template_sep < 0) { commandFinished (""); return; } processCall (); } } void SimpleBackend::processCall () { RK_TRACE (PHP); int next_token = current_template.indexOf ("$$$", template_pos); if (next_token < 0) next_token = template_sep; if (next_token > template_sep) next_token = template_sep; if (next_token < template_sep) { int token_end = current_template.indexOf ("$$$", next_token + 3); RK_ASSERT (token_end >= 0); QString token = current_template.mid (next_token + 3, token_end - (next_token + 3)); template_pos = token_end + 3; emit (requestValue (token, RKStandardComponent::StringValue)); return; } // all values are fetched. Now generate the return string finishCall (current_template.mid (template_sep + 3)); } void SimpleBackend::finishCall (const QString &conditions) { RK_TRACE (PHP); QString conds = conditions; int repl = current_values.count(); for (int i = repl; i > 0; --i) { QString placeholder = '%' + QString::number (i); QString replacement = current_values[i-1].toString (); conds.replace (placeholder, replacement); } QString output; int pos = 3; int max = conds.length (); do { int cond_end = conds.indexOf ("!?!", pos); if (cond_end < 0) cond_end = max; QString condition = conds.mid (pos, cond_end - pos); int if_end = condition.indexOf ("!:!"); RK_ASSERT (if_end >= 0); QString if_part = condition.left (if_end); int if_mid = if_part.indexOf ("!=!"); RK_ASSERT (if_mid >= 0); QString if_compare = if_part.left (if_mid); QString if_against = if_part.mid (if_mid + 3); if ((if_compare.isEmpty() && if_against.isEmpty ()) || (if_compare == if_against)) { output = condition.mid (if_end + 3); break; } pos = cond_end + 3; } while (pos < max); // reached end of template commandFinished (output); } rkward-0.6.4/rkward/scriptbackends/rkcomponentscripting.h0000664000175000017500000000646612633754364023342 0ustar thomasthomas/*************************************************************************** rkcomponentscripting - description ------------------- begin : Thu Jun 17 2010 copyright : (C) 2010, 2013 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKCOMPONENTSCRIPTING_H #define RKCOMPONENTSCRIPTING_H #include #include #include #include "../rbackend/rcommand.h" class RKComponent; class RKComponentBase; class RKComponentPropertyBase; /** This class basically provides the API that is available to scripts running within rkward plugins. The slots are meant to be called from the script. NOTE: This contains some duplication of ScriptBackend and derived classes. Perhaps this can be merged, better. The key technical difference between this, and ScriptBackend, is that this operates in the main thread, while ScriptBackend is designed to operate in a separate thread, and may merge a bunch of changes into a single update. */ class RKComponentScriptingProxy : public QObject { Q_OBJECT public: explicit RKComponentScriptingProxy (RKComponent *component); ~RKComponentScriptingProxy (); void initialize (const QString& file, const QString& command); public slots: void componentChanged (RKComponent* changed); void propertyChanged (RKComponentPropertyBase* changed); // these are meant to be called from the script void include (const QString& filename); void addChangeCommand (const QString& changed_id, const QString& command); /** @returns id of the command issued. */ QVariant doRCommand (const QString& command, const QString& callback); QVariant getValue (const QString &id) const; QVariant getString (const QString &id) const; QVariant getBoolean (const QString &id) const; QVariant getList (const QString &id) const; void setValue (const QString &value, const QString &id); void setListValue (const QStringList &value, const QString &id); QVariantList getObjectInfo (const QString &name); QString getObjectParent (const QString &name); QString getObjectChild (const QString &name); signals: void haveError (); private slots: void scriptRCommandFinished (RCommand* command); private: RKComponent* component; QScriptEngine engine; struct OutstandingCommand { RCommand *command; QString callback; }; QList outstanding_commands; QString _scriptfile; void evaluate (const QString &code); void handleChange (RKComponentBase* changed); QHash component_commands; void handleScriptError (const QString& current_file=QString ()); }; #endif rkward-0.6.4/rkward/scriptbackends/qtscriptbackend.cpp0000664000175000017500000002572012633754364022566 0ustar thomasthomas/*************************************************************************** qtscriptbackend - description ------------------- begin : Mon Sep 28 2009 copyright : (C) 2009, 2010, 2012, 2014, 2015 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "qtscriptbackend.h" #include #include #include "klocale.h" #include "kmessagebox.h" #include "../misc/rkcommonfunctions.h" #include "qtscripti18n.h" #include "../debug.h" QtScriptBackend::QtScriptBackend (const QString &filename, const RKMessageCatalog *catalog) : ScriptBackend () { RK_TRACE (PHP); script_thread = 0; QtScriptBackend::filename = filename; QtScriptBackend::catalog = catalog; dead = false; busy = true; } QtScriptBackend::~QtScriptBackend () { RK_TRACE (PHP); destroy (); if (script_thread && script_thread->isRunning ()) script_thread->terminate (); } bool QtScriptBackend::initialize (RKComponentPropertyCode *code_property, bool add_headings) { RK_TRACE (PHP); if (script_thread) { RK_DEBUG (PHP, DL_ERROR, "another template is already openend in this backend"); return false; } QDir files_path (RKCommonFunctions::getRKWardDataDir () + "phpfiles/"); QString common_js (files_path.absoluteFilePath ("common.js")); script_thread = new QtScriptBackendThread (common_js, filename, this, catalog); connect (script_thread, SIGNAL (error(QString)), this, SLOT (threadError(QString))); connect (script_thread, SIGNAL (commandDone(QString)), this, SLOT (commandDone(QString))); connect (script_thread, SIGNAL (needData(QString,int)), this, SLOT (needData(QString,int))); current_type = ScriptBackend::Ignore; script_thread->start (); QtScriptBackend::code_property = code_property; QtScriptBackend::add_headings = add_headings; return true; } void QtScriptBackend::destroy () { RK_TRACE (PHP); if (!dead) { if (script_thread) script_thread->goToSleep (false); dead = true; code_property = 0; if (script_thread) script_thread->kill (); QTimer::singleShot (10000, this, SLOT (deleteLater())); // don't wait for ever for the process to die, even if it's somewhat dangerous } busy = false; } void QtScriptBackend::tryNextFunction () { RK_TRACE (PHP); if (script_thread && (!busy) && (!command_stack.isEmpty ())) { /// clean up previous command if applicable if (command_stack.first ()->complete) { delete command_stack.takeFirst (); if (command_stack.isEmpty ()) { script_thread->goToSleep (true); return; } } RK_DEBUG (PHP, DL_DEBUG, "submitting QtScript code: %s", command_stack.first ()->command.toLatin1 ().data ()); if (script_thread) script_thread->goToSleep (false); script_thread->setCommand (command_stack.first ()->command); busy = true; command_stack.first ()->complete = true; current_flags = command_stack.first ()->flags; current_type = command_stack.first ()->type; } else { if (script_thread && command_stack.isEmpty ()) script_thread->goToSleep (true); } } void QtScriptBackend::writeData (const QVariant &data) { RK_TRACE (PHP); RK_DEBUG (PHP, DL_DEBUG, "submitting data: %s", qPrintable (data.toString ())); script_thread->setData (data); tryNextFunction (); } void QtScriptBackend::threadError (const QString &message) { RK_TRACE (PHP); if (dead) return; // we are already dead, so we've shown an error before. KMessageBox::error (0, i18n ("The QtScript-backend has reported an error:\n%1", message), i18n ("Scripting error")); emit (haveError ()); destroy (); } void QtScriptBackend::commandDone (const QString &result) { RK_TRACE (PHP); commandFinished (result); } void QtScriptBackend::needData (const QString &identifier, const int hint) { RK_TRACE (PHP); emit (requestValue (identifier, hint)); } ///////////////////////////////////////////////////////////////////////////////////////// #include QtScriptBackendThread::QtScriptBackendThread (const QString &commonfile, const QString &scriptfile, QtScriptBackend *parent, const RKMessageCatalog *catalog) : QThread (parent), engine (0) { RK_TRACE (PHP); // you'd think the engine already was in this thread, but no, it is not. You'd also think, this was fixable by setting "this" as the engine's parent, instead of 0, but no, somehow not. engine.moveToThread (this); _commonfile = commonfile; _scriptfile = scriptfile; killed = false; sleeping = false; QtScriptBackendThread::catalog = catalog; } QtScriptBackendThread::~QtScriptBackendThread () { RK_TRACE (PHP); } void QtScriptBackendThread::goToSleep (bool sleep) { RK_TRACE (PHP); if (sleeping != sleep) { if (sleep) { sleep_mutex.lock (); // hold a mutex until it's time to wake up, again. Thread will then wait on this mutex. sleeping = true; } else { sleeping = false; sleep_mutex.unlock (); } } } void QtScriptBackendThread::setCommand (const QString &command) { RK_TRACE (PHP); mutex.lock (); RK_ASSERT (_command.isNull ()); if (command.isNull ()) _command = ""; else _command = command; mutex.unlock (); } void QtScriptBackendThread::setData (const QVariant &data) { RK_TRACE (PHP); mutex.lock (); RK_ASSERT (_data.isNull ()); if (data.isNull ()) _data = ""; else _data = data; mutex.unlock (); } QVariant QtScriptBackendThread::getValue (const QString &identifier, const int hint) { RK_TRACE (PHP); emit (needData (identifier, hint)); QVariant ret; while (1) { if (killed) return QVariant (); mutex.lock (); if (!_data.isNull ()) { ret = _data; _data.clear (); } mutex.unlock (); if (!ret.isNull ()) break; usleep (20); // getValue () may be called very often, and we expect an answer very soon, so we don't sleep too long. } return (ret); } QVariant QtScriptBackendThread::getValue (const QString &identifier) { return getValue (identifier, RKStandardComponent::TraditionalValue); } QVariant QtScriptBackendThread::getList (const QString &identifier) { return getValue (identifier, RKStandardComponent::StringlistValue); } QVariant QtScriptBackendThread::getString (const QString &identifier) { return getValue (identifier, RKStandardComponent::StringValue); } QVariant QtScriptBackendThread::getBoolean (const QString &identifier) { return getValue (identifier, RKStandardComponent::BooleanValue); } QVariant QtScriptBackendThread::getUiLabelPair (const QString &identifier) { return getValue (identifier, RKStandardComponent::UiLabelPair); } bool QtScriptBackendThread::scriptError () { RK_TRACE (PHP); if (!engine.hasUncaughtException ()) return false; QString message = i18n ("Script Error: %1\nBacktrace:\n%2", engine.uncaughtException ().toString (), engine.uncaughtExceptionBacktrace ().join ("\n")); engine.clearExceptions (); emit (error (message)); return true; } bool QtScriptBackendThread::includeFile (const QString &filename) { RK_TRACE (PHP); QString _filename = filename; if (QFileInfo (filename).isRelative ()) { KUrl script_path = KUrl (QUrl::fromLocalFile (_scriptfile)).upUrl (); script_path.addPath (filename); _filename = script_path.toLocalFile (); } QFile file (_filename); if (!file.open (QIODevice::ReadOnly | QIODevice::Text)) { emit (error (i18n ("The file \"%1\" (needed by \"%2\") could not be found. Please check your installation.", _filename, _scriptfile))); return false; } // evaluate in global context engine.currentContext ()->setActivationObject (engine.globalObject ()); QScriptValue result = engine.evaluate (QString::fromUtf8 (file.readAll ()), _filename); if (scriptError ()) return false; return true; } void QtScriptBackendThread::run () { RK_TRACE (PHP); QScriptValue backend_object = engine.newQObject (this); engine.globalObject ().setProperty ("_RK_backend", backend_object); RKMessageCatalogObject::addI18nToScriptEngine (&engine, catalog); if (scriptError ()) return; #ifdef USE_Q_SCRIPT_PROGRAM if (!RKPrecompiledQtScripts::loadCommonScript (&engine, _commonfile)) { if (!engine.hasUncaughtException ()) { emit error (i18n ("Could not open common script file \"%1\"", _commonfile)); } else { scriptError (); } return; } #else if (!includeFile (_commonfile)) return; #endif if (!includeFile (_scriptfile)) return; emit (commandDone ("startup complete")); QString command; while (1) { if (killed) return; if (sleeping) { sleep_mutex.lock (); sleep_mutex.unlock (); } mutex.lock (); if (!_command.isNull ()) { command = _command; _command.clear (); } mutex.unlock (); if (command.isNull ()) { msleep (1); continue; } // do it! QScriptValue result = engine.evaluate (command); if (scriptError ()) return; emit (commandDone (result.toString ())); command.clear (); } } #ifdef USE_Q_SCRIPT_PROGRAM namespace RKPrecompiledQtScripts { QMap compiled_includes; QMutex compiled_includes_mutex; bool loadCommonScript (QScriptEngine* engine, QString scriptfile) { RK_TRACE (PHP); // NOTE: QScriptProgram cannot be evaluated concurrently in several threads (see https://bugreports.qt-project.org/browse/QTBUG-29246). // Neither would our QMap caching logic work in concurrent threads. Thus the mutex. This clearly has the drawback that QScript evaluation threads // may be waiting for each other during initialization. However, we assume that // - Typically only few such threads are running // - Responsiveness (i.e. UI startup speed), not throughput is the main goal, here. // - The important script engines are those running in the UI thread (and thus necessarily sequentially). As long as these are initialized before // the other threads, they will clearly profit from pre-compiling QMutexLocker ml (&compiled_includes_mutex); if (!compiled_includes.contains (scriptfile)) { RK_DEBUG (PHP, DL_DEBUG, "Compiling common script file %s", qPrintable (scriptfile)); QFile file (scriptfile); if (!file.open (QIODevice::ReadOnly | QIODevice::Text)) { return false; } compiled_includes.insert (scriptfile, QScriptProgram (QString::fromUtf8 (file.readAll ()), scriptfile)); file.close (); } else { RK_DEBUG (PHP, DL_DEBUG, "Script file %s is already compiled", qPrintable (scriptfile)); } engine->evaluate (compiled_includes[scriptfile]); if (engine->hasUncaughtException ()) { compiled_includes.remove (scriptfile); return false; } return true; } } #endif #include "qtscriptbackend.moc" rkward-0.6.4/rkward/dataeditor/0000755000175000017500000000000012633754363016011 5ustar thomasthomasrkward-0.6.4/rkward/dataeditor/rkeditor.cpp0000664000175000017500000000245212633754363020345 0ustar thomasthomas/*************************************************************************** rkeditor - description ------------------- begin : Fri Aug 20 2004 copyright : (C) 2004 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkeditor.h" #include "../debug.h" RKEditor::RKEditor (QWidget *parent) : RKMDIWindow (parent, RKMDIWindow::DataEditorWindow) { RK_TRACE (EDITOR); } RKEditor::~RKEditor () { RK_TRACE (EDITOR); // getObject ()->setObjectOpened (this, false); } #include "rkeditor.moc" rkward-0.6.4/rkward/dataeditor/rkeditordataframepart.rc0000664000175000017500000000253512633754363022725 0ustar thomasthomas &Edit rkward-0.6.4/rkward/dataeditor/rkvareditmodel.cpp0000664000175000017500000007541112633754363021543 0ustar thomasthomas/*************************************************************************** rkvareditmodel - description ------------------- begin : Mon Nov 05 2007 copyright : (C) 2007, 2010, 2011, 2012, 2014 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkvareditmodel.h" #include #include #include #include #include "../core/rcontainerobject.h" #include "../core/rkmodificationtracker.h" #include "../core/rkrownames.h" #include "../rbackend/rinterface.h" #include "../rkglobals.h" #include "../debug.h" RKVarEditModel::RKVarEditModel (QObject *parent) : RKVarEditModelBase (parent), RObjectListener (RObjectListener::DataModel) { RK_TRACE (EDITOR); meta_model = 0; trailing_rows = trailing_cols = 0; var_col_offset = 0; edit_blocks = 0; rownames = 0; header_locked = false; duplicate_check_triggered = false; reset_scheduled = false; addNotificationType (RObjectListener::ObjectRemoved); addNotificationType (RObjectListener::MetaChanged); addNotificationType (RObjectListener::DataChanged); } RKVarEditModel::~RKVarEditModel () { RK_TRACE (EDITOR); for (int i = objects.size () - 1; i >= 0; --i) stopListenForObject (objects[i]); } RKVariable* RKVarEditModel::getObject (int index) const { RK_TRACE (EDITOR); if (index >= trueCols ()) { RK_ASSERT (false); return 0; } return objects[index]; } void RKVarEditModel::addObject (int index, RKVariable* object) { RK_TRACE (EDITOR); RK_ASSERT (object); if ((index < 0) || (index >= objects.size ())) index = objects.size (); beginInsertColumns (QModelIndex (), index, index); if (meta_model) meta_model->beginAddDataObject (index); if (object->isPending () && (!object->getLength ())) object->setLength (trueRows ()); // probably we just created it ourselves listenForObject (object); objects.insert (index, object); if (meta_model) meta_model->endAddDataObject (); endInsertColumns (); checkDuplicates (); } void RKVarEditModel::objectRemoved (RObject* object) { RK_TRACE (EDITOR); int index = objects.indexOf (static_cast (object)); // no check for isVariable needed. we only need to look up, if we have this object, and where. if (index < var_col_offset) { if (index < 0) return; // e.g. the data.frame object // the rownames object should only be deleted, when the whole data.frame is gone) RK_ASSERT (objects.size () <= var_col_offset); } beginRemoveColumns (QModelIndex (), index, index); if (meta_model) meta_model->beginRemoveDataObject (index); stopListenForObject (objects.takeAt (index)); if (meta_model) meta_model->endRemoveDataObject (); endRemoveColumns (); if (objects.size () <= var_col_offset) emit (modelDepleted ()); // editor may or may want to auto-destruct } void RKVarEditModel::checkDuplicates () { RK_TRACE (EDITOR); if (duplicate_check_triggered) return; duplicate_check_triggered = true; QTimer::singleShot (0, this, SLOT (checkDuplicatesNow())); } void RKVarEditModel::checkDuplicatesNow () { RK_TRACE (EDITOR); duplicate_check_triggered = false; QStringList dupes; for (int i = var_col_offset; i < objects.size (); ++i) { QString name = objects[i]->getShortName (); for (int j = i+1; j < objects.size (); ++j) { if (objects[j]->getShortName () == name) { if (objects[i]->getFullName () == objects[j]->getFullName ()) { dupes.append (objects[i]->getFullName ()); j = objects.size (); // break } } } } if (!dupes.isEmpty ()) emit (hasDuplicates (dupes)); } void RKVarEditModel::objectMetaChanged (RObject* changed) { RK_TRACE (EDITOR); int cindex = objects.indexOf (static_cast (changed)); // no check for isVariable needed. we only need to look up, if we have this object, and where. if (cindex < 0) return; // none of our buisiness if (meta_model) meta_model->objectMetaChanged (cindex); } void RKVarEditModel::scheduleReset () { RK_TRACE (EDITOR); if (!reset_scheduled) { reset_scheduled = true; QTimer::singleShot (0, this, SLOT (doResetNow())); #if QT_VERSION >= 0x040600 beginResetModel (); #endif } } void RKVarEditModel::doResetNow () { RK_TRACE (EDITOR); RK_ASSERT (reset_scheduled); reset_scheduled = false; #if QT_VERSION >= 0x040600 endResetModel (); #else reset (); #endif } void RKVarEditModel::objectDataChanged (RObject* object, const RObject::ChangeSet *changes) { RK_TRACE (EDITOR); int cindex = objects.indexOf (static_cast (object)); // no check for isVariable needed. we only need to look up, if we have this object, and where. if (cindex < 0) return; // none of our buisiness RK_ASSERT (changes); if (changes->full_reset) { scheduleReset (); return; } emit (dataChanged (index (changes->from_index, cindex), index (changes->to_index, cindex))); } void RKVarEditModel::doInsertColumns (int, int) { RK_TRACE (EDITOR); RK_ASSERT (false); // should be implemented in a subclass, or never called } RKVarEditMetaModel* RKVarEditModel::getMetaModel () { RK_TRACE (EDITOR); if (!meta_model) meta_model = new RKVarEditMetaModel (this); return meta_model; } bool RKVarEditModel::insertRows (int row, int count, const QModelIndex& parent) { RK_TRACE (EDITOR); if (edit_blocks || parent.isValid () || objects.isEmpty () || (row > apparentRows ())) { RK_ASSERT (false); return false; } if (row > trueRows ()) row = trueRows (); int lastrow = row+count - 1; RK_ASSERT (row >= 0); RK_ASSERT (lastrow >= row); beginInsertRows (QModelIndex (), row, row+count-1); doInsertRowsInBackend (row, count); for (int i=0; i < objects.size (); ++i) { // TODO: this does not emit any data change notifications to other editors objects[i]->insertRows (row, count); } endInsertRows (); return true; } bool RKVarEditModel::removeRows (int row, int count, const QModelIndex& parent) { RK_TRACE (EDITOR); int lastrow = row+count - 1; if (edit_blocks || parent.isValid () || objects.isEmpty () || (lastrow >= (apparentRows ()))) { RK_ASSERT (false); return false; } if (lastrow >= objects[0]->getLength ()) lastrow = objects[0]->getLength () - 1; RK_ASSERT (row >= 0); RK_ASSERT (lastrow >= row); beginRemoveRows (QModelIndex (), row, lastrow); doRemoveRowsInBackend (row, lastrow - row + 1); for (int i=0; i < objects.size (); ++i) { // TODO: this does not emit any data change notifications to other editors objects[i]->removeRows (row, lastrow); } endRemoveRows (); return true; } void RKVarEditModel::doInsertRowsInBackend (int, int) { RK_TRACE (EDITOR); // TODO: implement RK_ASSERT (false); } void RKVarEditModel::doRemoveRowsInBackend (int, int) { RK_TRACE (EDITOR); // TODO: implement RK_ASSERT (false); } int RKVarEditModel::rowCount (const QModelIndex& parent) const { RK_TRACE (EDITOR); if (parent.isValid ()) return 0; if (objects.isEmpty ()) { RK_ASSERT (false); return 0; } return (apparentRows ()); } int RKVarEditModel::columnCount (const QModelIndex& parent) const { RK_TRACE (EDITOR); if (parent.isValid ()) return 0; return (apparentCols ()); } QVariant RKVarEditModel::data (const QModelIndex& index, int role) const { RK_TRACE (EDITOR); if (!index.isValid ()) return QVariant (); int row = index.row (); int col = index.column (); if ((col >= apparentCols ()) || (row >= apparentRows ())) { RK_ASSERT (false); return QVariant (); } if (col < var_col_offset) { // TODO: make this look more like a normal header if (role == Qt::ForegroundRole) return (Qt::blue); } // on a trailing row / col if ((col >= objects.size ()) || (row >= objects[0]->getLength ())) { if (role == Qt::BackgroundRole) return (Qt::gray); if (role == Qt::ToolTipRole) { if (col >= objects.size ()) return (i18n ("Type on these fields to add new columns")); else return (i18n ("Type on these fields to add new rows")); } return QVariant (); } // a normal cell RKVariable *var = objects[col]; RK_ASSERT (var); if (role == Qt::EditRole) return var->getText (row, false); RKVariable::Status status = var->cellStatus (row); if (role == Qt::DisplayRole) { if (status == RKVariable::ValueUnused) return QString (""); return var->getText (row, true); } if (role == Qt::BackgroundRole) { if (status == RKVariable::ValueInvalid) return (Qt::red); } else if (role == Qt::ToolTipRole) { if (status == RKVariable::ValueInvalid) return (i18n ("This value is not allowed, here")); } if ((role == Qt::ForegroundRole) && ((status == RKVariable::ValueUnknown) || (status == RKVariable::ValueUnused))) return (Qt::lightGray); if (role == Qt::TextAlignmentRole) { if (var->getAlignment () == RKVariable::AlignCellLeft) return ((int) Qt::AlignLeft | Qt::AlignVCenter); else return ((int) Qt::AlignRight | Qt::AlignVCenter); } return QVariant (); } Qt::ItemFlags RKVarEditModel::flags (const QModelIndex& index) const { RK_TRACE (EDITOR); Qt::ItemFlags flags = 0; if (!index.isValid ()) return flags; int row = index.row (); int col = index.column (); if ((col >= apparentCols ()) || (row >= apparentRows ())) { RK_ASSERT (false); return flags; } if ((col < var_col_offset) && header_locked) return flags; if (!edit_blocks) flags |= Qt::ItemIsEditable | Qt::ItemIsEnabled; if ((col < objects.size ()) && (row < objects[0]->getLength ())) flags |= Qt::ItemIsSelectable; return flags; } bool RKVarEditModel::setData (const QModelIndex& index, const QVariant& value, int role) { RK_TRACE (EDITOR); if (!index.isValid ()) return false; int row = index.row (); int col = index.column (); if (edit_blocks || (role != Qt::EditRole) || (col >= apparentCols ()) || (row >= apparentRows ())) { RK_ASSERT (false); return false; } if (col >= objects.size ()) { // trailing col // somebody should add a column for us doInsertColumns (objects.size (), 1); if (col >= objects.size ()) { // apparently, no column has been added in the above signal return false; } } if (row >= objects[0]->getLength ()) { // trailing row insertRows (objects[0]->getLength (), 1); } // edit of normal cells RKVariable* var = objects[col]; RK_ASSERT (var); var->setText (row, value.toString ()); return true; } QVariant RKVarEditModel::headerData (int section, Qt::Orientation orientation, int role) const { RK_TRACE (EDITOR); if (orientation == Qt::Horizontal) { if (role == Qt::DisplayRole) { if (section >= objects.size ()) return i18n ("#New Variable#"); if (section < var_col_offset) return i18n ("Row names"); return (QString::number (section - var_col_offset + 1)); } else if (role == Qt::BackgroundRole) { if ((section < objects.size ()) && objects[section]->hasInvalidFields ()) return QVariant (Qt::red); } else if ((role == Qt::ToolTipRole) || (role == Qt::StatusTipRole)) { if ((section < objects.size ()) && objects[section]->hasInvalidFields ()) return i18n ("This column contains one or more invalid fields"); } } else { if ((role == Qt::DisplayRole) && (section < rownames->getLength ())) { return rownames->getText (section); } } return QVariant (); } RKTextMatrix RKVarEditModel::getTextMatrix (const QItemSelectionRange& range) const { RK_TRACE (EDITOR); if ((!range.isValid ()) || objects.isEmpty ()) return RKTextMatrix (); // NOTE: of course, when the range is small, this is terribly inefficient. On the other hand, it doesn't really matter, then, either. QItemSelectionRange erange = range.intersected (QItemSelectionRange (index (0, 0), index (trueRows () - 1, trueCols () - 1))); int top = erange.top (); int bottom = erange.bottom (); int left = erange.left (); int right = erange.right (); RKTextMatrix ret; int tcol = 0; for (int col = left; col <= right; ++col) { QString* data = objects[col]->getCharacter (top, bottom); RK_ASSERT (data); ret.setColumn (tcol, data, bottom - top + 1); delete [] data; ++tcol; } return ret; } void RKVarEditModel::blankRange (const QItemSelectionRange& range) { RK_TRACE (EDITOR); if ((!range.isValid ()) || objects.isEmpty ()) return; // NOTE: of course, when the range is small, this is terribly inefficient. On the other hand, it doesn't really matter, then, either. QItemSelectionRange erange = range.intersected (QItemSelectionRange (index (0, 0), index (trueRows () - 1, trueCols () - 1))); int top = erange.top (); int bottom = erange.bottom (); int left = erange.left (); int right = erange.right (); for (int col = left; col <= right; ++col) { RKVariable* var = objects[col]; for (int row = top; row <= bottom; ++row) { var->setText (row, QString ()); } } } void RKVarEditModel::setTextMatrix (const QModelIndex& offset, const RKTextMatrix& text, const QItemSelectionRange& confine_to) { RK_TRACE (EDITOR); // NOTE: of course, when the range is small, this is terribly inefficient. On the other hand, it doesn't really matter, then, either. Single cells will be set using setData() if ((!offset.isValid ()) || text.isEmpty ()) return; int top = offset.row (); int left = offset.column (); int bottom = top + text.numRows () - 1; int right = left + text.numColumns () - 1; if (confine_to.isValid ()) { if (confine_to.top () > top) return; // can't confine top-left. Should have been set by caller if (confine_to.left () > left) return; bottom = qMin (confine_to.bottom (), bottom); right = qMin (confine_to.right (), right); } // TODO: some models might not support column addition. if (right >= trueCols ()) doInsertColumns (objects.size (), right - trueCols () + 1); RK_ASSERT (right < trueCols ()); int current_rows = objects[0]->getLength (); if (bottom >= current_rows) insertRows (current_rows, bottom - current_rows + 1); int tcol = 0; for (int col = left; col <= right; ++col) { RKVariable* var = objects[col]; int trow = 0; for (int row = top; row <= bottom; ++row) { var->setText (row, text.getText (trow, tcol)); ++trow; } ++tcol; } } void RKVarEditModel::restoreObject (RObject* object, RCommandChain* chain) { RK_TRACE (EDITOR); RK_ASSERT (objects.contains (static_cast (object))); static_cast (object)->restore (chain); } /////////////////// RKVarEditMetaModel //////////////////////// RKVarEditMetaModel::RKVarEditMetaModel (RKVarEditModel* data_model) : RKVarEditModelBase (data_model) { RK_TRACE (EDITOR); RK_ASSERT (data_model); RKVarEditMetaModel::data_model = data_model; } RKVarEditMetaModel::~RKVarEditMetaModel () { RK_TRACE (EDITOR); } void RKVarEditMetaModel::beginAddDataObject (int index) { RK_TRACE (EDITOR); beginInsertColumns (QModelIndex (), index, index); } void RKVarEditMetaModel::endAddDataObject () { RK_TRACE (EDITOR); endInsertColumns (); } void RKVarEditMetaModel::beginRemoveDataObject (int index) { RK_TRACE (EDITOR); beginRemoveColumns (QModelIndex (), index, index); } void RKVarEditMetaModel::endRemoveDataObject () { RK_TRACE (EDITOR); endRemoveColumns (); } void RKVarEditMetaModel::objectMetaChanged (int atcolumn) { RK_TRACE (EDITOR); emit (dataChanged (index (0, atcolumn), index (RowCount - 1, atcolumn))); emit (headerDataChanged (Qt::Horizontal, atcolumn, atcolumn)); } int RKVarEditMetaModel::rowCount (const QModelIndex& parent) const { RK_TRACE (EDITOR); if (parent.isValid ()) return 0; return RowCount; } int RKVarEditMetaModel::columnCount (const QModelIndex& parent) const { RK_TRACE (EDITOR); return (data_model->columnCount (parent)); } QVariant RKVarEditMetaModel::data (const QModelIndex& index, int role) const { RK_TRACE (EDITOR); if (!index.isValid ()) return QVariant (); int row = index.row (); int col = index.column (); if ((col >= data_model->apparentCols ()) || (row >= RowCount)) { RK_ASSERT (false); return QVariant (); } if (col < var_col_offset) { if (role == Qt::BackgroundRole) return (Qt::lightGray); return QVariant (); } // on a trailing col if (col >= data_model->objects.size ()) { if (role == Qt::BackgroundRole) return (Qt::gray); if (role == Qt::ToolTipRole) return (i18n ("Type on these fields to add new columns")); return QVariant (); } if ((role == Qt::DisplayRole) || (role == Qt::EditRole)) { RKVariable *var = data_model->objects[col]; RK_ASSERT (var); if (row == NameRow) return var->getShortName (); if (row == LabelRow) return var->getLabel (); if (row == TypeRow) { if (role == Qt::EditRole) return QString::number (var->getDataType ()); return RObject::typeToText (var->getDataType ()); } if (row == FormatRow) return var->getFormattingOptionsString (); if (row == LevelsRow) return var->getValueLabelString (); } if ((role == Qt::FontRole) && (row == NameRow)) { QFont font; font.setBold (true); return (font); } return QVariant (); } Qt::ItemFlags RKVarEditMetaModel::flags (const QModelIndex& index) const { RK_TRACE (EDITOR); Qt::ItemFlags flags = 0; if (!index.isValid ()) return flags; int row = index.row (); int col = index.column (); if ((col >= data_model->apparentCols ()) || (row >= RowCount)) { RK_ASSERT (false); return flags; } if (col < var_col_offset) return flags; if (!data_model->edit_blocks) flags |= Qt::ItemIsEditable | Qt::ItemIsEnabled; if ((col < data_model->objects.size ()) && (row < RowCount)) flags |= Qt::ItemIsSelectable; return flags; } bool RKVarEditMetaModel::setData (const QModelIndex& index, const QVariant& value, int role) { RK_TRACE (EDITOR); if (!index.isValid ()) return false; int row = index.row (); int col = index.column (); if (data_model->edit_blocks || (role != Qt::EditRole) || (col >= data_model->apparentCols ()) || (row >= RowCount)) { RK_ASSERT (false); return false; } if (col < var_col_offset) { RK_ASSERT (false); return false; } if (col >= data_model->objects.size ()) { // trailing col // somebody should add a column for us data_model->doInsertColumns (data_model->objects.size (), 1); if (col >= data_model->objects.size ()) { // apparently, no column has been added in the above signal return false; } } // edit of normal cells RKVariable* var = data_model->objects[col]; RK_ASSERT (var); if (row == NameRow) { if (var->getShortName () != value.toString ()) { if (!var->canRename ()) return false; if (var->parentObject ()->isContainer ()) { RKGlobals::tracker ()->renameObject (var, static_cast (var->parentObject ())->validizeName (value.toString ())); } else return false; } } else if (row == LabelRow) { var->setLabel (value.toString (), true); } else if (row == TypeRow) { var->setVarType ((RObject::RDataType) value.toInt ()); } else if (row == FormatRow) { var->setFormattingOptionsString (value.toString ()); } else if (row == LevelsRow) { if (value.toString () != var->getValueLabelString ()) { var->setValueLabelString (value.toString ()); } } return true; } RObject::ValueLabels RKVarEditMetaModel::getValueLabels (int column) const { RK_TRACE (EDITOR); if (column >= trueCols ()) return RObject::ValueLabels (); return (getObject (column)->getValueLabels ()); } void RKVarEditMetaModel::setValueLabels (int column, const RObject::ValueLabels& labels) { RK_TRACE (EDITOR); if (column >= data_model->apparentCols ()) { RK_ASSERT (false); return; } if (column >= trueCols ()) { data_model->doInsertColumns (trueCols (), 1); } RKVariable* var = getObject (column); RK_ASSERT (var); var->setValueLabels (labels); } QVariant RKVarEditMetaModel::headerData (int section, Qt::Orientation orientation, int role) const { RK_TRACE (EDITOR); if (orientation == Qt::Horizontal) { return data_model->headerData (section, orientation, role); } if (role == Qt::DisplayRole) { if (section == NameRow) return (i18n ("Name")); if (section == LabelRow) return (i18n ("Label")); if (section == TypeRow) return (i18n ("Type")); if (section == FormatRow) return (i18n ("Format")); if (section == LevelsRow) return (i18n ("Levels")); } if (role == Qt::ToolTipRole) { if (section == NameRow) return (i18n ("Edit these fields to rename variables.")); if (section == LabelRow) return (i18n ("A descriptive label for each column (optional).")); if (section == TypeRow) return (i18n ("Type of data.")); if (section == FormatRow) return (i18n ("Double click on these fields to customize data display.")); if (section == LevelsRow) return (i18n ("Double click on these fields to edit factor levels.")); } return QVariant (); } RKTextMatrix RKVarEditMetaModel::getTextMatrix (const QItemSelectionRange& range) const { RK_TRACE (EDITOR); if ((!range.isValid ()) || data_model->objects.isEmpty ()) return RKTextMatrix (); // NOTE: of course, when the range is small, this is terribly inefficient. On the other hand, it doesn't really matter, then, either. QItemSelectionRange erange = range.intersected (QItemSelectionRange (index (0, 0), index (trueRows () - 1, trueCols () - 1))); int top = erange.top (); int bottom = erange.bottom (); int left = erange.left (); int right = erange.right (); RKTextMatrix ret; int tcol = 0; for (int col = left; col <= right; ++col) { int trow = 0; for (int row = top; row <= bottom; ++row) { QVariant celldata = data (index (row, col), Qt::EditRole); if (!celldata.isValid ()) { RK_ASSERT (false); break; } ret.setText (trow, tcol, celldata.toString ()); ++trow; } ++tcol; } return ret; } void RKVarEditMetaModel::blankRange (const QItemSelectionRange& range) { RK_TRACE (EDITOR); if ((!range.isValid ()) || data_model->objects.isEmpty ()) return; // NOTE: of course, when the range is small, this is terribly inefficient. On the other hand, it doesn't really matter, then, either. QItemSelectionRange erange = range.intersected (QItemSelectionRange (index (0, 0), index (trueRows () - 1, trueCols () - 1))); int top = erange.top (); int bottom = erange.bottom (); int left = erange.left (); int right = erange.right (); for (int col = left; col <= right; ++col) { RKVariable* var = data_model->objects[col]; var->lockSyncing (true); for (int row = top; row <= bottom; ++row) { setData (createIndex (row, col), QString (), Qt::EditRole); } var->lockSyncing (false); } } void RKVarEditMetaModel::setTextMatrix (const QModelIndex& offset, const RKTextMatrix& text, const QItemSelectionRange& confine_to) { RK_TRACE (EDITOR); if ((!offset.isValid ()) || text.isEmpty ()) return; int top = offset.row (); int left = offset.column (); int bottom = top + text.numRows () - 1; int right = left + text.numColumns () - 1; if (confine_to.isValid ()) { if (confine_to.top () > top) return; // can't confine top-left. Should have been set by caller if (confine_to.left () > left) return; bottom = qMin (confine_to.bottom (), bottom); right = qMin (confine_to.right (), right); } // TODO: some models might not support column addition. if (right >= trueCols ()) data_model->doInsertColumns (trueCols (), right - trueCols () + 1); RK_ASSERT (right < data_model->objects.size ()); bottom = qMin (bottom, trueRows () - 1); int tcol = 0; for (int col = left; col <= right; ++col) { int trow = 0; RKVariable* var = data_model->objects[col]; var->lockSyncing (true); for (int row = top; row <= bottom; ++row) { setData (index (row, col), text.getText (trow, tcol), Qt::EditRole); ++trow; } var->lockSyncing (false); ++tcol; } } /////////////////// RKVarEditDataFrameModel //////////////////////// RKVarEditDataFrameModel::RKVarEditDataFrameModel (RContainerObject* dataframe, QObject* parent) : RKVarEditModel (parent) { RK_TRACE (EDITOR); init (dataframe); } RKVarEditDataFrameModel::RKVarEditDataFrameModel (const QString& validized_name, RContainerObject* parent_object, RCommandChain* chain, int initial_cols, QObject* parent) : RKVarEditModel (parent) { RK_TRACE (EDITOR); RObject *object = parent_object->createPendingChild (validized_name, -1, true, true); RK_ASSERT (object->isDataFrame ()); RContainerObject* df = static_cast (object); // initialize the new object for (int i = 0; i < initial_cols; ++i) { RObject* child = df->createPendingChild (df->validizeName (QString ()), -1, false, false); RK_ASSERT (child->isVariable ()); // let's start with one (empty) row, to avoid confusion static_cast (child)->setLength (1); } init (df); // creates the pending object in the backend pushTable (chain); } void RKVarEditDataFrameModel::init (RContainerObject* dataframe) { RK_TRACE (EDITOR); RKVarEditDataFrameModel::dataframe = dataframe; trailing_rows = 1; trailing_cols = 1; addNotificationType (RObjectListener::ChildAdded); addNotificationType (RObjectListener::ChildMoved); listenForObject (dataframe); for (int i = 0; i < dataframe->numChildren (); ++i) { RObject* obj = dataframe->findChildByIndex (i); RK_ASSERT (obj); RK_ASSERT (obj->isVariable ()); addObject (i, static_cast (obj)); } rownames = dataframe->rowNames (); addObject (0, rownames); getMetaModel ()->var_col_offset = var_col_offset = 1; } RKVarEditDataFrameModel::~RKVarEditDataFrameModel () { RK_TRACE (EDITOR); if (dataframe) stopListenForObject (dataframe); } bool RKVarEditDataFrameModel::insertColumns (int column, int count, const QModelIndex& parent) { RK_TRACE (EDITOR); if (parent.isValid ()) { RK_ASSERT (false); return false; } if (column > trueCols ()) column = trueCols (); if (column < var_col_offset) column = var_col_offset; for (int col = column; col < (column + count); ++col) { RObject *obj = dataframe->createPendingChild (dataframe->validizeName (QString ()), col-var_col_offset); RK_ASSERT (obj->isVariable ()); // addObject (col, obj); // the object will be added via RKModificationTracker::addObject -> this::childAdded. That will also take care of calling beginInsertColumns()/endInsertColumns() RKGlobals::rInterface ()->issueCommand (new RCommand (".rk.data.frame.insert.column (" + dataframe->getFullName () + ", \"" + obj->getShortName () + "\", " + QString::number (col+1-var_col_offset) + ")", RCommand::App | RCommand::Sync)); } return true; } bool RKVarEditDataFrameModel::removeColumns (int column, int count, const QModelIndex& parent) { RK_TRACE (EDITOR); if (parent.isValid ()) { RK_ASSERT (false); return false; } if (column < var_col_offset) { RK_ASSERT (false); return false; } while ((column + count) > objects.size ()) --count; for (int i = column + count - 1; i >= column; --i) { // we start at the end so that the index remains valid RKGlobals::tracker ()->removeObject (objects[i]); // the comment in insertColumns, above: The object will be removed from our list in objectRemoved(). } return true; } void RKVarEditDataFrameModel::doInsertRowsInBackend (int row, int count) { RK_TRACE (EDITOR); // TODO: most of the time we're only adding one row at a time, still we should have a function to add multiple rows at once. for (int i = row; i < row + count; ++i) { RKGlobals::rInterface ()->issueCommand (new RCommand (".rk.data.frame.insert.row (" + dataframe->getFullName () + ", " + QString::number (i+1) + ')', RCommand::App | RCommand::Sync)); } } void RKVarEditDataFrameModel::doRemoveRowsInBackend (int row, int count) { RK_TRACE (EDITOR); for (int i = row + count - 1; i >= row; --i) { RKGlobals::rInterface ()->issueCommand (new RCommand (".rk.data.frame.delete.row (" + dataframe->getFullName () + ", " + QString::number (i+1) + ')', RCommand::App | RCommand::Sync)); } } void RKVarEditDataFrameModel::objectRemoved (RObject* object) { RK_TRACE (EDITOR); if (object == dataframe) { while (!objects.isEmpty ()) RKVarEditModel::objectRemoved (objects.last()); // NOTE: The rownames object (index position 0) must always go away last! stopListenForObject (dataframe); dataframe = 0; } RKVarEditModel::objectRemoved (object); // if the dataframe is gone, the editor will most certainly want to auto-destruct. // since the model will be taken down as well, this has to come last in the function. if (!dataframe) emit (modelObjectDestroyed ()); } void RKVarEditDataFrameModel::childAdded (int index, RObject* parent) { RK_TRACE (EDITOR); if (parent == dataframe) { RObject* child = dataframe->findChildByIndex (index); RK_ASSERT (child); if (child->isVariable ()) addObject (index + var_col_offset, static_cast (child)); else RK_ASSERT (false); } } void RKVarEditDataFrameModel::childMoved (int old_index, int new_index, RObject* parent) { RK_TRACE (EDITOR); if (parent == dataframe) { RObject *child = dataframe->findChildByIndex (new_index); // it's at the new position, already RK_ASSERT (objects.size () > (old_index + var_col_offset)); RK_ASSERT (child == objects[old_index + var_col_offset]); // if an object has changed position, there should be at least two objects left. Hence, the objectRemoved-call will never lead to editor destruction RK_ASSERT (objects.size () >= (var_col_offset + 2)); objectRemoved (child); RK_ASSERT (child->isVariable ()); addObject (new_index + var_col_offset, static_cast (child)); } else { // even though we are listening for the child objects as well, we should see move notifications // only for children of the parent. RK_ASSERT (false); } } void RKVarEditDataFrameModel::doInsertColumns (int index, int count) { RK_TRACE (EDITOR); insertColumns (index, count); } void RKVarEditDataFrameModel::pushTable (RCommandChain *sync_chain) { RK_TRACE (EDITOR); // first push real data QString command = dataframe->getFullName (); command.append (" <- data.frame ("); RK_ASSERT (objects.size ()); RKVariable* child = objects[0]; QString na_vector = "=as.numeric (rep (NA, " + QString::number (child->getLength ()) + "))"; for (int col=var_col_offset; col < objects.size (); ++col) { if (col > var_col_offset) command.append (", "); command.append (objects[col]->getShortName () + na_vector); } command.append (")"); // push all children RKGlobals::rInterface ()->issueCommand (new RCommand (command, RCommand::Sync), sync_chain); for (int col=0; col < objects.size (); ++col) { objects[col]->restore (sync_chain); } // now store the meta-data dataframe->writeMetaData (sync_chain); RKGlobals::rInterface ()->issueCommand (new RCommand (QString (), RCommand::Sync | RCommand::EmptyCommand | RCommand::ObjectListUpdate), sync_chain); } void RKVarEditDataFrameModel::restoreObject (RObject* object, RCommandChain* chain) { RK_TRACE (EDITOR); if (object == dataframe) { pushTable (chain); } else { RKVarEditModel::restoreObject (object, chain); } } #include "rkvareditmodel.moc" rkward-0.6.4/rkward/dataeditor/twintablemember.h0000664000175000017500000000505612633754363021353 0ustar thomasthomas/*************************************************************************** twintablemember.h - description ------------------- begin : Tue Oct 29 2002 copyright : (C) 2002, 2006, 2007, 2010, 2012 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef TWINTABLEMEMBER_H #define TWINTABLEMEMBER_H #include #include #include class TwinTable; class CellEditor; class RKVarEditModelBase; #include "../misc/rktableview.h" #include "rkeditor.h" /** One of the tables used in a TwinTable. @author Thomas Friedrichsmeier */ class TwinTableMember : public RKTableView { Q_OBJECT public: explicit TwinTableMember (QWidget *parent); ~TwinTableMember (); TwinTableMember *getTwin () { return twin; }; /** ends editing. Actually it's just a simple wrapper around QTable::endEdit () */ void stopEditing (); void copy (); void paste (RKEditor::PasteMode mode); void setRKModel (RKVarEditModelBase* model); int trueRows () const; // re-implemented from RKTableView int trueColumns () const; // re-implemented from RKTableView public slots: /** blanks out the currently selected cells (or the currently active cell, if there is no selection) */ void blankSelected (); signals: void contextMenuRequest (int row, int col, const QPoint& pos); protected: TwinTableMember *twin; bool updating_twin; /** reimplemented from QTableView to also adjust the twin */ void scrollContentsBy (int dx, int dy); RKVarEditModelBase* mymodel; bool rw; friend class TwinTable; void setTwin (TwinTableMember *new_twin); protected slots: void handleContextMenuRequest (const QPoint& pos); void updateColWidth (int section, int old_w, int new_w); void tableSelectionChanged (const QItemSelection& selected, const QItemSelection& deselected); }; #endif rkward-0.6.4/rkward/dataeditor/twintable.h0000664000175000017500000001124212633754363020155 0ustar thomasthomas/*************************************************************************** twintable.h - description ------------------- begin : Tue Oct 29 2002 copyright : (C) 2002, 2006, 2007, 2010, 2015 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef TWINTABLE_H #define TWINTABLE_H #include "rkeditor.h" #include #include #include #include "../core/rkmodificationtracker.h" class TwinTableMember; class RKVarEditModel; class QActionGroup; class KAction; /** *@author Thomas Friedrichsmeier */ class TwinTable : public RKEditor, public RObjectListener, public KXMLGUIClient { Q_OBJECT public: explicit TwinTable (QWidget *parent=0); ~TwinTable (); /** Pastes clipboard content to the current table */ void paste (RKEditor::PasteMode paste_mode); /** Clear the currently selected cells */ void clearSelected (); /** Flushes pending edit-operations */ void flushEdit (); void initTable (RKVarEditModel* model, RObject* object); RKVarEditModel* datamodel; QActionGroup* editActions () const { return edit_actions; }; public slots: void metaHeaderPressed (int section); void metaHeaderEntered (int section); void metaHeaderClicked (int section); void enableEditing (bool on); void showRownames (bool show); /** put the marked cells into the clipboard and remove them from the table */ void cut(); /** Copy selection in the current table to clipboard */ void copy(); /** paste the clipboard into the table, expanding the table as necessary */ void paste(); /** paste the clipboard into the table, but not beyond table boundaries */ void pasteToTable(); /** paste the clipboard into the table, but not beyond selection boundaries */ void pasteToSelection(); /** connected to RKVarEditModel::hasDuplicates() */ void containsDuplicates (const QStringList& dupes); private: int meta_header_anchor_section; /** read-write */ bool rw; /** If currently in one of the context menus, this holds, which table the mouse was pressed over (or 0) */ TwinTableMember* context_menu_table; /** Only valid, if context_menu_table != 0. Row of current context menu event. -1 for header row. -2 for no cell. */ int context_menu_row; /** Only valid, if context_menu_table != 0. Column of current context menu event. -1 for header column. -2 for no cell. */ int context_menu_column; protected: /** Returns the active Table (of the two members), 0 if no table active */ TwinTableMember *activeTable (); TwinTableMember* metaview; TwinTableMember* dataview; KAction* action_insert_col_left; KAction* action_delete_col; KAction* action_insert_row_above; KAction* action_delete_row; KAction* action_delete_rows; KAction* action_enable_editing; KAction* action_tb_lock_editing; KAction* action_tb_unlock_editing; KAction* action_show_rownames; KAction* editCut; KAction* editCopy; KAction* editPaste; KAction* editPasteToSelection; KAction* editPasteToTable; QActionGroup* edit_actions; /** receives object meta change notifications. This updates the caption */ void objectMetaChanged (RObject* changed); void initActions (); RObject* main_object; private slots: /** inserts a new column (NOTE the action connected to this signal carries the info, where the column is to be inserted) */ void insertColumn (); /** inserts a new row in the dataview (NOTE the action connected to this signal carries the info, where the column is to be inserted) */ void insertRow (); /** deletes the current row (in the dataview) */ void deleteRow (); /** deletes all marked rows (in the dataview) */ void deleteSelectedRows (); /** deletes the column at the current header_pos. Actually it does not really delete the column, but requests object-removal from the model, which will pass the request to RKModifcationTracker */ void deleteColumn (); /** handle context menu requests from the two members */ void contextMenu (int row, int col, const QPoint& pos); }; #endif rkward-0.6.4/rkward/dataeditor/twintablemember.cpp0000664000175000017500000001332712633754363021706 0ustar thomasthomas/*************************************************************************** twintablemember.cpp - description ------------------- begin : Tue Oct 29 2002 copyright : (C) 2002, 2006, 2007, 2009, 2010, 2012 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "twintablemember.h" #include #include #include #include "twintable.h" #include "rktextmatrix.h" #include "rkvareditmodel.h" #include "../debug.h" TwinTableMember::TwinTableMember (QWidget *parent) : RKTableView (parent){ RK_TRACE (EDITOR); rw = true; setVerticalScrollBarPolicy (Qt::ScrollBarAlwaysOn); setSelectionMode (QAbstractItemView::ContiguousSelection); verticalHeader ()->setContextMenuPolicy (Qt::CustomContextMenu); connect (verticalHeader (), SIGNAL (customContextMenuRequested(QPoint)), this, SLOT (handleContextMenuRequest(QPoint))); horizontalHeader ()->setContextMenuPolicy (Qt::CustomContextMenu); connect (horizontalHeader (), SIGNAL (customContextMenuRequested(QPoint)), this, SLOT (handleContextMenuRequest(QPoint))); setContextMenuPolicy (Qt::CustomContextMenu); connect (this, SIGNAL (customContextMenuRequested(QPoint)), this, SLOT (handleContextMenuRequest(QPoint))); updating_twin = false; connect (this, SIGNAL (blankSelectionRequest()), this, SLOT (blankSelected())); } TwinTableMember::~TwinTableMember(){ RK_TRACE (EDITOR); } void TwinTableMember::setRKModel (RKVarEditModelBase* model) { RK_TRACE (EDITOR); mymodel = model; setModel (model); // now we should also have a selectionModel() (but not before) connect (selectionModel (), SIGNAL (selectionChanged(QItemSelection,QItemSelection)), this, SLOT (tableSelectionChanged(QItemSelection,QItemSelection))); } void TwinTableMember::setTwin (TwinTableMember * new_twin) { RK_TRACE (EDITOR); twin = new_twin; // probably we only need this one way (metaview->dataview), but why not be safe, when it's so easy connect (twin->horizontalHeader (), SIGNAL (sectionResized(int,int,int)), this, SLOT (updateColWidth(int,int,int))); } void TwinTableMember::tableSelectionChanged (const QItemSelection& selected, const QItemSelection&) { RK_TRACE (EDITOR); RK_ASSERT (twin); if (!selected.isEmpty ()) twin->clearSelection (); } int TwinTableMember::trueColumns () const { return mymodel->trueCols (); } int TwinTableMember::trueRows () const { return mymodel->trueRows(); } void TwinTableMember::stopEditing () { RK_TRACE (EDITOR); // I wonder why Qt 4.3 doe not provide a function like this... if (state () != QAbstractItemView::EditingState) return; QModelIndex current = currentIndex (); setCurrentIndex (QModelIndex ()); setCurrentIndex (current); } void TwinTableMember::copy () { RK_TRACE (EDITOR); QItemSelectionRange range = getSelectionBoundaries (); if (range.isValid ()) { RKTextMatrix mat = mymodel->getTextMatrix (range); mat.copyToClipboard (); } } void TwinTableMember::blankSelected () { RK_TRACE (EDITOR); if (!rw) return; QItemSelectionRange range = getSelectionBoundaries (); if (range.isValid ()) mymodel->blankRange (range); } void TwinTableMember::paste (RKEditor::PasteMode mode) { RK_TRACE (EDITOR); if (!rw) return; RKTextMatrix pasted = RKTextMatrix::matrixFromClipboard (); QItemSelectionRange selrange = getSelectionBoundaries (); QItemSelectionRange limrange; if (mode == RKEditor::PasteToSelection) { limrange = selrange; } else if (mode == RKEditor::PasteToTable) { limrange = QItemSelectionRange (mymodel->index (0, 0), mymodel->index (mymodel->trueRows () - 1, mymodel->trueCols () - 1)); } // else: range not set means not confined = PasteAnywhere mymodel->setTextMatrix (selrange.topLeft (), pasted, limrange); } void TwinTableMember::scrollContentsBy (int dx, int dy) { RK_TRACE (EDITOR); if (updating_twin) return; updating_twin = true; RK_ASSERT (twin); QTableView::scrollContentsBy (dx, dy); twin->horizontalScrollBar ()->setValue (horizontalScrollBar ()->value ()); updating_twin = false; } void TwinTableMember::updateColWidth (int section, int, int new_w) { RK_TRACE (EDITOR); if (updating_twin) return; updating_twin = true; setColumnWidth (section, new_w); twin->setColumnWidth (section, new_w); updating_twin = false; } void TwinTableMember::handleContextMenuRequest (const QPoint& pos) { RK_TRACE (EDITOR); if (sender () == horizontalHeader ()) { int col = horizontalHeader ()->logicalIndexAt (pos); if (col >= 0) emit (contextMenuRequest (-1, col, horizontalHeader ()->mapToGlobal (pos))); } else if (sender () == verticalHeader ()) { int row = verticalHeader ()->logicalIndexAt (pos); if (row >= 0) emit (contextMenuRequest (row, -1, verticalHeader ()->mapToGlobal (pos))); } else { RK_ASSERT (sender () == this); int col = columnAt (pos.x ()); int row = rowAt (pos.y ()); if ((row < 0) || (col < 0)) { row = col = -2; // to differentiate from the headers, above } emit (contextMenuRequest (row, col, mapToGlobal (pos))); } } #include "twintablemember.moc" rkward-0.6.4/rkward/dataeditor/rkeditordataframe.h0000664000175000017500000000433112633754363021655 0ustar thomasthomas/*************************************************************************** rkeditordataframe - description ------------------- begin : Fri Aug 20 2004 copyright : (C) 2004, 2006 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKEDITORDATAFRAME_H #define RKEDITORDATAFRAME_H #include "rkeditor.h" #include "twintable.h" #include "../rbackend/rcommandreceiver.h" class TwinTable; class RCommand; class RKVariable; /** An RKEditor for data.frames. @author Thomas Friedrichsmeier */ class RKEditorDataFrame : public TwinTable, public RCommandReceiver { Q_OBJECT public: /** constructor. @param object an existing R object @param parent parent widget */ RKEditorDataFrame (RContainerObject* object, QWidget *parent); /** This constructor creates a new (empty) data.frame with the given name and then opens it for editing. @param new_object_name name of the new data.frame @param parent parent widget */ RKEditorDataFrame (const QString& new_object_name, QWidget *parent); /** destructor */ ~RKEditorDataFrame (); void flushChanges (); /** Tells the editor to restore the given object in the R-workspace from its copy of the data */ void restoreObject (RObject *object); private slots: void detachModel (); private: /// syncs the whole table. void pushTable (RCommandChain *sync_chain); void commonInit (); RCommandChain *open_chain; void waitForLoad (); protected: void rCommandDone (RCommand *command); }; #endif rkward-0.6.4/rkward/dataeditor/rktextmatrix.cpp0000664000175000017500000001334712633754363021275 0ustar thomasthomas/*************************************************************************** rktextmatrix - description ------------------- begin : Thu Nov 08 2007 copyright : (C) 2007, 2010, 2012 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rktextmatrix.h" #include #include #include #include "../debug.h" RKTextMatrix::RKTextMatrix () { RK_TRACE (EDITOR); clear (); } RKTextMatrix::RKTextMatrix (const RKTextMatrix& copy) { RK_TRACE (EDITOR); colcount = copy.colcount; rows = copy.rows; } RKTextMatrix::~RKTextMatrix () { RK_TRACE (EDITOR); } // static RKTextMatrix RKTextMatrix::matrixFromClipboard () { RK_TRACE (EDITOR); const QMimeData* data = QApplication::clipboard ()->mimeData (); // actually, we don't care, whether tsv or plain gets pasted - it's both // treated the same. We should however encourage external senders to // provided the two in order. if (data->hasFormat ("text/tab-separated-values")) { RK_DEBUG (EDITOR, DL_DEBUG, "paste tsv"); return (matrixFromSeparatedValues (QString::fromLocal8Bit (data->data ("text/tab-separated-values")))); } else if (data->hasText ()) { RK_DEBUG (EDITOR, DL_DEBUG, "paste plain text"); return (matrixFromSeparatedValues (data->text ())); } return RKTextMatrix (); } // static RKTextMatrix RKTextMatrix::matrixFromSeparatedValues (const QString& text, const QRegExp& tab, const QChar& brk) { RK_TRACE (EDITOR); RKTextMatrix ret; if (text.isEmpty ()) return ret; QStringList textrows = text.split (brk); if (textrows.last ().isEmpty ()) textrows.removeLast (); // some apps append a trailing line break for (int i = 0; i < textrows.size (); ++i) { QStringList split = textrows[i].split (tab); ret.appendRow (split); } return ret; } QString RKTextMatrix::toTabSeparatedValues () const { RK_TRACE (EDITOR); QString ret; for (int row = 0; row < rows.size (); ++row) { if (row) ret.append ('\n'); ret.append (rows[row].join (QChar ('\t'))); } return ret; } void RKTextMatrix::copyToClipboard () const { RK_TRACE (EDITOR); QString text = toTabSeparatedValues (); QMimeData* data = new QMimeData (); data->setText (text); data->setData ("text/tab-separated-values", text.toLocal8Bit ()); QApplication::clipboard()->setMimeData (data); } RKTextMatrix RKTextMatrix::transformed (bool reverse_h, bool reverse_v, bool transpose) const { RK_TRACE (EDITOR); RKTextMatrix ret; if (isEmpty ()) return ret; // empty matrices would violate some assumptions of the following code const int maxrow = rows.size () - 1; // for easier typing const int maxcol = rows[0].size () - 1; if (transpose) ret.upsize (maxcol, maxrow); // set dimensions from the start to save a few cycles else ret.upsize (maxrow, maxcol); for (int row=0; row <= maxrow; ++row) { for (int col=0; col <= maxcol; ++col) { int dest_row = row; if (reverse_v) dest_row = maxrow - row; int dest_col = col; if (reverse_h) dest_col = maxcol - col; if (transpose) { int dummy = dest_row; dest_row = dest_col; dest_col = dummy; } ret.setText (dest_row, dest_col, rows[row][col]); } } return ret; } void RKTextMatrix::setText (int row, int col, const QString& text) { // RK_TRACE (EDITOR); upsize (row, col); rows[row][col] = text; } void RKTextMatrix::setColumn (int column, const QString* textarray, int length) { RK_TRACE (EDITOR); upsize (length - 1, column); for (int i = 0; i < length; ++i) { rows[i][column] = textarray[i]; } } void RKTextMatrix::appendRow (const QStringList& row) { RK_TRACE (EDITOR); QStringList _row = row; while (colcount > _row.size ()) _row.append (QString ()); rows.append (_row); upsize (rows.size ()-1, row.size ()-1); } QString RKTextMatrix::getText (int row, int col) const { // RK_TRACE (EDITOR); if (row > rows.size ()) return QString (); if (col > colcount) return QString (); return (rows[row][col]); } QStringList RKTextMatrix::getColumn (int col) const { RK_TRACE (EDITOR); if (col > colcount) { return QStringList (); } QStringList ret; #if QT_VERSION >= 0x040700 ret.reserve (rows.size ()); #endif for (int i = 0; i < rows.size (); ++i) { ret.append (rows[i][col]); } return ret; } QStringList RKTextMatrix::getRow (int row) const { RK_TRACE (EDITOR); if (row >= rows.size ()) return (QStringList ()); RK_ASSERT (rows[row].size () == colcount); return (rows[row]); } void RKTextMatrix::clear () { RK_TRACE (EDITOR); rows.clear (); colcount = 0; } bool RKTextMatrix::isEmpty () const { RK_TRACE (EDITOR); if (rows.isEmpty() || (colcount == 0)) return true; return false; } void RKTextMatrix::upsize (int newmaxrow, int newmaxcol) { // RK_TRACE (EDITOR); while (newmaxrow >= rows.size ()) { QStringList list; for (int i = 0; i < colcount; ++i) list.append (QString ()); rows.append(list); } if (newmaxcol >= colcount) { for (int i = 0; i < rows.size (); ++i) { while (newmaxcol >= rows[i].size ()) rows[i].append (QString()); } colcount = newmaxcol + 1; } } rkward-0.6.4/rkward/dataeditor/rkvareditmodel.h0000664000175000017500000002340512633754363021204 0ustar thomasthomas/*************************************************************************** rkvareditmodel - description ------------------- begin : Mon Nov 05 2007 copyright : (C) 2007, 2010, 2011 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKVAREDITMODEL #define RKVAREDITMODEL #include #include #include #include "rktextmatrix.h" #include "../core/rkvariable.h" #include "../core/rkmodificationtracker.h" class RKVarEditMetaModel; class RCommandChain; class RKEditor; class RKRowNames; /** Base class for RKVarEditModel and RKVarEditMetaModel. Defines a common interface for copy and paste operations. Models might reimplement these functions for more efficiency. @author Thomas Friedrichsmeier */ class RKVarEditModelBase : public QAbstractTableModel { public: explicit RKVarEditModelBase (QObject *parent) : QAbstractTableModel (parent) {}; virtual ~RKVarEditModelBase () {}; virtual RKTextMatrix getTextMatrix (const QItemSelectionRange& range) const = 0; virtual void blankRange (const QItemSelectionRange& range) = 0; virtual void setTextMatrix (const QModelIndex& offset, const RKTextMatrix& text, const QItemSelectionRange& confine_to = QItemSelectionRange ()) = 0; virtual int trueRows () const = 0; virtual int trueCols () const = 0; int firstRealColumn () const { return var_col_offset; }; int var_col_offset; }; /** This class represents a collection of RKVariables of uniform length (typically a data.frame) suitable for editing in a model/view editor such as QTableView. Probably it will only ever support editing a single RKVariable, though, as it is not possible to ensure uniform length outside of a data.frame. For a data.frame use RKVarEditDataFrameModel . Since the real data storage is in RKVariable, it is ok (and recommended) to create separate models for separate editors/viewers, even if the objects in question are the same. */ class RKVarEditModel : public RKVarEditModelBase, public RObjectListener { Q_OBJECT public: explicit RKVarEditModel (QObject *parent); ~RKVarEditModel (); /** set the editor that is using this model. This is useful to find out, e.g. which window should be raised, when calling "Edit" on an object represented in this data-model. Also, the editor will be notified, if all objects in the model have been removed. */ void setEditor (RKEditor* editor) { myeditor = editor; }; /** see setEditor () */ RKEditor* getEditor () const { return myeditor; }; /** Add an object to the model at the given index. Calls this only once, unless from an RKVarEditDataFrameModel. You should add at least one object, **before** you add this model to any view. @param index position to insert at, or -1 to append the item @param object the objects to insert */ void addObject (int index, RKVariable* object); /** Return a pointer to the meta model. The meta model is created the first time this function is called. */ RKVarEditMetaModel* getMetaModel (); // QAbstractTableModel implementations bool insertRows (int row, int count, const QModelIndex& parent = QModelIndex()); bool removeRows (int row, int count, const QModelIndex& parent = QModelIndex()); int rowCount (const QModelIndex& parent = QModelIndex()) const; int columnCount (const QModelIndex& parent = QModelIndex()) const; QVariant data (const QModelIndex& index, int role = Qt::DisplayRole) const; Qt::ItemFlags flags (const QModelIndex& index) const; bool setData (const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); QVariant headerData (int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; RKTextMatrix getTextMatrix (const QItemSelectionRange& range) const; void blankRange (const QItemSelectionRange& range); void setTextMatrix (const QModelIndex& offset, const RKTextMatrix& text, const QItemSelectionRange& confine_to = QItemSelectionRange ()); int trueCols () const { return objects.size (); }; int trueRows () const { return (objects.isEmpty () ? 0 : objects[0]->getLength ()); }; void lockHeader (bool lock) { header_locked = lock; }; virtual void restoreObject (RObject* object, RCommandChain* chain); void objectMetaChanged (RObject* changed); void objectDataChanged (RObject* object, const RObject::ChangeSet *changes); RKVariable* getObject (int index) const; signals: void modelDepleted (); void hasDuplicates (const QStringList& dupes); private slots: void checkDuplicatesNow (); void doResetNow (); private: bool reset_scheduled; void scheduleReset (); protected: friend class RKVarEditMetaModel; QList objects; RKRowNames *rownames; /** very simple convenience function to return the number of true cols + trailing cols */ int apparentCols () const { return (trueCols () + trailing_cols); }; /** very simple convenience function to return the number of true rows + trailing rows */ int apparentRows () const { return (trueRows () + trailing_rows); }; /** Receives notifications of object removals. Takes care of removing the object from the list. */ void objectRemoved (RObject* object); /** insert new columns at index. Default implementation does nothing. To be implemented in subclasses */ virtual void doInsertColumns (int index, int count); virtual void doInsertRowsInBackend (int row, int count); virtual void doRemoveRowsInBackend (int row, int count); /** Check whether there are any duplicate names in the model. Actual check is delayed until the next iteration of the event loop. If a duplicate is found, hasDuplicates(const QStringList&) is emitted. */ void checkDuplicates (); bool duplicate_check_triggered; int trailing_rows; int trailing_cols; int edit_blocks; bool header_locked; RKVarEditMetaModel* meta_model; RKEditor* myeditor; }; /** Represents the meta information portion belonging to an RKVarEditModel. Implemented in a separate class for technical reasons, only (so this info can be displayed in a separate QTableView). This model mostly acts as a slave of an RKVarEditModel. You will not need to call any functions directly except from the RKVarEditModel, or an item view. */ class RKVarEditMetaModel : public RKVarEditModelBase { Q_OBJECT public: enum Rows { NameRow=0, LabelRow, TypeRow, FormatRow, LevelsRow, RowCount = LevelsRow + 1 }; // QAbstractTableModel implementations int rowCount (const QModelIndex& parent = QModelIndex()) const; int columnCount (const QModelIndex& parent = QModelIndex()) const; QVariant data (const QModelIndex& index, int role = Qt::DisplayRole) const; Qt::ItemFlags flags (const QModelIndex& index) const; bool setData (const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); QVariant headerData (int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; RKTextMatrix getTextMatrix (const QItemSelectionRange& range) const; void blankRange (const QItemSelectionRange& range); void setTextMatrix (const QModelIndex& offset, const RKTextMatrix& text, const QItemSelectionRange& confine_to = QItemSelectionRange ()); int trueCols () const { return data_model->trueCols (); }; int trueRows () const { return RowCount; }; RObject::ValueLabels getValueLabels (int column) const; void setValueLabels (int column, const RObject::ValueLabels& labels); RKVariable* getObject (int index) const { return data_model->getObject (index); }; RKVarEditModel* getDataModel () const { return data_model; }; protected: friend class RKVarEditModel; RKVarEditMetaModel (RKVarEditModel* data_model); ~RKVarEditMetaModel (); void beginAddDataObject (int index); void endAddDataObject (); void beginRemoveDataObject (int index); void endRemoveDataObject (); void objectMetaChanged (int atcolumn); RKVarEditModel* data_model; }; class RKVarEditDataFrameModel : public RKVarEditModel { Q_OBJECT public: RKVarEditDataFrameModel (RContainerObject* dataframe, QObject* parent); /** ctor that constructs a new empty data frame */ RKVarEditDataFrameModel (const QString& validized_name, RContainerObject* parent_object, RCommandChain* chain, int initial_cols, QObject* parent); ~RKVarEditDataFrameModel (); bool insertColumns (int column, int count, const QModelIndex& parent = QModelIndex()); bool removeColumns (int column, int count, const QModelIndex& parent = QModelIndex()); RContainerObject* getObject () const { return dataframe; }; void restoreObject (RObject* object, RCommandChain* chain); signals: void modelObjectDestroyed (); protected: void doInsertColumns (int index, int count); /** reimplemented from RKVarEditModel to listen for the dataframe object as well */ void objectRemoved (RObject* object); /** receives notifications of new objects added to this data.frame */ void childAdded (int index, RObject* parent); /** receives notifications of object position changes inside this data.frame */ void childMoved (int old_index, int new_index, RObject* parent); void doInsertRowsInBackend (int row, int count); void doRemoveRowsInBackend (int row, int count); RContainerObject* dataframe; void init (RContainerObject* dataframe); void pushTable (RCommandChain* sync_chain); }; #endif rkward-0.6.4/rkward/dataeditor/rktextmatrix.h0000664000175000017500000000576112633754363020743 0ustar thomasthomas/*************************************************************************** rktextmatrix - description ------------------- begin : Thu Nov 08 2007 copyright : (C) 2007, 2010, 2012 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKTEXTMATRIX_H #define RKTEXTMATRIX_H #include /** This class is meant to assist paste operations in tables. Most importantly, it provides methods to map to and from text/tab-separated-values format, and it does not hickup in case of ragged length data. @author Thomas Friedrichsmeier */ class RKTextMatrix { public: RKTextMatrix (); /** copy constructor. Since we're mostly just copying a QList (which is implicitely shared) and two ints, this is pretty fast. */ RKTextMatrix (const RKTextMatrix& copy); ~RKTextMatrix (); static RKTextMatrix matrixFromClipboard (); static RKTextMatrix matrixFromSeparatedValues (const QString& text, const QRegExp& tab=QRegExp ("\t"), const QChar& brk='\n'); QString toTabSeparatedValues () const; void copyToClipboard () const; void setText (int row, int col, const QString& text); /** set an entire column at once. This takes a copy of the data, so you will still have to delete it when done. TODO: convert to using QStringList */ void setColumn (int column, const QString* textarray, int length); /** set an entire row at once. */ void appendRow (const QStringList& row); QString getText (int row, int col) const; /** get the contents of an entire column at once. It's your responsibility to delete the data when done. The returned list has length numRows() */ QStringList getColumn (int col) const; /** get the contents of an entire row at once */ QStringList getRow (int row) const; /** Return a transformed matrix. Not optimized for performance! @param reverse_h Reverse order of columns @param reverse_v Reverse order of rows @param transpose Switch rows against columns */ RKTextMatrix transformed (bool reverse_h, bool reverse_v, bool transpose) const; void clear (); bool isEmpty () const; int numColumns () const { return colcount; } int numRows () const { return rows.size (); } private: QList rows; inline void upsize (int newmaxrow, int newmaxcol); int colcount; }; #endif rkward-0.6.4/rkward/dataeditor/CMakeLists.txt0000644000175000017500000000225012455741220020536 0ustar thomasthomasINCLUDE_DIRECTORIES( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${KDE4_INCLUDE_DIR} ${QT_INCLUDES} ) ########### next target ############### SET(dataeditor_STAT_SRCS twintable.cpp twintablemember.cpp rkeditor.cpp rkeditordataframe.cpp rkvareditmodel.cpp rktextmatrix.cpp ) QT4_AUTOMOC(${dataeditor_STAT_SRCS}) ADD_LIBRARY(dataeditor STATIC ${dataeditor_STAT_SRCS}) ########### install files ############### INSTALL(FILES rkeditordataframepart.rc DESTINATION ${DATA_INSTALL_DIR}/rkward ) #original Makefile.am contents follow: #INCLUDES = $(all_includes) #METASOURCES = AUTO #noinst_LIBRARIES = libdataeditor.a #libdataeditor_a_SOURCES = twintable.cpp twintablemember.cpp rkdrag.cpp \ # rkeditor.cpp rkeditordataframe.cpp twintabledatamember.cpp twintablemetamember.cpp \ # celleditor.cpp editlabelsdialog.cpp editformatdialog.cpp rkeditordataframepart.cpp #noinst_HEADERS = rkdrag.h twintable.h twintablemember.h rkeditor.h \ # rkeditordataframe.h twintabledatamember.h twintablemetamember.h celleditor.h \ # editlabelsdialog.h editformatdialog.h rkeditordataframepart.h # #rcdir = $(kde_datadir)/rkward #rc_DATA = rkeditordataframepart.rc rkward-0.6.4/rkward/dataeditor/rkeditor.h0000664000175000017500000000403312633754363020007 0ustar thomasthomas/*************************************************************************** rkeditor - description ------------------- begin : Fri Aug 20 2004 copyright : (C) 2004 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKEDITOR_H #define RKEDITOR_H #include #include "../core/robject.h" #include #include "../windows/rkmdiwindow.h" class RCommandChain; class RKDrag; /** Use as a base class for all widgets that can be used to display and edit RObjects of whatever type. // TODO: not sure we really need this any longer @author Thomas Friedrichsmeier */ class RKEditor : public RKMDIWindow { Q_OBJECT protected: RKEditor (QWidget *parent); virtual ~RKEditor (); public: /// flushes all pending edit operations and syncs the data to R. Implement in the child classes virtual void flushChanges () = 0; /// returns the object that is being edited in this editor RObject *getObject () { return object; }; enum PasteMode {PasteEverywhere, PasteToTable, PasteToSelection}; /** Tells the editor to restore the given object in the R-workspace from its copy of the data */ virtual void restoreObject (RObject *object) = 0; bool isModified () { return false; }; protected: RObject *object; }; #endif rkward-0.6.4/rkward/dataeditor/rkeditordataframe.cpp0000664000175000017500000001037412633754363022214 0ustar thomasthomas/*************************************************************************** rkeditordataframe - description ------------------- begin : Fri Aug 20 2004 copyright : (C) 2004, 2006, 2010 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkeditordataframe.h" #include #include #include "../rbackend/rinterface.h" #include "../rkglobals.h" #include "twintable.h" #include "twintablemember.h" #include "rkvareditmodel.h" #include "../core/robject.h" #include "../core/robjectlist.h" #include "../core/renvironmentobject.h" #include "../core/rcontainerobject.h" #include "../misc/rkdummypart.h" #include "../windows/rkworkplace.h" #include "../debug.h" #define LOAD_COMPLETE_COMMAND 1 // warning! numbers above GET_DATA_OFFSET are used to determine, which row, the data should go to! #define GET_DATA_OFFSET 10 RKEditorDataFrame::RKEditorDataFrame (RContainerObject* object, QWidget *parent) : TwinTable (parent) { RK_TRACE (EDITOR); commonInit (); RK_ASSERT (!object->isPending ()); RKEditor::object = object; RK_ASSERT (object->isDataFrame ()); setGlobalContextProperty ("current_object", object->getFullName()); RKVarEditDataFrameModel* model = new RKVarEditDataFrameModel (object, this); initTable (model, object); connect (model, SIGNAL (modelObjectDestroyed()), this, SLOT (detachModel())); waitForLoad (); } RKEditorDataFrame::RKEditorDataFrame (const QString& new_object_name, QWidget* parent) : TwinTable (parent) { RK_TRACE (EDITOR); commonInit (); QString valid = RObjectList::getGlobalEnv ()->validizeName (new_object_name); if (valid != new_object_name) KMessageBox::sorry (this, i18n ("The name you specified was already in use or not valid. Renamed to %1", valid), i18n ("Invalid Name")); RKVarEditDataFrameModel* model = new RKVarEditDataFrameModel (valid, RObjectList::getGlobalEnv (), open_chain, 5, this); RKEditor::object = model->getObject (); RK_ASSERT (object->isDataFrame ()); setGlobalContextProperty ("current_object", object->getFullName()); initTable (model, object); connect (model, SIGNAL (modelObjectDestroyed()), this, SLOT (deleteLater())); RKGlobals::rInterface ()->closeChain (open_chain); } void RKEditorDataFrame::commonInit () { RK_TRACE (EDITOR); setPart (new RKDummyPart (this, this)); getPart ()->insertChildClient (this); initializeActivationSignals (); open_chain = RKGlobals::rInterface ()->startChain (0); } RKEditorDataFrame::~RKEditorDataFrame () { RK_TRACE (EDITOR); } void RKEditorDataFrame::detachModel () { RK_TRACE (EDITOR); dataview->setRKModel (0); metaview->setRKModel (0); deleteLater (); } void RKEditorDataFrame::flushChanges () { RK_TRACE (EDITOR); flushEdit (); } void RKEditorDataFrame::waitForLoad () { RK_TRACE (EDITOR); flushEdit (); enableEditing (false); RCommand *command = new RCommand (QString (), RCommand::EmptyCommand | RCommand::Sync | RCommand::GetStringVector, QString (), this, LOAD_COMPLETE_COMMAND); RKGlobals::rInterface ()->issueCommand (command, open_chain); } void RKEditorDataFrame::rCommandDone (RCommand *command) { RK_TRACE (EDITOR); if (command->getFlags () == LOAD_COMPLETE_COMMAND) { RKGlobals::rInterface ()->closeChain (open_chain); open_chain = 0; enableEditing (true); } } void RKEditorDataFrame::restoreObject (RObject *object) { RK_TRACE (EDITOR); #ifdef __GNUC__ # warning TODO: this interface should be moved to the model for good. #endif datamodel->restoreObject (object, 0); } #include "rkeditordataframe.moc" rkward-0.6.4/rkward/dataeditor/twintable.cpp0000664000175000017500000004141312633754363020513 0ustar thomasthomas/*************************************************************************** twintable.cpp - description ------------------- begin : Tue Oct 29 2002 copyright : (C) 2002, 2006, 2007, 2010, 2015 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "twintable.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "twintablemember.h" #include "rkvareditmodel.h" #include "../core/rcontainerobject.h" #include "../misc/rkstandardicons.h" #include "../rkward.h" #include "../debug.h" TwinTable::TwinTable (QWidget *parent) : RKEditor (parent), RObjectListener (RObjectListener::Other), KXMLGUIClient () { RK_TRACE (EDITOR); main_object = 0; QVBoxLayout *layout = new QVBoxLayout(this); layout->setContentsMargins (0, 0, 0, 0); QSplitter *splitter = new QSplitter(this); splitter->setOrientation(Qt::Vertical); metaview = new TwinTableMember (splitter); splitter->setStretchFactor (splitter->indexOf (metaview), 0); metaview->verticalHeader ()->setResizeMode (QHeaderView::Fixed); metaview->setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOff); dataview = new TwinTableMember (splitter); splitter->setStretchFactor (splitter->indexOf (dataview), 1); dataview->verticalHeader ()->setResizeMode (QHeaderView::Fixed); dataview->horizontalHeader ()->hide (); dataview->setAlternatingRowColors (true); layout->addWidget (splitter); // these are to keep the two tables in sync metaview->setTwin (dataview); dataview->setTwin (metaview); // pressing the columns in the metaview-header should select columns in the dataview disconnect (metaview->horizontalHeader (), SIGNAL (sectionClicked(int))); connect (metaview->horizontalHeader (), SIGNAL (sectionClicked(int)), this, SLOT (metaHeaderClicked(int))); disconnect (metaview->horizontalHeader (), SIGNAL (sectionPressed(int))); connect (metaview->horizontalHeader (), SIGNAL (sectionPressed(int)), this, SLOT (metaHeaderPressed(int))); disconnect (metaview->horizontalHeader (), SIGNAL (sectionEntered(int))); connect (metaview->horizontalHeader (), SIGNAL (sectionEntered(int)), this, SLOT (metaHeaderEntered(int))); meta_header_anchor_section = -1; // catch header context menu requests connect (dataview, SIGNAL (contextMenuRequest(int,int,QPoint)), this, SLOT (contextMenu(int,int,QPoint))); connect (metaview, SIGNAL (contextMenuRequest(int,int,QPoint)), this, SLOT (contextMenu(int,int,QPoint))); context_menu_table = 0; context_menu_row = context_menu_column = -2; setXMLFile ("rkeditordataframepart.rc"); initActions (); setFocusPolicy (Qt::StrongFocus); } TwinTable::~TwinTable() { RK_TRACE (EDITOR); RK_ASSERT (main_object); stopListenForObject (main_object); // TODO: are the models auto-destructed? } void TwinTable::initActions () { RK_TRACE (EDITOR); editCut = actionCollection ()->addAction (KStandardAction::Cut, "cut", this, SLOT(cut())); editCut->setStatusTip (i18n("Cuts the selected section and puts it to the clipboard")); editCopy = actionCollection ()->addAction (KStandardAction::Copy, "copy", this, SLOT(copy())); editCopy->setStatusTip (i18n("Copies the selected section to the clipboard")); // editor->editActions ()->addAction (editCopy); // this is a read-only action, not an "edit" action editPaste = actionCollection ()->addAction (KStandardAction::Paste, "paste", this, SLOT(paste())); editPaste->setStatusTip (i18n("Pastes the clipboard contents to current position")); editPasteToTable = actionCollection ()->addAction ("paste_to_table", this, SLOT(pasteToTable())); editPasteToTable->setText (i18n("Paste inside table")); editPasteToTable->setIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionPasteInsideTable)); editPasteToTable->setStatusTip (i18n("Pastes the clipboard contents to current position, but not beyond the table's boundaries")); editPasteToSelection = actionCollection ()->addAction ("paste_to_selection", this, SLOT(pasteToSelection())); editPasteToSelection->setText (i18n("Paste inside selection")); editPasteToSelection->setIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionPasteInsideSelection)); editPasteToSelection->setStatusTip (i18n("Pastes the clipboard contents to current position, but not beyond the boundaries of the current selection")); // header menus action_insert_col_left = actionCollection ()->addAction ("insert_col_left", this, SLOT (insertColumn())); action_insert_col_left->setIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionInsertVar)); action_delete_col = actionCollection ()->addAction ("delete_col", this, SLOT (deleteColumn())); action_delete_col->setIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionDeleteVar)); action_insert_row_above = actionCollection ()->addAction ("insert_row_above", this, SLOT (insertRow())); action_insert_row_above->setIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionInsertRow)); action_delete_row = actionCollection ()->addAction ("delete_row", this, SLOT (deleteRow())); action_delete_row->setIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionDeleteRow)); action_delete_rows = actionCollection ()->addAction ("delete_rows", this, SLOT (deleteSelectedRows())); action_delete_rows->setIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionDeleteRow)); // global actions action_show_rownames = actionCollection ()->addAction ("show_rownames", this, SLOT (showRownames(bool))); action_show_rownames->setText ("Show / Edit row names"); action_show_rownames->setCheckable (true); action_enable_editing = actionCollection ()->addAction ("enable_editing", this, SLOT (enableEditing(bool))); action_enable_editing->setText ("Enable editing"); action_enable_editing->setCheckable (true); // these actually do the same thing, but are designed to work well in the toolbar QActionGroup *lockactions = new QActionGroup (this); lockactions->setExclusive (true); action_tb_lock_editing = new KToggleAction (i18nc ("verb: switch to read-only state. Make this short.", "Lock"), this); action_tb_lock_editing->setIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionLock)); action_tb_lock_editing->setActionGroup (lockactions); action_tb_lock_editing->setStatusTip (i18n ("Disable editing (to prevent accidental modification of data)")); actionCollection ()->addAction ("lock_editing", action_tb_lock_editing); action_tb_unlock_editing = new KToggleAction (i18nc ("verb: switch to read-write state. Make this short.", "Unlock"), this); action_tb_unlock_editing->setIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionUnlock)); action_tb_unlock_editing->setActionGroup (lockactions); action_tb_unlock_editing->setStatusTip (i18n ("Enable editing")); actionCollection ()->addAction ("unlock_editing", action_tb_unlock_editing); connect (action_tb_unlock_editing, SIGNAL (toggled(bool)), this, SLOT (enableEditing(bool))); // NOTE: No need to connect lock_editing, too, as they are radio-exclusive // add all edit-actions to a group, so they can be enabled/disabled easily edit_actions = new QActionGroup (this); edit_actions->addAction (editCut); edit_actions->addAction (editPaste); edit_actions->addAction (editPasteToTable); edit_actions->addAction (editPasteToSelection); edit_actions->addAction (action_insert_col_left); edit_actions->addAction (action_delete_col); edit_actions->addAction (action_insert_row_above); edit_actions->addAction (action_delete_row); edit_actions->addAction (action_delete_rows); enableEditing (true); } void TwinTable::initTable (RKVarEditModel* model, RObject* object) { RK_TRACE (EDITOR); datamodel = model; main_object = object; dataview->setRKModel (model); metaview->setRKModel (model->getMetaModel ()); model->setEditor (this); dataview->setRKItemDelegate (new RKItemDelegate (this, datamodel)); metaview->setRKItemDelegate (new RKItemDelegate (this, datamodel->getMetaModel ())); metaview->setMinimumHeight (metaview->horizontalHeader ()->height ()); metaview->setMaximumHeight (metaview->rowHeight (0) * 5 + metaview->horizontalHeader ()->height () + 5); dataview->verticalHeader ()->setFixedWidth (metaview->verticalHeader ()->width ()); showRownames (false); // init caption addNotificationType (RObjectListener::MetaChanged); listenForObject (object); objectMetaChanged (object); connect (model, SIGNAL (hasDuplicates(QStringList)), this, SLOT (containsDuplicates(QStringList))); } void TwinTable::containsDuplicates (const QStringList& dupes) { RK_TRACE (EDITOR); if (!rw) return; KMessageBox::informationList (this, i18n ("The editor '%1' contains the following duplicate columns. Editing this table may not be safe, and has been disabled. You may re-enable editing if you know what you are doing, but you are strongly advised to fix the table, and/or backup your data, first.", windowTitle ()), dupes, i18n ("Duplicate columns detected")); enableEditing (false); } void TwinTable::objectMetaChanged (RObject* changed) { RK_TRACE (EDITOR); RK_ASSERT (changed == main_object); QString caption = main_object->getShortName (); if (!rw) caption = i18n ("%1 [read-only]", caption); setCaption (caption); } void TwinTable::metaHeaderPressed (int section) { RK_TRACE (EDITOR); if (meta_header_anchor_section < 0) { meta_header_anchor_section = section; dataview->selectColumn (section); } dataview->setFocus (); } void TwinTable::metaHeaderClicked (int) { RK_TRACE (EDITOR); RK_ASSERT (meta_header_anchor_section >= 0); meta_header_anchor_section = -1; dataview->setFocus (); } void TwinTable::metaHeaderEntered (int section) { RK_TRACE (EDITOR); if (meta_header_anchor_section >= 0) { dataview->selectionModel ()->select (QItemSelection (datamodel->index (0, qMin (meta_header_anchor_section, section)), datamodel->index (0, qMax (meta_header_anchor_section, section))), QItemSelectionModel::Columns | QItemSelectionModel::Select); dataview->setFocus (); } } // TODO: handle situation when several entire cols are selected! void TwinTable::contextMenu (int row, int col, const QPoint& pos) { RK_TRACE (EDITOR); RK_ASSERT (context_menu_table == 0); context_menu_row = row; context_menu_column = col; QString container_name; if (sender () == metaview) { context_menu_table = metaview; if (row == -1) { // header action_insert_col_left->setEnabled (rw && (col >= datamodel->firstRealColumn ())); action_insert_col_left->setText (i18n ("Insert new variable left")); // TODO: show name action_delete_col->setEnabled (rw && (col >= datamodel->firstRealColumn ()) && (col < datamodel->trueCols ())); action_delete_col->setText (i18n ("Delete this variable")); // TODO: show name container_name = "top_header_menu"; } } else if (sender () == dataview) { context_menu_table = dataview; if (col == -1) { if (row >= 0) { RK_ASSERT (row <= datamodel->trueRows ()); action_insert_row_above->setText (i18n ("Insert new case above (at %1)", row + 1)); QItemSelectionRange sel = dataview->getSelectionBoundaries (); if (sel.isValid () && rw) { int top = sel.top (); int bottom = sel.bottom (); if (bottom >= datamodel->trueRows ()) bottom = datamodel->trueRows () - 1; action_delete_rows->setEnabled (top > bottom); if (top > bottom) bottom = top; action_delete_rows->setText (i18n ("Delete marked rows (%1-%2)", (top+1), (bottom+1))); } else { action_delete_rows->setEnabled (false); } action_delete_row->setEnabled (rw && (row < datamodel->trueRows ())); action_delete_row->setText (i18n ("Delete this row (%1)", (row+1))); container_name = "left_header_menu"; } } } else { RK_ASSERT (sender () == this); } if (container_name.isEmpty ()) { // none of the headers container_name = "general_context_menu"; } RK_ASSERT (factory ()); QMenu* menu = dynamic_cast (factory ()->container (container_name, this)); if (menu) { menu->exec (pos); } else { RK_ASSERT (false); // but may happen, if ui.rc-file was not found } context_menu_table = 0; context_menu_row = context_menu_column = -2; } void TwinTable::deleteColumn () { RK_TRACE (EDITOR); RK_ASSERT (rw); if (context_menu_table != metaview) { RK_ASSERT (false); return; } RK_ASSERT (context_menu_column >= datamodel->firstRealColumn ()); flushEdit (); datamodel->removeColumns (context_menu_column, 1); } void TwinTable::insertColumn () { RK_TRACE (EDITOR); RK_ASSERT (rw); if (context_menu_table != metaview) { RK_ASSERT (false); return; } RK_ASSERT (context_menu_column >= datamodel->firstRealColumn ()); flushEdit (); datamodel->insertColumns (context_menu_column, 1); } void TwinTable::deleteRow () { RK_TRACE (EDITOR); RK_ASSERT (rw); if (context_menu_table != dataview) { RK_ASSERT (false); return; } RK_ASSERT (context_menu_row > 0); flushEdit (); datamodel->removeRows (context_menu_row, 1); } void TwinTable::deleteSelectedRows () { RK_TRACE (EDITOR); RK_ASSERT (rw); if (context_menu_table != dataview) { RK_ASSERT (false); return; } QItemSelectionRange sel = dataview->getSelectionBoundaries (); if (sel.isValid ()) { int top = sel.top (); int bottom = sel.bottom (); if (bottom >= datamodel->trueRows ()) bottom = datamodel->trueRows () - 1; if (top > bottom) top = bottom; datamodel->removeRows (top, bottom - top + 1); } else { RK_ASSERT (false); } } void TwinTable::insertRow () { RK_TRACE (EDITOR); RK_ASSERT (rw); if (context_menu_table != dataview) { RK_ASSERT (false); return; } RK_ASSERT (context_menu_row > 0); flushEdit (); datamodel->insertRows (context_menu_row, 1); } void TwinTable::copy () { RK_TRACE (EDITOR); flushEdit (); TwinTableMember *table = activeTable (); if (!table) return; table->copy (); } void TwinTable::paste (RKEditor::PasteMode paste_mode) { RK_TRACE (EDITOR); if (!rw) return; flushEdit (); TwinTableMember *table = activeTable (); if (!table) return; RKWardMainWindow::getMain ()->slotSetStatusBarText (i18n ("Inserting clipboard contents...")); table->paste (paste_mode); RKWardMainWindow::getMain ()->slotSetStatusReady (); } void TwinTable::paste() { RK_TRACE (EDITOR); paste (PasteEverywhere); } void TwinTable::pasteToTable() { RK_TRACE (EDITOR); paste (PasteToTable); } void TwinTable::pasteToSelection() { RK_TRACE (EDITOR); paste (PasteToSelection); } TwinTableMember *TwinTable::activeTable () { RK_TRACE (EDITOR); if (metaview->hasFocus ()) { return metaview; } else if (dataview->hasFocus ()) { return dataview; } else { return 0; } } void TwinTable::clearSelected () { RK_TRACE (EDITOR); if (!rw) return; TwinTableMember *table = activeTable (); if (!table) return; table->blankSelected (); } void TwinTable::cut () { RK_TRACE (EDITOR); copy(); clearSelected (); } void TwinTable::flushEdit () { RK_TRACE (EDITOR); // flush pending edit operations metaview->stopEditing (); dataview->stopEditing (); } void TwinTable::enableEditing (bool on) { RK_TRACE (EDITOR); flushEdit (); rw = on; metaview->rw = rw; dataview->rw = rw; QPalette palette = metaview->palette (); if (on) palette.setColor (QPalette::Base, QApplication::palette ().color (QPalette::Active, QPalette::Base)); else palette.setColor (QPalette::Base, QApplication::palette ().color (QPalette::Disabled, QPalette::Base)); metaview->setPalette (palette); dataview->setPalette (palette); QAbstractItemView::EditTriggers triggers = QAbstractItemView::NoEditTriggers; if (rw) triggers = QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed | QAbstractItemView::AnyKeyPressed; metaview->setEditTriggers (triggers); dataview->setEditTriggers (triggers); edit_actions->setEnabled (rw); action_enable_editing->setChecked (rw); action_tb_unlock_editing->setChecked (rw); if (main_object) objectMetaChanged (main_object); // update_caption; } void TwinTable::showRownames (bool show) { RK_TRACE (EDITOR); RK_ASSERT (show == action_show_rownames->isChecked ()); metaview->setColumnHidden (0, !show); dataview->setColumnHidden (0, !show); datamodel->lockHeader (!show); } #include "twintable.moc" rkward-0.6.4/rkward/windows/0000755000175000017500000000000012633754364015364 5ustar thomasthomasrkward-0.6.4/rkward/windows/rkfilebrowser.h0000664000175000017500000000545112633754364020424 0ustar thomasthomas/*************************************************************************** rkfilebrowser - description ------------------- begin : Thu Apr 26 2007 copyright : (C) 2007, 2010, 2011 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKFILEBROWSER_H #define RKFILEBROWSER_H #include "rkmdiwindow.h" #include #include class KDirOperator; class RKFileBrowserWidget; class KUrlComboBox; class KFileItem; class KFileItemActions; class QMenu; class QAction; /** The file browser (tool) window. In order to save some startup time, the file browser is not really created until it is first shown. Hence, this is mostly just a wrapper around RKFileBrowserWidget */ class RKFileBrowser : public RKMDIWindow { Q_OBJECT public: RKFileBrowser (QWidget *parent, bool tool_window, const char *name=0); ~RKFileBrowser (); /** reimplemented to create the real file browser widget only when the file browser is shown for the first time */ void showEvent (QShowEvent *e); static RKFileBrowser *getMainBrowser() { return main_browser; }; public slots: void currentWDChanged (); private: RKFileBrowserWidget *real_widget; KVBox *layout_widget; friend class RKWardMainWindow; static RKFileBrowser *main_browser; }; /** The internal widget used in RKFileBrowser TODO: KDE4: check whether there is a ready widget for this. Much of the implementation is a modified copy from Kate / kdevelop. */ class RKFileBrowserWidget : public KVBox { Q_OBJECT public: explicit RKFileBrowserWidget (QWidget *widget); ~RKFileBrowserWidget (); void setURL (const QString &url); bool eventFilter (QObject* o, QEvent* e); public slots: void urlChangedInView (const KUrl &url); void urlChangedInCombo (const QString &url); void urlChangedInCombo (const KUrl &url); void fileActivated (const KFileItem& item); void saveConfig (); void contextMenuHook (const KFileItem &item, QMenu *menu); private: QList added_service_actions; KDirOperator *dir; KUrlComboBox *urlbox; KFileItemActions *fi_actions; }; #endif rkward-0.6.4/rkward/windows/rkworkplace.cpp0000664000175000017500000007341012633754364020423 0ustar thomasthomas/*************************************************************************** rkworkplace - description ------------------- begin : Thu Sep 21 2006 copyright : (C) 2006-2013 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkworkplace.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "detachedwindowcontainer.h" #include "rkcommandeditorwindow.h" #include "rkhtmlwindow.h" #include "rkworkplaceview.h" #include "rktoolwindowbar.h" #include "rktoolwindowlist.h" #include "../core/robject.h" #include "../core/rcontainerobject.h" #include "../core/robjectlist.h" #include "../dataeditor/rkeditor.h" #include "../dataeditor/rkeditordataframe.h" #include "../robjectviewer.h" #include "../settings/rksettingsmodulegeneral.h" #include "../settings/rksettingsmodulecommandeditor.h" #include "../rbackend/rinterface.h" #include "../windows/rkwindowcatcher.h" #include "../rbackend/rcommand.h" #include "../misc/rkcommonfunctions.h" #include "../rkglobals.h" #include "../rkward.h" #include "../debug.h" // static RKWorkplace *RKWorkplace::main_workplace = 0; RKWorkplace::RKWorkplace (QWidget *parent) : QWidget (parent) { RK_TRACE (APP); RK_ASSERT (main_workplace == 0); main_workplace = this; _workspace_config = 0; /* Splitter setup contains heavy copying from Kate's katemdi! */ KVBox *vbox = new KVBox (this); tool_window_bars[RKToolWindowList::Top] = new RKToolWindowBar (KMultiTabBar::Top, vbox); vert_splitter = new QSplitter (Qt::Vertical, vbox); vert_splitter->setOpaqueResize (KGlobalSettings::opaqueResize ()); tool_window_bars[RKToolWindowList::Top]->setSplitter (vert_splitter); KHBox *hbox = new KHBox (vert_splitter); vert_splitter->setCollapsible (vert_splitter->indexOf (hbox), false); vert_splitter->setStretchFactor (vert_splitter->indexOf (hbox), 1); tool_window_bars[RKToolWindowList::Left] = new RKToolWindowBar (KMultiTabBar::Left, hbox); horiz_splitter = new QSplitter (Qt::Horizontal, hbox); horiz_splitter->setOpaqueResize (KGlobalSettings::opaqueResize ()); tool_window_bars[RKToolWindowList::Left]->setSplitter (horiz_splitter); wview = new RKWorkplaceView (horiz_splitter); horiz_splitter->setCollapsible (horiz_splitter->indexOf (wview), false); horiz_splitter->setStretchFactor(horiz_splitter->indexOf (wview), 1); tool_window_bars[RKToolWindowList::Bottom] = new RKToolWindowBar (KMultiTabBar::Bottom, vbox); tool_window_bars[RKToolWindowList::Bottom]->setSplitter (vert_splitter); tool_window_bars[RKToolWindowList::Right] = new RKToolWindowBar (KMultiTabBar::Right, hbox); tool_window_bars[RKToolWindowList::Right]->setSplitter (horiz_splitter); KConfigGroup toolbar_config = KGlobal::config ()->group ("ToolwindowBars"); for (int i = 0; i < TOOL_WINDOW_BAR_COUNT; ++i) tool_window_bars[i]->restoreSize (toolbar_config); // now add it all to this widget QVBoxLayout *box = new QVBoxLayout (this); box->setContentsMargins (0, 0, 0, 0); box->addWidget (vbox); history = new RKMDIWindowHistory (this); connect (RKWardMainWindow::getMain (), SIGNAL (aboutToQuitRKWard()), this, SLOT (saveSettings())); } RKWorkplace::~RKWorkplace () { RK_TRACE (APP); delete _workspace_config; // closeAll (); // not needed, as the windows will autodelete themselves using QObject mechanism. Of course, closeAll () should be called *before* quitting. } QString workspaceConfigFileName (const KUrl &url) { QString base_name = QString (QCryptographicHash::hash (url.prettyUrl ().toUtf8 (), QCryptographicHash::Md5).toHex()); return (KStandardDirs::locateLocal ("data", "rkward/workspace_config_" + base_name)); } KConfigBase *RKWorkplace::workspaceConfig () { if (!_workspace_config) { RK_TRACE (APP); _workspace_config = new KConfig (workspaceConfigFileName (workspaceURL ())); } return _workspace_config; } QString RKWorkplace::portableUrl (const KUrl &url) { KUrl relative = KUrl::relativeUrl (workspaceURL (), url); relative.cleanPath (); return relative.prettyUrl (); } void RKWorkplace::setWorkspaceURL (const KUrl &url, bool keep_config) { RK_TRACE (APP); if (url != current_url) { current_url = url; if (keep_config && _workspace_config) { KConfig * _new_config = _workspace_config->copyTo (workspaceConfigFileName (workspaceURL ())); delete _workspace_config; _workspace_config = _new_config; } else { delete _workspace_config; _workspace_config = 0; } emit (workspaceUrlChanged (url)); } } void RKWorkplace::saveSettings () { RK_TRACE (APP); KConfigGroup toolbar_config = KGlobal::config ()->group ("ToolwindowBars"); for (int i = 0; i < TOOL_WINDOW_BAR_COUNT; ++i) tool_window_bars[i]->saveSize (toolbar_config); } void RKWorkplace::initActions (KActionCollection *ac, const char *left_id, const char *right_id) { RK_TRACE (APP); wview->initActions (ac, left_id, right_id); } void RKWorkplace::attachWindow (RKMDIWindow *window) { RK_TRACE (APP); RK_ASSERT (windows.contains (window)); // This should not happen for now. if (!window->isAttached ()) { QWidget *old_parent = window->parentWidget (); window->prepareToBeAttached (); if (old_parent && qobject_cast (old_parent)) { old_parent->deleteLater (); } } // all the rest is done, even if the window was previously "Attached", as this may also mean it was freshly created window->state = RKMDIWindow::Attached; if (window->isToolWindow ()) { if (!window->tool_window_bar) placeInToolWindowBar (window, RKToolWindowList::Bottom); else window->tool_window_bar->reclaimDetached (window); } else { view ()->addWindow (window); view ()->topLevelWidget ()->raise (); view ()->topLevelWidget ()->activateWindow (); } RK_ASSERT (window->getPart ()); RKWardMainWindow::getMain ()->partManager ()->addPart (window->getPart ()); } void RKWorkplace::detachWindow (RKMDIWindow *window, bool was_attached) { RK_TRACE (APP); if (!window) return; RK_ASSERT (windows.contains (window)); // Can't detach a window that is not registered window->prepareToBeDetached (); window->state = RKMDIWindow::Detached; RK_ASSERT (window->getPart ()); if (was_attached) { RKWardMainWindow::getMain ()->partManager ()->removePart (window->getPart ()); if (!window->isToolWindow ()) view ()->removeWindow (window); } DetachedWindowContainer *detached = new DetachedWindowContainer (window); detached->show (); if (!was_attached) window->activate (); } void RKWorkplace::addWindow (RKMDIWindow *window, bool attached) { RK_TRACE (APP); windows.append (window); connect (window, SIGNAL (destroyed(QObject*)), this, SLOT (removeWindow(QObject*))); connect (window, SIGNAL (windowActivated(RKMDIWindow*)), history, SLOT (windowActivated(RKMDIWindow*))); if (window->isToolWindow () && !window->tool_window_bar) return; if (attached) attachWindow (window); else detachWindow (window, false); } void RKWorkplace::placeToolWindows() { RK_TRACE (APP); foreach (const RKToolWindowList::ToolWindowRepresentation& rep, RKToolWindowList::registeredToolWindows ()) { placeInToolWindowBar (rep.window, rep.default_placement); getHistory ()->popLastWindow (rep.window); // windows send a spurious activation signal triggered from KPartsManager::addPart(), so we pop them, again } } void RKWorkplace::placeInToolWindowBar (RKMDIWindow *window, int position) { RK_TRACE (APP); RK_ASSERT (window->isToolWindow ()); bool needs_registration = (!window->tool_window_bar && (position != RKToolWindowList::Nowhere)); if ((position < 0) || (position >= TOOL_WINDOW_BAR_COUNT)) { RK_ASSERT (position == RKToolWindowList::Nowhere); // should never happen... position = RKToolWindowList::Nowhere; // ... but let's set this explicitly, in case of a broken workplace representation } if (position == RKToolWindowList::Nowhere) { if (window->tool_window_bar) window->tool_window_bar->removeWidget (window); } else { tool_window_bars[position]->addWidget (window); } if (!windows.contains (window)) addWindow (window, true); // first time we see this window else if (needs_registration) attachWindow (window); } bool RKWorkplace::openAnyUrl (const KUrl &url, const QString &known_mimetype, bool force_external) { RK_TRACE (APP); if (url.protocol () == "rkward") { if (RKHTMLWindow::handleRKWardURL (url)) return true; } KMimeType::Ptr mimetype; if (!known_mimetype.isEmpty ()) mimetype = KMimeType::mimeType (known_mimetype); else mimetype = KMimeType::findByUrl (url); if (!force_external) { // NOTE: Currently a known mimetype implies that the URL is local or served from the local machine. // Thus, external web pages are *not* opened, here. Which is the behavior we want, although the implementation is ugly if (mimetype->is ("text/html")) { openHelpWindow (url, true); return true; // TODO } if (url.fileName ().toLower ().endsWith (".rdata") || url.fileName ().toLower ().endsWith (".rda")) { RKWardMainWindow::getMain ()->askOpenWorkspace (url); return true; // TODO } if (mimetype->is ("text/plain")) { return (openScriptEditor (url, QString (), RKSettingsModuleCommandEditor::matchesScriptFileFilter (url.fileName()))); } RK_DEBUG (APP, DL_INFO, "Don't know how to handle mimetype %s.", qPrintable (mimetype->name ())); } if (KMessageBox::questionYesNo (this, i18n ("The url you are trying to open ('%1') is not a local file or the filetype is not supported by RKWard. Do you want to open the url in the default application?", url.prettyUrl ()), i18n ("Open in default application?")) != KMessageBox::Yes) { return false; } KRun *runner = new KRun (url, topLevelWidget()); // according to KRun-documentation, KRun will self-destruct when done. runner->setRunExecutables (false); return false; } RKMDIWindow* RKWorkplace::openScriptEditor (const KUrl &url, const QString& encoding, bool use_r_highlighting, bool read_only, const QString &force_caption, bool delete_on_close) { RK_TRACE (APP); // is this url already opened? if (!url.isEmpty ()) { RKWorkplaceObjectList script_windows = getObjectList (RKMDIWindow::CommandEditorWindow, RKMDIWindow::AnyWindowState); for (RKWorkplaceObjectList::const_iterator it = script_windows.constBegin (); it != script_windows.constEnd (); ++it) { KUrl ourl = static_cast (*it)->url (); if (url == ourl) { (*it)->activate (); return (*it); } } } RKCommandEditorWindow *editor = new RKCommandEditorWindow (view (), use_r_highlighting); if (!url.isEmpty ()) { if (!editor->openURL (url, encoding, use_r_highlighting, read_only, delete_on_close)) { delete editor; KMessageBox::messageBox (view (), KMessageBox::Error, i18n ("Unable to open \"%1\"", url.prettyUrl ()), i18n ("Could not open command file")); return 0; } } if (!force_caption.isEmpty ()) editor->setCaption (force_caption); addWindow (editor); return (editor); } RKMDIWindow* RKWorkplace::openHelpWindow (const KUrl &url, bool only_once) { RK_TRACE (APP); if (url.isEmpty ()) { RK_ASSERT (false); return 0; } if (only_once) { RKWorkplaceObjectList help_windows = getObjectList (RKMDIWindow::HelpWindow, RKMDIWindow::AnyWindowState); for (RKWorkplaceObjectList::const_iterator it = help_windows.constBegin (); it != help_windows.constEnd (); ++it) { if (static_cast (*it)->url ().equals (url, KUrl::CompareWithoutTrailingSlash | KUrl::CompareWithoutFragment)) { (*it)->activate (); return (*it); } } } RKHTMLWindow *hw = new RKHTMLWindow (view (), RKHTMLWindow::HTMLHelpWindow); hw->openURL (url); addWindow (hw); return (hw); } RKMDIWindow* RKWorkplace::openOutputWindow (const KUrl &url) { RK_TRACE (APP); RKHTMLWindow *w = RKOutputWindowManager::self ()->getCurrentOutputWindow (); if (!windows.contains (w)) { addWindow (w); } else { w->activate (); } return (w); } void RKWorkplace::newX11Window (WId window_to_embed, int device_number) { RK_TRACE (APP); RKCaughtX11Window *window = new RKCaughtX11Window (window_to_embed, device_number); window->state = RKMDIWindow::Detached; addWindow (window, false); } void RKWorkplace::newRKWardGraphisWindow (RKGraphicsDevice* dev, int device_number) { RK_TRACE (APP); RKCaughtX11Window *window = new RKCaughtX11Window (dev, device_number); window->state = RKMDIWindow::Detached; addWindow (window, false); } void RKWorkplace::newObjectViewer (RObject *object) { RK_TRACE (APP); RK_ASSERT (object); RKWorkplaceObjectList object_windows = getObjectList (RKMDIWindow::ObjectWindow, RKMDIWindow::AnyWindowState); for (RKWorkplaceObjectList::const_iterator it = object_windows.constBegin (); it != object_windows.constEnd (); ++it) { if (static_cast (*it)->object () == object) { (*it)->activate (); return; } } RObjectViewer *ov = new RObjectViewer (view (), object); addWindow (ov); } bool RKWorkplace::canEditObject (RObject *object) { RK_TRACE (APP); if (object->isDataFrame ()) { return true; } else if (object->isVariable () && object->parentObject ()->isDataFrame ()) { return true; } return false; } RKEditor* RKWorkplace::editNewDataFrame (const QString &name) { RK_TRACE (APP); RKEditorDataFrame* ed = new RKEditorDataFrame (name, 0); addWindow (ed); ed->activate (); return ed; } RKEditor *RKWorkplace::editObject (RObject *object) { RK_TRACE (APP); RK_ASSERT (object); RObject *iobj = object; RKEditor *ed = 0; RKEditor *existing_editor = object->editor (); if (!existing_editor) { if (!iobj->isDataFrame ()) { if (iobj->isVariable () && iobj->parentObject ()->isDataFrame ()) { iobj = iobj->parentObject (); } else { return 0; } } unsigned long size = 1; foreach (int dim, iobj->getDimensions ()) { size *= dim; } if ((RKSettingsModuleGeneral::warnLargeObjectThreshold () != 0) && (size > RKSettingsModuleGeneral::warnLargeObjectThreshold ())) { if (KMessageBox::warningContinueCancel (view (), i18n ("You are about to edit object \"%1\", which is very large (%2 fields). RKWard is not optimized to handle very large objects in the built in data editor. This will use a lot of memory, and - depending on your system - might be very slow. For large objects it is generally recommended to edit using command line means or to split into smaller chunks before editing. On the other hand, if you have enough memory, or the data is simple enough (numeric data is easier to handle, than factor), editing may not be a problem at all. You can configure this warning (or turn it off entirely) under Settings->Configure RKWard->General.\nReally edit object?", iobj->getFullName (), size), i18n ("About to edit very large object")) != KMessageBox::Continue) { return 0; } } ed = new RKEditorDataFrame (static_cast (iobj), 0); addWindow (ed); } else { ed = existing_editor; } ed->activate (); return ed; } void RKWorkplace::flushAllData () { RK_TRACE (APP); for (RKWorkplaceObjectList::const_iterator it = windows.constBegin (); it != windows.constEnd (); ++it) { if ((*it)->type == RKMDIWindow::DataEditorWindow) { static_cast (*it)->flushChanges (); } } } void RKWorkplace::closeWindow (RKMDIWindow *window) { RK_TRACE (APP); RK_ASSERT (windows.contains (window)); bool tool_window = window->isToolWindow (); window->close (true); // all the rest should happen in removeWindow () if (tool_window) windowRemoved (); // for regular windows, this happens in removeWindow(), already } void RKWorkplace::closeActiveWindow () { RK_TRACE (APP); RKMDIWindow *w = activeWindow (RKMDIWindow::Attached); if (w) closeWindow (w); } RKWorkplace::RKWorkplaceObjectList RKWorkplace::getObjectList (int type, int state) { RK_TRACE (APP); RKWorkplaceObjectList ret; for (RKWorkplaceObjectList::const_iterator it = windows.constBegin (); it != windows.constEnd (); ++it) { if (((*it)->type & type) && ((*it)->state & state)) { ret.append ((*it)); } } return ret; } void RKWorkplace::closeAll (int type, int state) { RK_TRACE (APP); RKWardMainWindow::getMain ()->lockGUIRebuild (true); RKWorkplaceObjectList list_to_close = getObjectList (type, state); for (RKWorkplaceObjectList::const_iterator it = list_to_close.constBegin (); it != list_to_close.constEnd (); ++it) { closeWindow (*it); } RKWardMainWindow::getMain ()->lockGUIRebuild (false); } void RKWorkplace::removeWindow (QObject *object) { RK_TRACE (APP); RKMDIWindow *window = static_cast (object); // remove from history first (otherwise, we might find it there, when trying to activate a new window) history->removeWindow (window); // WARNING: the window is dead. Don't call any functions on it. RK_ASSERT (windows.contains (window)); windows.removeAll (window); // do this first! view()->removeWindow will call activePage() indirectly from setCaption, causing us to iterate over all known windows! if (view ()->hasWindow (window)) view ()->removeWindow (window, true); windowRemoved (); } void RKWorkplace::windowRemoved () { RK_TRACE (APP); if (activeWindow (RKMDIWindow::AnyWindowState) != 0) return; // something already active // try to activate an attached document window, first RKMDIWindow *window = view ()->activePage (); if (window) { window->activate (true); return; } // some document window in the history? Try that. window = history->previousDocumentWindow (); if (window) { window->activate (true); return; } // now try to activate an attached (tool) window, if one is visible for (RKWorkplaceObjectList::const_iterator it = windows.constBegin (); it != windows.constEnd (); ++it) { if ((*it)->isAttached ()) { if ((*it)->isVisible ()) { (*it)->activate (true); return; } } } // nothing, yet? Try *any* visible window for (RKWorkplaceObjectList::const_iterator it = windows.constBegin (); it != windows.constEnd (); ++it) { if ((*it)->isVisible ()) { (*it)->activate (true); return; } } // give up } RKMDIWindow *RKWorkplace::activeWindow (RKMDIWindow::State state) { RK_TRACE (APP); RKMDIWindow *ret = 0; for (RKWorkplaceObjectList::const_iterator it = windows.constBegin (); it != windows.constEnd (); ++it) { if (!(state & ((*it)->state))) continue; if ((*it)->isActive ()) { ret = *it; break; } } return (ret); } QStringList RKWorkplace::makeWorkplaceDescription () { RK_TRACE (APP); QStringList workplace_description; // first, save the base directory of the workplace. This allows us to cope better with moved workspaces while restoring. KUrl base_url = workspaceURL (); base_url.setPath (base_url.directory ()); if (base_url.isLocalFile () && base_url.hasPath ()) workplace_description.append ("base::::" + base_url.url ()); // window order in the workplace view may have changed with respect to our list. Thus we first generate a properly sorted list RKWorkplaceObjectList list = getObjectList (RKMDIWindow::DocumentWindow, RKMDIWindow::Detached); for (int i=0; i < wview->count (); ++i) { list.append (static_cast (wview->widget (i))); } list.append (getObjectList (RKMDIWindow::ToolWindow, RKMDIWindow::AnyWindowState)); foreach (RKMDIWindow *win, list) { QString type, specification; QStringList params; if (win->isType (RKMDIWindow::DataEditorWindow)) { type = "data"; specification = static_cast (win)->getObject ()->getFullName (); } else if (win->isType (RKMDIWindow::CommandEditorWindow)) { type = "script"; specification = static_cast (win)->url ().url (); } else if (win->isType (RKMDIWindow::OutputWindow)) { type = "output"; specification = static_cast (win)->url ().url (); } else if (win->isType (RKMDIWindow::HelpWindow)) { type = "help"; specification = static_cast (win)->restorableUrl ().url (); } else if (win->isToolWindow ()) { type = RKToolWindowList::idOfWindow (win); } if (!type.isEmpty ()) { if (!win->isAttached ()) { params.append (QString ("detached,") + QString::number (win->x ()) + ',' + QString::number (win->y ()) + ',' + QString::number (win->width ()) + ',' + QString::number (win->height ())); } if (win->isToolWindow ()) { int sidebar = RKToolWindowList::Nowhere; for (int i = 0; i < TOOL_WINDOW_BAR_COUNT; ++i) { if (win->tool_window_bar == tool_window_bars[i]) { sidebar = i; break; } } params.append (QString ("sidebar,") + QString::number (sidebar)); } workplace_description.append (type + "::" + params.join (":") + "::" + specification); } } return workplace_description; } void RKWorkplace::saveWorkplace (RCommandChain *chain) { RK_TRACE (APP); // TODO: This is still a mess. All workplace-related settings, including the workspaceConfig(), should be saved to a single place, and in // standard KConfig format. if (RKSettingsModuleGeneral::workplaceSaveMode () != RKSettingsModuleGeneral::SaveWorkplaceWithWorkspace) return; RKGlobals::rInterface ()->issueCommand ("rk.save.workplace(description=" + RObject::rQuote (makeWorkplaceDescription().join ("\n")) + ')', RCommand::App, i18n ("Save Workplace layout"), 0, 0, chain); } void RKWorkplace::restoreWorkplace (RCommandChain *chain, bool merge) { RK_TRACE (APP); if (RKSettingsModuleGeneral::workplaceSaveMode () != RKSettingsModuleGeneral::SaveWorkplaceWithWorkspace) return; QString no_close_windows; if (merge) no_close_windows = "close.windows = FALSE"; RKGlobals::rInterface ()->issueCommand ("rk.restore.workplace(" + no_close_windows + ')', RCommand::App, i18n ("Restore Workplace layout"), 0, 0, chain); } KUrl checkAdjustRestoredUrl (const QString &_url, const QString old_base) { KUrl url (_url); if (old_base.isEmpty ()) return (url); KUrl new_base_url = RKWorkplace::mainWorkplace ()->workspaceURL (); new_base_url.setPath (new_base_url.directory ()); if (new_base_url.isEmpty ()) return (url); KUrl old_base_url (old_base); if (old_base_url == new_base_url) return (url); // TODO: Should we also care about non-local files? In theory: yes, but stat'ing remote files for existence can take a long time. if (!(old_base_url.isLocalFile () && new_base_url.isLocalFile () && url.isLocalFile ())) return (url); // if the file exists, unadjusted, return it. if (QFileInfo (url.toLocalFile ()).exists ()) return (url); // check whether a file exists for the adjusted url KUrl relative = KUrl::fromLocalFile (new_base_url.path () + '/' + KUrl::relativePath (old_base_url.path (), url.path ())); relative.cleanPath (); // if (QFileInfo (relative.toLocalFile ()).exists ()) return (relative); return (url); } void RKWorkplace::restoreWorkplace (const QStringList &description) { RK_TRACE (APP); RKWardMainWindow::getMain ()->lockGUIRebuild (true); QString base; for (int i = 0; i < description.size (); ++i) { // Item format for rkward <= 0.5.4: "type:specification" // Item format for rkward <= 0.5.5: "type::[optional_params1[:optional_params2[:...]]]::specification" int typeend = description[i].indexOf (':'); if ((typeend < 0) || (typeend >= (description[i].size () - 1))) { RK_ASSERT (false); continue; } QString type, specification; QStringList params; type = description[i].left (typeend); if (description[i].at (typeend + 1) == ':') { // rkward 0.5.5 or later int specstart = description[i].indexOf ("::", typeend + 2); if (specstart < typeend) { RK_ASSERT (false); continue; } params = description[i].mid (typeend + 2, specstart - typeend - 2).split (':', QString::SkipEmptyParts); specification = description[i].mid (specstart + 2); } else { specification = description[i].mid (typeend + 1); } RKMDIWindow *win = 0; if (type == "base") { RK_ASSERT (i == 0); base = specification; } else if (type == "data") { RObject *object = RObjectList::getObjectList ()->findObject (specification); if (object) win = editObject (object); } else if (type == "script") { win = openScriptEditor (checkAdjustRestoredUrl (specification, base)); } else if (type == "output") { win = openOutputWindow (checkAdjustRestoredUrl (specification, base)); } else if (type == "help") { win = openHelpWindow (checkAdjustRestoredUrl (specification, base), true); } else { win = RKToolWindowList::findToolWindowById (type); RK_ASSERT (win); } // apply generic window parameters if (win) { for (int p = 0; p < params.size (); ++p) { if (params[p].startsWith ("sidebar")) { int position = params[p].section (',', 1).toInt (); placeInToolWindowBar (win, position); } if (params[p].startsWith ("detached")) { QStringList geom = params[p].split (','); win->hide (); win->setGeometry (geom.value (1).toInt (), geom.value (2).toInt (), geom.value (3).toInt (), geom.value (4).toInt ()); detachWindow (win); } } } } RKWardMainWindow::getMain ()->lockGUIRebuild (false); } ///////////////////////// END RKWorkplace //////////////////////////// ///////////////////// BEGIN RKMDIWindowHistory /////////////////////// #include "../misc/rkstandardicons.h" #include class RKMDIWindowHistoryWidget : public QListWidget { public: RKMDIWindowHistoryWidget () : QListWidget (0) { RK_TRACE (APP); current = 0; setFocusPolicy (Qt::StrongFocus); setWindowFlags (Qt::Popup); } ~RKMDIWindowHistoryWidget () { RK_TRACE (APP); } void update (const QList windows) { RK_TRACE (APP); clear (); _windows = windows; for (int i = windows.count () - 1; i >= 0; --i) { // most recent top RKMDIWindow *win = windows[i]; QListWidgetItem *item = new QListWidgetItem (this); item->setIcon (RKStandardIcons::iconForWindow (win)); item->setText (win->windowTitle ()); } if (current >= count ()) current = count () - 1; if (current < 0) { hide (); return; } setCurrentRow (current); } void next () { RK_TRACE (APP); if (--current < 0) current = count () - 1; setCurrentRow (current); } void prev () { RK_TRACE (APP); if (++current >= count ()) current = 0; setCurrentRow (current); } private: void focusOutEvent (QFocusEvent *) { RK_TRACE (APP); deleteLater (); } void keyReleaseEvent (QKeyEvent *ev) { RK_TRACE (APP); if (ev->modifiers () == Qt::NoModifier) { commit (); } } void mouseReleaseEvent (QMouseEvent *ev) { RK_TRACE (APP); // HACK to get by without slots, and the associated moc'ing QListWidget::mouseReleaseEvent (ev); commit (); } void commit () { RK_TRACE (APP); current = currentRow (); if ((current > 0) && (current < count ())) { RKMDIWindow *win = _windows.value (count () - 1 - current); RK_ASSERT (win); win->activate (true); } deleteLater (); } int current; QList _windows; }; RKMDIWindowHistory::RKMDIWindowHistory (QObject *parent) : QObject (parent) { RK_TRACE (APP); switcher = 0; } RKMDIWindowHistory::~RKMDIWindowHistory () { RK_TRACE (APP); RK_DEBUG (APP, DL_DEBUG, "Remaining windows in history: %d", recent_windows.count ()); } void RKMDIWindowHistory::windowActivated (RKMDIWindow *window) { RK_TRACE (APP); if (!window) return; if (!recent_windows.isEmpty () && (window == recent_windows.last ())) return; // update lists recent_windows.removeAll (window); // remove dupes recent_windows.append (window); updateSwitcher (); } void RKMDIWindowHistory::next (KAction* prev_action, KAction *next_action) { RK_TRACE (APP); if (recent_windows.isEmpty ()) return; getSwitcher (prev_action, next_action)->next (); } void RKMDIWindowHistory::prev (KAction* prev_action, KAction *next_action) { RK_TRACE (APP); if (recent_windows.isEmpty ()) return; getSwitcher (prev_action, next_action)->prev (); } RKMDIWindow* RKMDIWindowHistory::previousDocumentWindow () { RK_TRACE (APP); for (int i = recent_windows.count () - 1; i >= 0; --i) { if (!recent_windows[i]->isToolWindow ()) return (recent_windows[i]); } return 0; } void RKMDIWindowHistory::updateSwitcher () { RK_TRACE (APP); if (switcher) switcher->update (recent_windows); } void RKMDIWindowHistory::removeWindow (RKMDIWindow *window) { RK_TRACE (APP); recent_windows.removeAll (window); updateSwitcher (); } RKMDIWindowHistoryWidget* RKMDIWindowHistory::getSwitcher (KAction* prev_action, KAction *next_action) { RK_TRACE (APP); if (switcher) return switcher; switcher = new RKMDIWindowHistoryWidget (); connect (switcher, SIGNAL (destroyed(QObject*)), this, SLOT (switcherDestroyed())); switcher->addAction (prev_action); switcher->addAction (next_action); switcher->update (recent_windows); switcher->show (); QWidget *act = QApplication::activeWindow (); if (act) { int center_x = act->x () + act->width () / 2; int center_y = act->y () + act->height () / 2; switcher->move (center_x - switcher->width () / 2, center_y - switcher->height () / 2); } else { RK_ASSERT (false); } switcher->setFocus (); return switcher; } void RKMDIWindowHistory::switcherDestroyed () { RK_TRACE (APP); RK_ASSERT (switcher); switcher = 0; } void RKMDIWindowHistory::popLastWindow (RKMDIWindow* match) { RK_TRACE (APP); if (recent_windows.isEmpty ()) return; else if (recent_windows.last () == match) recent_windows.removeLast (); updateSwitcher (); } #include "rkworkplace.moc" rkward-0.6.4/rkward/windows/rktoplevelwindowgui.rc0000644000175000017500000000310212455741221022020 0ustar thomasthomas &Window &Activate &Settings &Help rkward-0.6.4/rkward/windows/rktoolwindowbar.cpp0000664000175000017500000002350412633754364021325 0ustar thomasthomas/*************************************************************************** rktoolwindowbar - description ------------------- begin : Fri Oct 12 2007 copyright : (C) 2007, 2011 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ /* This code is based substantially on kate's katemdi! */ #include "rktoolwindowbar.h" #include #include #include #include #include #include #include #include "rkworkplace.h" #include "rkworkplaceview.h" #include "rkmdiwindow.h" #include "../rkward.h" #include "../misc/rkstandardicons.h" #include "../debug.h" #define DEFAULT_SPLITTER_SIZE 200 #define SPLITTER_MIN_SIZE 30 RKToolWindowBar::RKToolWindowBar (KMultiTabBarPosition position, QWidget *parent) : KMultiTabBar (position, parent), container (0) { RK_TRACE (APP); setStyle (KMultiTabBar::KDEV3ICON); last_known_size = SPLITTER_MIN_SIZE; } RKToolWindowBar::~RKToolWindowBar () { RK_TRACE (APP); } void RKToolWindowBar::restoreSize (const KConfigGroup &cg) { RK_TRACE (APP); last_known_size = cg.readEntry (QString ("view_size_%1").arg (position ()), DEFAULT_SPLITTER_SIZE); } void RKToolWindowBar::saveSize (KConfigGroup &cg) const { RK_TRACE (APP); cg.writeEntry (QString ("view_size_%1").arg (position ()), last_known_size); } int RKToolWindowBar::getSplitterSize () const { RK_TRACE (APP); int pos = splitter->indexOf (container); if (pos < 0) { RK_ASSERT (false); return 0; } return (splitter->sizes ()[pos]); } void RKToolWindowBar::setSplitterSize (int new_size) { RK_TRACE (APP); // HACK / WORKAROUND: reset the collapsed state of the container (if collapsed). Else we will not be able to open it again int index = splitter->indexOf (container); QList sizes = splitter->sizes (); if (sizes[index] == 0) { sizes[index] = last_known_size; splitter->setSizes (sizes); } if (splitter->orientation () == Qt::Horizontal) { container->resize (new_size, container->height ()); } else { container->resize (container->width (), new_size); } } void RKToolWindowBar::splitterMoved (int, int) { RK_TRACE (APP); int pos = getSplitterSize (); if (pos >= SPLITTER_MIN_SIZE) last_known_size = pos; if (!pos) { // collapsed. Hide it properly. for (QMap::const_iterator it = widget_to_id.constBegin (); it != widget_to_id.constEnd (); ++it) { if (isTabRaised (it.value ())) { hideWidget (it.key ()); break; } } } } void RKToolWindowBar::setSplitter (QSplitter *splitter) { RK_TRACE (APP); RK_ASSERT (!container); RKToolWindowBar::splitter = splitter; container = new KHBox (splitter); splitter->setContentsMargins (0, 0, 0, 0); container->layout ()->setContentsMargins (0, 0, 0, 0); container->layout ()->setSpacing (0); container->layout ()->setMargin (0); container->hide (); connect (splitter, SIGNAL(splitterMoved(int,int)), this, SLOT(splitterMoved(int,int))); } void RKToolWindowBar::addWidget (RKMDIWindow *window) { RK_TRACE (APP); RK_ASSERT (window); if (window->tool_window_bar == this) return; // may happen while restoring windows RK_ASSERT (container); static int id_count = 0; int id = ++id_count; if (window->tool_window_bar) { window->tool_window_bar->removeWidget (window); } appendTab (window->windowIcon ().pixmap (QSize (16, 16)), id, window->shortCaption ()); window->tool_window_bar = this; widget_to_id.insert (window, id); connect (tab (id), SIGNAL (clicked(int)), this, SLOT (tabClicked(int))); tab (id)->installEventFilter (this); if (window->isAttached ()) { window->hide(); window->setParent (container); } show (); } void RKToolWindowBar::reclaimDetached (RKMDIWindow *window) { RK_TRACE (APP); window->hide(); window->setParent (container); } void RKToolWindowBar::removeWidget (RKMDIWindow *widget) { RK_TRACE (APP); RK_ASSERT (widget_to_id.contains (widget)); int id = widget_to_id[widget]; bool was_active_in_bar = isTabRaised (id); removeTab (id); widget_to_id.remove (widget); widget->tool_window_bar = 0; if (widget->isAttached ()) { widget->setParent (0); widget->hide (); } if (was_active_in_bar) { RK_ASSERT (widget->isAttached ()); container->hide (); widget->active = false; } if (widget_to_id.isEmpty ()) hide (); } void RKToolWindowBar::showWidget (RKMDIWindow *widget) { RK_TRACE (APP); RK_ASSERT (widget_to_id.contains (widget)); int id = widget_to_id[widget]; // close any others for (QMap::const_iterator it = widget_to_id.constBegin (); it != widget_to_id.constEnd (); ++it) { RKMDIWindow *cur = it.key (); if (cur != widget) { if (cur->isAttached ()) { cur->active = false; cur->hide (); } setTab (it.value (), false); } } widget->show (); if (widget->isAttached ()) { setTab (id, true); container->show (); setSplitterSize (last_known_size); } else { widget->topLevelWidget ()->show (); widget->topLevelWidget ()->raise (); } widget->active = true; } void RKToolWindowBar::hideWidget (RKMDIWindow *widget) { RK_TRACE (APP); RK_ASSERT (widget_to_id.contains (widget)); // prevent recursion if (!widget->active) return; int id = widget_to_id[widget]; bool was_active_in_bar = ((widget->parent () == container) && widget->isVisible ()); if (was_active_in_bar) { container->hide (); } RKWardMainWindow::getMain()->partManager()->setActivePart (0); widget->active = false; widget->hide (); setTab (id, false); RKWorkplace::mainWorkplace ()->view ()->setFocus (); } void RKToolWindowBar::tabClicked (int id) { RK_TRACE (APP); RKMDIWindow *widget = idToWidget (id); RK_ASSERT (widget); if (widget->isActive ()) { if (!widget->isAttached ()) widget->close (false); else hideWidget (widget); } else { widget->activate (true); } } RKMDIWindow* RKToolWindowBar::idToWidget (int id) const { RK_TRACE (APP); for (QMap::const_iterator it = widget_to_id.constBegin (); it != widget_to_id.constEnd (); ++it) { if (it.value () == id) { return (it.key ()); } } return 0; } bool RKToolWindowBar::eventFilter (QObject *obj, QEvent *ev) { if (ev->type() == QEvent::ContextMenu) { RK_TRACE (APP); QContextMenuEvent *e = (QContextMenuEvent *) ev; KMultiTabBarTab *bt = dynamic_cast(obj); if (bt) { id_of_popup = bt->id (); RKMDIWindow *widget = idToWidget (id_of_popup); RK_ASSERT (widget); if (widget) { KMenu menu (this); QAction *a = menu.addAction (RKStandardIcons::getIcon (widget->isAttached () ? RKStandardIcons::ActionDetachWindow : RKStandardIcons::ActionAttachWindow), widget->isAttached () ? i18n("Detach") : i18n("Attach")); connect (a, SIGNAL (triggered(bool)), this, SLOT (changeAttachment())); KSelectAction *sel = new KSelectAction (i18n ("Position"), &menu); sel->addAction (KIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionMoveLeft)), i18n ("Left Sidebar")); sel->addAction (KIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionMoveRight)), i18n ("Right Sidebar")); sel->addAction (KIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionMoveUp)), i18n ("Top Sidebar")); sel->addAction (KIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionMoveDown)), i18n ("Bottom Sidebar")); sel->addAction (KIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionDelete)), i18n ("Not shown in sidebar")); connect (sel, SIGNAL (triggered(int)), this, SLOT (moveToolWindow(int))); menu.addAction (sel); menu.exec (e->globalPos()); return true; } } } return false; } void RKToolWindowBar::contextMenuEvent (QContextMenuEvent* event) { RK_TRACE (APP); KMenu menu (this); foreach (const RKToolWindowList::ToolWindowRepresentation& rep, RKToolWindowList::registeredToolWindows ()) { QAction *a = menu.addAction (rep.window->windowIcon (), rep.window->shortCaption ()); a->setCheckable (true); a->setChecked (rep.window->tool_window_bar == this); a->setData (rep.id); } connect (&menu, SIGNAL (triggered(QAction*)), this, SLOT (addRemoveToolWindow(QAction*))); menu.exec (event->globalPos ()); event->accept (); } void RKToolWindowBar::changeAttachment () { RK_TRACE (APP); RKMDIWindow *window = idToWidget (id_of_popup); RK_ASSERT (window); // toggle attachment if (window->isAttached ()) RKWorkplace::mainWorkplace ()->detachWindow (window); else RKWorkplace::mainWorkplace ()->attachWindow (window); } void RKToolWindowBar::moveToolWindow (int target) { RK_TRACE (APP); RK_ASSERT (target >= RKToolWindowList::Left); RK_ASSERT (target <= RKToolWindowList::Bottom); if (target == position ()) return; RKMDIWindow *window = idToWidget (id_of_popup); RK_ASSERT (window); RKWorkplace::mainWorkplace ()->placeInToolWindowBar (window, target); } void RKToolWindowBar::addRemoveToolWindow (QAction *action) { RK_TRACE (APP); RK_ASSERT (action); RKMDIWindow *win = RKToolWindowList::findToolWindowById (action->data ().toString ()); if (action->isChecked ()) { RKWorkplace::mainWorkplace ()->placeInToolWindowBar (win, position ()); } else { RK_ASSERT (win->tool_window_bar == this); removeWidget (win); } } #include "rktoolwindowbar.moc" rkward-0.6.4/rkward/windows/rkwindowcatcher.cpp0000664000175000017500000007170412633754364021301 0ustar thomasthomas/*************************************************************************** rwindowcatcher.cpp - description ------------------- begin : Wed May 4 2005 copyright : (C) 2005 - 2013 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkwindowcatcher.h" #ifndef DISABLE_RKWINDOWCATCHER #include #include #include #include #include #include #include "../rkwardapplication.h" #include "../settings/rksettingsmodulegraphics.h" #include "../dialogs/rkerrordialog.h" #include "rkworkplace.h" #include "../misc/rkstandardicons.h" #include "../debug.h" RKWindowCatcher::RKWindowCatcher () { RK_TRACE (MISC); } RKWindowCatcher::~RKWindowCatcher () { RK_TRACE (MISC); } void RKWindowCatcher::start (int prev_cur_device) { RK_TRACE (MISC); RK_DEBUG (RBACKEND, DL_DEBUG, "Window Catcher activated"); RKWardApplication::getApp ()->startWindowCreationDetection (); last_cur_device = prev_cur_device; } void RKWindowCatcher::stop (int new_cur_device) { RK_TRACE (MISC); RK_DEBUG (RBACKEND, DL_DEBUG, "Window Catcher deactivated"); WId w = RKWardApplication::getApp ()->endWindowCreationDetection (); if (new_cur_device != last_cur_device) { if (w) { RKWorkplace::mainWorkplace ()->newX11Window (w, new_cur_device); #if defined Q_WS_X11 // All this syncing looks like a bloody hack? Absolutely. It appears to work around the occasional error "figure margins too large" from R, though. qApp->processEvents (); qApp->syncX (); qApp->processEvents (); // this appears to have the side-effect of forcing the captured window to sync with X, which is exactly, what we're trying to achieve. KWindowInfo wininfo = KWindowSystem::windowInfo (w, NET::WMName | NET::WMGeometry); #endif } else { #if defined Q_WS_MAC KMessageBox::information (0, i18n ("You have tried to embed a new R graphics device window in RKWard. However, this is not currently supported in this build of RKWard on Mac OS X. See http://rkward.kde.org/mac for more information."), i18n ("Could not embed R X11 window"), "embed_x11_device_not_supported"); #else RKErrorDialog::reportableErrorMessage (0, i18n ("You have tried to embed a new R graphics device window in RKWard. However, either no window was created, or RKWard failed to detect the new window. If you think RKWard should have done better, consider reporting this as a bug. Alternatively, you may want to adjust Settings->Configure RKWard->Onscreen Graphics."), QString (), i18n ("Could not embed R X11 window"), "failure_to_detect_x11_device"); #endif } } last_cur_device = new_cur_device; } void RKWindowCatcher::updateHistory (QStringList params) { RK_TRACE (MISC); RK_ASSERT (params.count () >= 1); int history_length = params[0].toInt (); QStringList labels = params.mid (1, history_length); RK_ASSERT (((params.count () - history_length) % 2) == 1) for (int i = history_length + 1; i < (params.count () - 1); i += 2) { RKCaughtX11Window* window = RKCaughtX11Window::getWindow (params[i].toInt ()); if (window) { int position = params[i+1].toInt (); window->updateHistoryActions (history_length, position, labels); } else { RK_DEBUG (RBACKEND, DL_DEBUG, "Device %d is not managed, while trying to update history", params[i].toInt ()); } } } void RKWindowCatcher::killDevice (int device_number) { RK_TRACE (MISC); RKCaughtX11Window* window = RKCaughtX11Window::getWindow (device_number); if (window) { window->setKilledInR (); window->close (true); QApplication::syncX (); } } ///////////////////////////////// END RKWindowCatcher ////////////////////////////////// /**************************************************************************************/ //////////////////////////////// BEGIN RKCaughtX11Window ////////////////////////////// #include #include #ifdef Q_WS_WIN # include "../qwinhost/qwinhost.h" # include #elif defined Q_WS_X11 # include #endif #include #include #include #include #include #include #include #include #include #include "../rkglobals.h" #include "../rbackend/rinterface.h" #include "../rbackend/rkwarddevice/rkgraphicsdevice.h" #include "../core/robject.h" #include "../misc/rkprogresscontrol.h" #include "../misc/rksaveobjectchooser.h" #include "../plugin/rkcomponentcontext.h" // static QHash RKCaughtX11Window::device_windows; RKCaughtX11Window::RKCaughtX11Window (WId window_to_embed, int device_number) : RKMDIWindow (0, X11Window), RCommandReceiver () { RK_TRACE (MISC); commonInit (device_number); embedded = window_to_embed; #ifdef Q_WS_WIN // unfortunately, trying to get KWindowInfo as below hangs on windows (KDElibs 4.2.3) WINDOWINFO wininfo; wininfo.cbSize = sizeof (WINDOWINFO); GetWindowInfo (embedded, &wininfo); // clip off the window frame and menubar xembed_container->setContentsMargins (wininfo.rcWindow.left - wininfo.rcClient.left, wininfo.rcWindow.top - wininfo.rcClient.top, wininfo.rcClient.right - wininfo.rcWindow.right, wininfo.rcClient.bottom - wininfo.rcWindow.bottom); // set a fixed size until the window is shown xembed_container->setFixedSize (wininfo.rcClient.right - wininfo.rcClient.left, wininfo.rcClient.bottom - wininfo.rcClient.top); setGeometry (wininfo.rcClient.left, wininfo.rcClient.right, wininfo.rcClient.top, wininfo.rcClient.bottom); // see comment in X11 section move (wininfo.rcClient.left, wininfo.rcClient.top); // else the window frame may be off scree on top/left. #elif defined Q_WS_X11 KWindowInfo wininfo = KWindowSystem::windowInfo (embedded, NET::WMName | NET::WMGeometry); RK_ASSERT (wininfo.valid ()); // set a fixed size until the window is shown xembed_container->setFixedSize (wininfo.geometry ().width (), wininfo.geometry ().height ()); setGeometry (wininfo.geometry ()); // it's important to set a size, even while not visible. Else DetachedWindowContainer will assign a default size of 640*480, and then size upwards, if necessary. setCaption (wininfo.name ()); #endif // somehow in Qt 4.4.3, when the RKCaughtWindow is reparented the first time, the QX11EmbedContainer may kill its client. Hence we delay the actual embedding until after the window was shown. // In some previous version of Qt, this was not an issue, but I did not track the versions. QTimer::singleShot (0, this, SLOT (doEmbed())); } RKCaughtX11Window::RKCaughtX11Window (RKGraphicsDevice* rkward_device, int device_number) : RKMDIWindow (0, X11Window) { RK_TRACE (MISC); commonInit (device_number); rk_native_device = rkward_device; xembed_container->setFixedSize (rk_native_device->viewPort ()->size ()); resize (xembed_container->size ()); rk_native_device->viewPort ()->setParent (xembed_container); connect (rkward_device, SIGNAL (captionChanged(QString)), this, SLOT (setCaption(QString))); connect (rkward_device, SIGNAL (goingInteractive(bool,QString)), this, SLOT (deviceInteractive(bool,QString))); stop_interaction->setVisible (true); stop_interaction->setEnabled (false); setCaption (rkward_device->viewPort ()->windowTitle ()); QTimer::singleShot (0, this, SLOT (doEmbed())); } void RKCaughtX11Window::commonInit (int device_number) { RK_TRACE (MISC); capture = 0; rk_native_device = 0; embedded = 0; killed_in_r = close_attempted = false; RKCaughtX11Window::device_number = device_number; RK_ASSERT (!device_windows.contains (device_number)); device_windows.insert (device_number, this); error_dialog = new RKProgressControl (0, i18n ("An error occurred"), i18n ("An error occurred"), RKProgressControl::DetailedError); setPart (new RKCaughtX11WindowPart (this)); setMetaInfo (i18n ("Graphics Device Window"), "rkward://page/rkward_plot_history", RKSettings::PageX11); initializeActivationSignals (); setFocusPolicy (Qt::ClickFocus); updateHistoryActions (0, 0, QStringList ()); status_popup = new KPassivePopup (this); status_popup->setTimeout (0); disconnect (status_popup, SIGNAL (clicked()), status_popup, SLOT (hide())); // no auto-hiding, please QVBoxLayout *layout = new QVBoxLayout (this); layout->setContentsMargins (0, 0, 0, 0); box_widget = new KVBox (this); layout->addWidget (box_widget); scroll_widget = new QScrollArea (this); scroll_widget->hide (); layout->addWidget (scroll_widget); xembed_container = new KVBox (box_widget); // QX11EmbedContainer can not be reparented (between the box_widget, and the scroll_widget) directly. Therefore we place it into a container, and reparent that instead. // Also, this makes it easier to handle the various different devices dynamic_size = false; dynamic_size_action->setChecked (false); } void RKCaughtX11Window::doEmbed () { RK_TRACE (MISC); if (embedded) { #ifdef Q_WS_WIN capture = new QWinHost (xembed_container); capture->setWindow (embedded); capture->setFocusPolicy (Qt::ClickFocus); capture->setAutoDestruct (true); connect (capture, SIGNAL (clientDestroyed()), this, SLOT (deleteLater()), Qt::QueuedConnection); connect (capture, SIGNAL (clientTitleChanged(QString)), this, SLOT (setCaption(QString)), Qt::QueuedConnection); setCaption (capture->getClientTitle ()); #elif defined Q_WS_X11 capture = new QX11EmbedContainer (xembed_container); capture->embedClient (embedded); connect (capture, SIGNAL (clientClosed()), this, SLOT (deleteLater())); RKWardApplication::getApp ()->registerNameWatcher (embedded, this); #endif } // make xembed_container resizable, again, now that it actually has a content dynamic_size_action->setChecked (true); fixedSizeToggled (); // try to be helpful when the window is too large to fit on screen QRect dims = window ()->frameGeometry (); QRect avail = QApplication::desktop ()->availableGeometry (window ()); if ((dims.width () > avail.width ()) || (dims.height () > avail.height ())) { KMessageBox::information (this, i18n ("The current window appears too large to fit on the screen. If this happens regularly, you may want to adjust the default graphics window size in Settings->Configure RKWard->Onscreen Graphics."), i18n ("Large window"), "dont_ask_again_large_x11_window"); } } RKCaughtX11Window::~RKCaughtX11Window () { RK_TRACE (MISC); RK_ASSERT (device_windows.contains (device_number)); device_windows.remove (device_number); close (false); #ifdef Q_WS_X11 if (embedded) RKWardApplication::getApp ()->unregisterNameWatcher (embedded); #endif error_dialog->autoDeleteWhenDone (); delete status_popup; } void RKCaughtX11Window::forceClose () { killed_in_r = true; if (capture) { #ifdef Q_WS_X11 // HACK: Somehow (R 3.0.0alpha), the X11() window is surpisingly die-hard, if it is not close "the regular way". // So we expurge it, and leave the rest to the user. capture->discardClient (); qApp->processEvents (); #endif } RKMDIWindow::close (true); } bool RKCaughtX11Window::close (bool also_delete) { RK_TRACE (MISC); if (killed_in_r || RKGlobals::rInterface ()->backendIsDead ()) { return RKMDIWindow::close (also_delete); } if (rk_native_device) rk_native_device->stopInteraction (); QString status = i18n ("Closing device (saving history)"); if (!close_attempted) { RCommand* c = new RCommand ("dev.off (" + QString::number (device_number) + ')', RCommand::App, i18n ("Shutting down device number %1", device_number)); setStatusMessage (status, c); RKGlobals::rInterface ()->issueCommand (c); close_attempted = true; } else { if (KMessageBox::questionYesNo (this, i18n ("

The graphics device is being closed, saving the last plot to the plot history. This may take a while, if the R backend is still busy. You can close the graphics device immediately, in case it is stuck. However, the last plot may be missing from the plot history, if you do this.

") #ifdef Q_WS_X11 + i18n ("

Note: On X11, the embedded window may be expurged, and you will have to close it manually in this case.

") #endif , status, KGuiItem (i18n ("Close immediately")), KGuiItem (i18n ("Keep waiting"))) == KMessageBox::Yes) forceClose (); } return false; } void RKCaughtX11Window::reEmbed () { RK_TRACE (MISC); #ifdef Q_WS_X11 if (!capture) return; // somehow, since some version of Qt, the QX11EmbedContainer would loose its client while reparenting. This allows us to circumvent the problem. capture->discardClient (); capture->deleteLater (); RKWardApplication::getApp ()->unregisterNameWatcher (embedded); QTimer::singleShot (0, this, SLOT(doEmbed())); #endif } void RKCaughtX11Window::prepareToBeAttached () { RK_TRACE (MISC); dynamic_size_action->setChecked (false); fixedSizeToggled (); dynamic_size_action->setEnabled (false); reEmbed (); } void RKCaughtX11Window::prepareToBeDetached () { RK_TRACE (MISC); dynamic_size_action->setEnabled (true); reEmbed (); } void RKCaughtX11Window::deviceInteractive (bool interactive, const QString& prompt) { RK_TRACE (MISC); stop_interaction->setToolTip (prompt); stop_interaction->setEnabled (interactive); if (interactive) { activate (true); // it is necessary to do this also in the wrapper widget. Otherwise, for some reason, the view cannot be expanded, but can be shrunk. setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed); } else { setSizePolicy (QSizePolicy::Preferred, QSizePolicy::Preferred); } } void RKCaughtX11Window::stopInteraction () { RK_TRACE (MISC); RK_ASSERT (rk_native_device); rk_native_device->stopInteraction (); } void RKCaughtX11Window::fixedSizeToggled () { RK_TRACE (MISC); if (dynamic_size == dynamic_size_action->isChecked ()) return; dynamic_size = dynamic_size_action->isChecked (); if (dynamic_size_action->isChecked ()) { scroll_widget->takeWidget (); xembed_container->setParent (box_widget); xembed_container->show (); scroll_widget->hide (); box_widget->show (); xembed_container->setMinimumSize (5, 5); xembed_container->setMaximumSize (32767, 32767); } else { xembed_container->setFixedSize (xembed_container->size ()); scroll_widget->setWidget (xembed_container); box_widget->hide (); scroll_widget->show (); } } void RKCaughtX11Window::setFixedSize1 () { RK_TRACE (MISC); dynamic_size_action->setChecked (false); fixedSizeToggled (); // apparently KToggleAction::setChecked () does not invoke the slot! xembed_container->setFixedSize (500, 500); } void RKCaughtX11Window::setFixedSize2 () { RK_TRACE (MISC); dynamic_size_action->setChecked (false); fixedSizeToggled (); // see setFixedSize1 () above xembed_container->setFixedSize (1000, 1000); } void RKCaughtX11Window::setFixedSize3 () { RK_TRACE (MISC); dynamic_size_action->setChecked (false); fixedSizeToggled (); // see setFixedSize1 () above xembed_container->setFixedSize (2000, 2000); } void RKCaughtX11Window::setFixedSizeManual () { RK_TRACE (MISC); // TODO: not very pretty, yet KDialog *dialog = new KDialog (this); dialog->setButtons (KDialog::Ok|KDialog::Cancel); dialog->setCaption (i18n ("Specify fixed size")); dialog->setModal (true); KVBox *page = new KVBox (dialog); dialog->setMainWidget (page); new QLabel (i18n ("Width"), page); KIntSpinBox *width = new KIntSpinBox (5, 32767, 1, xembed_container->width (), page, 10); width->setEditFocus (true); new QLabel (i18n ("Height"), page); KIntSpinBox *height = new KIntSpinBox (5, 32767, 1, xembed_container->height (), page, 10); dialog->exec (); if (dialog->result () == QDialog::Accepted) { dynamic_size_action->setChecked (false); fixedSizeToggled (); // see setFixedSize1 () above xembed_container->setFixedSize (width->value (), height->value ()); } delete dialog; } void RKCaughtX11Window::activateDevice () { RK_TRACE (MISC); RKGlobals::rInterface ()->issueCommand ("dev.set (" + QString::number (device_number) + ')', RCommand::App, i18n ("Activate graphics device number %1", device_number), error_dialog); } void RKCaughtX11Window::copyDeviceToOutput () { RK_TRACE (MISC); RKGlobals::rInterface ()->issueCommand ("dev.set (" + QString::number (device_number) + ")\ndev.copy (device=rk.graph.on)\nrk.graph.off ()", RCommand::App | RCommand::CCOutput, i18n ("Copy contents of graphics device number %1 to output", device_number), error_dialog); } void RKCaughtX11Window::printDevice () { RK_TRACE (MISC); QString printer_device; if (RKSettingsModuleGraphics::kdePrintingEnabled ()) printer_device = "rk.printer.device"; RKGlobals::rInterface ()->issueCommand ("dev.set (" + QString::number (device_number) + ")\ndev.print (" + printer_device + ')', RCommand::App, i18n ("Print contents of graphics device number %1", device_number), error_dialog); } void RKCaughtX11Window::copyDeviceToRObject () { RK_TRACE (MISC); // TODO: not very pretty, yet KDialog *dialog = new KDialog (this); dialog->setButtons (KDialog::Ok|KDialog::Cancel); dialog->setCaption (i18n ("Specify R object")); dialog->setModal (true); KVBox *page = new KVBox (dialog); dialog->setMainWidget (page); new QLabel (i18n ("Specify the R object name, you want to save the graph to"), page); RKSaveObjectChooser *chooser = new RKSaveObjectChooser (page, "my.plot"); connect (chooser, SIGNAL (changed(bool)), dialog, SLOT (enableButtonOk(bool))); if (!chooser->isOk ()) dialog->enableButtonOk (false); dialog->exec (); if (dialog->result () == QDialog::Accepted) { RK_ASSERT (chooser->isOk ()); QString name = chooser->currentFullName (); RKGlobals::rInterface ()->issueCommand ("dev.set (" + QString::number (device_number) + ")\n" + name + " <- recordPlot ()", RCommand::App | RCommand::ObjectListUpdate, i18n ("Save contents of graphics device number %1 to object '%2'", device_number, name), error_dialog); } delete dialog; } void RKCaughtX11Window::duplicateDevice () { RK_TRACE (MISC); RKGlobals::rInterface ()->issueCommand ("rk.duplicate.device (" + QString::number (device_number) + ')', RCommand::App, i18n ("Duplicate graphics device number %1", device_number), error_dialog); } void RKCaughtX11Window::nextPlot () { RK_TRACE (MISC); RCommand* c = new RCommand ("rk.next.plot (" + QString::number (device_number) + ')', RCommand::App, i18n ("Load next plot in device number %1", device_number), error_dialog); setStatusMessage (i18n ("Loading plot from history"), c); RKGlobals::rInterface ()->issueCommand (c); } void RKCaughtX11Window::previousPlot () { RK_TRACE (MISC); RCommand* c = new RCommand ("rk.previous.plot (" + QString::number (device_number) + ')', RCommand::App, i18n ("Load previous plot in device number %1", device_number), error_dialog); setStatusMessage (i18n ("Loading plot from history"), c); RKGlobals::rInterface ()->issueCommand (c); } void RKCaughtX11Window::firstPlot () { RK_TRACE (MISC); RCommand* c = new RCommand ("rk.first.plot (" + QString::number (device_number) + ')', RCommand::App, i18n ("Load first plot in device number %1", device_number), error_dialog); setStatusMessage (i18n ("Loading plot from history"), c); RKGlobals::rInterface ()->issueCommand (c); } void RKCaughtX11Window::lastPlot () { RK_TRACE (MISC); RCommand* c = new RCommand ("rk.last.plot (" + QString::number (device_number) + ')', RCommand::App, i18n ("Load last plot in device number %1", device_number), error_dialog); setStatusMessage (i18n ("Loading plot from history"), c); RKGlobals::rInterface ()->issueCommand (c); } void RKCaughtX11Window::gotoPlot (int index) { RK_TRACE (MISC); RCommand* c = new RCommand ("rk.goto.plot (" + QString::number (device_number) + ", " + QString::number (index+1) + ')', RCommand::App, i18n ("Load plot %1 in device number %2", index, device_number), error_dialog); setStatusMessage (i18n ("Loading plot from history"), c); RKGlobals::rInterface ()->issueCommand (c); } void RKCaughtX11Window::forceAppendCurrentPlot () { RK_TRACE (MISC); RKGlobals::rInterface ()->issueCommand ("rk.force.append.plot (" + QString::number (device_number) + ')', RCommand::App, i18n ("Append this plot to history (device number %1)", device_number), error_dialog); } void RKCaughtX11Window::removeCurrentPlot () { RK_TRACE (MISC); RKGlobals::rInterface ()->issueCommand ("rk.removethis.plot (" + QString::number (device_number) + ')', RCommand::App, i18n ("Remove current plot from history (device number %1)", device_number), error_dialog); } void RKCaughtX11Window::clearHistory () { RK_TRACE (MISC); if (KMessageBox::warningContinueCancel (this, i18n ("This will clear the plot history for all device windows, not just this one. If this is not your intent, press cancel, below.")) != KMessageBox::Continue) return; RKGlobals::rInterface ()->issueCommand ("rk.clear.plot.history ()", RCommand::App, i18n ("Clear plot history"), error_dialog); } void RKCaughtX11Window::showPlotInfo () { RK_TRACE (MISC); RKGlobals::rInterface ()->issueCommand ("rk.show.plot.info (" + QString::number (device_number) + ')', RCommand::App, i18n ("Plot properties (device number %1)", device_number), error_dialog); } void RKCaughtX11Window::updateHistoryActions (int history_length, int position, const QStringList &labels) { RK_TRACE (MISC); RKCaughtX11Window::history_length = history_length; RKCaughtX11Window::history_position = position; plot_first_action->setEnabled ((history_length > 0) && (position > 1)); plot_prev_action->setEnabled ((history_length > 0) && (position > 1)); plot_next_action->setEnabled ((history_length > 0) && (position < history_length)); plot_last_action->setEnabled ((history_length > 0) && (position < history_length)); QStringList _labels = labels; if (position > history_length) _labels.append (i18n ("")); plot_list_action->setItems (_labels); plot_list_action->setCurrentItem (history_position - 1); plot_list_action->setEnabled (history_length > 0); plot_force_append_action->setEnabled ((history_length > 0) && (RKSettingsModuleGraphics::plotHistoryEnabled ())); plot_remove_action->setEnabled (history_length > 0); plot_clear_history_action->setEnabled (history_length > 0); plot_properties_action->setEnabled (RKSettingsModuleGraphics::plotHistoryEnabled ()); } void RKCaughtX11Window::setStatusMessage (const QString& message, RCommand *command) { RK_TRACE (MISC); status_change_command = command; if (command) command->addReceiver (this); if (!message.isEmpty ()) { status_popup->setView (QString (), message); status_popup->show (xembed_container->mapToGlobal (QPoint (20, 20))); } else { status_popup->hide (); } } // static void RKCaughtX11Window::setStatusMessage(int dev_num, const QString& message, RCommand* command) { RK_TRACE (MISC); RKCaughtX11Window *window = getWindow (dev_num); if (!window) return; window->setStatusMessage (message, command); } void RKCaughtX11Window::rCommandDone (RCommand *command) { RK_TRACE (MISC); if (command == status_change_command) { setStatusMessage (QString ()); status_popup->hide(); } RCommandReceiver::rCommandDone (command); } ///////////////////////////////// END RKCaughtX11Window /////////////////////////////// /**************************************************************************************/ //////////////////////////////// BEGIN RKCaughtX11WindowPart ////////////////////////// RKCaughtX11WindowPart::RKCaughtX11WindowPart (RKCaughtX11Window *window) : KParts::Part (0) { RK_TRACE (MISC); setComponentData (KGlobal::mainComponent ()); setWidget (window); RKCaughtX11WindowPart::window = window; setXMLFile ("rkcatchedx11windowpart.rc"); window->dynamic_size_action = new KToggleAction (i18n ("Draw area follows size of window"), window); connect (window->dynamic_size_action, SIGNAL (triggered()), window, SLOT (fixedSizeToggled())); actionCollection ()->addAction ("toggle_fixed_size", window->dynamic_size_action); QAction *action; action = actionCollection ()->addAction ("set_fixed_size_1", window, SLOT (setFixedSize1())); action->setText (i18n ("Set fixed size 500x500")); action = actionCollection ()->addAction ("set_fixed_size_2", window, SLOT (setFixedSize2())); action->setText (i18n ("Set fixed size 1000x1000")); action = actionCollection ()->addAction ("set_fixed_size_3", window, SLOT (setFixedSize3())); action->setText (i18n ("Set fixed size 2000x2000")); action = actionCollection ()->addAction ("set_fixed_size_manual", window, SLOT (setFixedSizeManual())); action->setText (i18n ("Set specified fixed size...")); action = actionCollection ()->addAction ("plot_prev", window, SLOT (previousPlot())); action->setText (i18n ("Previous plot")); action->setIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionMoveLeft)); window->plot_prev_action = (KAction*) action; action = actionCollection ()->addAction ("plot_first", window, SLOT (firstPlot())); action->setText (i18n ("First plot")); action->setIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionMoveFirst)); window->plot_first_action = (KAction*) action; action = actionCollection ()->addAction ("plot_next", window, SLOT (nextPlot())); action->setText (i18n ("Next plot")); action->setIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionMoveRight)); window->plot_next_action = (KAction*) action; action = actionCollection ()->addAction ("plot_last", window, SLOT (lastPlot())); action->setText (i18n ("Last plot")); action->setIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionMoveLast)); window->plot_last_action = (KAction*) action; action = window->plot_list_action = new KSelectAction (i18n ("Go to plot"), 0); window->plot_list_action->setToolBarMode (KSelectAction::MenuMode); action->setIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionListPlots)); actionCollection ()->addAction ("plot_list", action); connect (action, SIGNAL (triggered(int)), window, SLOT (gotoPlot(int))); action = actionCollection ()->addAction ("plot_force_append", window, SLOT (forceAppendCurrentPlot())); action->setText (i18n ("Append this plot")); action->setIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionSnapshot)); window->plot_force_append_action = (KAction*) action; action = actionCollection ()->addAction ("plot_remove", window, SLOT (removeCurrentPlot())); action->setText (i18n ("Remove this plot")); action->setIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionRemovePlot)); window->plot_remove_action = (KAction*) action; action = actionCollection ()->addAction ("plot_clear_history", window, SLOT (clearHistory())); window->plot_clear_history_action = (KAction*) action; action->setText (i18n ("Clear history")); action->setIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionClear)); action = actionCollection ()->addAction ("plot_properties", window, SLOT (showPlotInfo())); window->plot_properties_action = (KAction*) action; action->setText (i18n ("Plot properties")); action->setIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionDocumentInfo)); action = actionCollection ()->addAction ("device_activate", window, SLOT (activateDevice())); action->setText (i18n ("Make active")); action->setIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionFlagGreen)); action = actionCollection ()->addAction ("device_copy_to_output", window, SLOT (copyDeviceToOutput())); action->setText (i18n ("Copy to output")); action->setIcon (RKStandardIcons::getIcon (RKStandardIcons::WindowOutput)); action = actionCollection ()->addAction (KStandardAction::Print, "device_print", window, SLOT (printDevice())); action = actionCollection ()->addAction ("device_copy_to_r_object", window, SLOT (copyDeviceToRObject())); action->setText (i18n ("Store as R object...")); action = actionCollection ()->addAction ("device_duplicate", window, SLOT (duplicateDevice())); action->setText (i18n ("Duplicate")); action->setIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionWindowDuplicate)); action = window->stop_interaction = actionCollection ()->addAction ("stop_interaction", window, SLOT (stopInteraction())); action->setText (i18n ("Stop interaction")); action->setIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionInterrupt)); action->setVisible (false); // initialize context for plugins RKComponentGUIXML *context = RKComponentMap::getContext ("x11"); if (!context) return; RKContextHandler *context_handler = context->makeContextHandler (this); insertChildClient (context_handler); RKComponentPropertyInt *devnum_property = new RKComponentPropertyInt (this, false, 0); devnum_property->setIntValue (window->device_number); context_handler->addChild ("devnum", devnum_property); } RKCaughtX11WindowPart::~RKCaughtX11WindowPart () { RK_TRACE (MISC); } #include "rkwindowcatcher.moc" #endif // DISABLE_RKWINDOWCATCHER rkward-0.6.4/rkward/windows/rkfilebrowser.cpp0000664000175000017500000001633512633754364020762 0ustar thomasthomas/*************************************************************************** rkfilebrowser - description ------------------- begin : Thu Apr 26 2007 copyright : (C) 2007, 2008, 2009, 2010, 2011 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkfilebrowser.h" #include #include #include #include #include #include #include #include #if KDE_IS_VERSION(4,3,0) # include # include #endif #include #include #include #include #include #include #include "rkworkplace.h" #include "../rkward.h" #include "../misc/rkdummypart.h" #include "../debug.h" // static RKFileBrowser *RKFileBrowser::main_browser = 0; RKFileBrowser::RKFileBrowser (QWidget *parent, bool tool_window, const char *name) : RKMDIWindow (parent, FileBrowserWindow, tool_window, name) { RK_TRACE (APP); real_widget = 0; QVBoxLayout *layout = new QVBoxLayout (this); layout->setContentsMargins (0, 0, 0, 0); layout_widget = new KVBox (this); layout->addWidget (layout_widget); layout_widget->setFocusPolicy (Qt::StrongFocus); RKDummyPart *part = new RKDummyPart (this, layout_widget); setPart (part); initializeActivationSignals (); } RKFileBrowser::~RKFileBrowser () { RK_TRACE (APP); hide (); } void RKFileBrowser::showEvent (QShowEvent *e) { RK_TRACE (APP); if (!real_widget) { RK_DEBUG (APP, DL_INFO, "creating file browser"); real_widget = new RKFileBrowserWidget (layout_widget); setFocusProxy (real_widget); } RKMDIWindow::showEvent (e); } void RKFileBrowser::currentWDChanged () { RK_TRACE (APP); } /////////////////// RKFileBrowserWidget //////////////////// RKFileBrowserWidget::RKFileBrowserWidget (QWidget *parent) : KVBox (parent) { RK_TRACE (APP); KToolBar *toolbar = new KToolBar (this); toolbar->setIconSize (QSize (16, 16)); toolbar->setToolButtonStyle (Qt::ToolButtonIconOnly); urlbox = new KUrlComboBox (KUrlComboBox::Directories, true, this); KUrlCompletion* cmpl = new KUrlCompletion (KUrlCompletion::DirCompletion); urlbox->setCompletionObject (cmpl); urlbox->setAutoDeleteCompletionObject (true); urlbox->setSizePolicy (QSizePolicy (QSizePolicy::Expanding, QSizePolicy::Fixed)); urlbox->completionBox (true)->installEventFilter (this); setFocusProxy (urlbox); dir = new KDirOperator (KUrl (), this); dir->setPreviewWidget (0); KConfigGroup config = KGlobal::config ()->group ("file browser window"); dir->readConfig (config); dir->setView (KFile::Default); connect (RKWardMainWindow::getMain (), SIGNAL (aboutToQuitRKWard()), this, SLOT (saveConfig())); toolbar->addAction (dir->actionCollection ()->action ("up")); toolbar->addAction (dir->actionCollection ()->action ("back")); toolbar->addAction (dir->actionCollection ()->action ("forward")); toolbar->addAction (dir->actionCollection ()->action ("home")); toolbar->addAction (dir->actionCollection ()->action ("short view")); toolbar->addAction (dir->actionCollection ()->action ("tree view")); toolbar->addAction (dir->actionCollection ()->action ("detailed view")); // toolbar->addAction (dir->actionCollection ()->action ("detailed tree view")); // should we have this as well? Trying to avoid crowding in the toolbar #if KDE_IS_VERSION(4, 3, 0) fi_actions = new KFileItemActions (this); connect (dir, SIGNAL (contextMenuAboutToShow(KFileItem,QMenu*)), this, SLOT (contextMenuHook(KFileItem,QMenu*))); #endif connect (dir, SIGNAL (urlEntered(KUrl)), this, SLOT (urlChangedInView(KUrl))); connect (urlbox, SIGNAL (returnPressed(QString)), this, SLOT (urlChangedInCombo(QString))); connect (urlbox, SIGNAL (urlActivated(KUrl)), this, SLOT (urlChangedInCombo(KUrl))); connect (dir, SIGNAL (fileSelected(KFileItem)), this, SLOT (fileActivated(KFileItem))); setURL (QDir::currentPath ()); } RKFileBrowserWidget::~RKFileBrowserWidget () { RK_TRACE (APP); } void RKFileBrowserWidget::contextMenuHook(const KFileItem& item, QMenu* menu) { RK_TRACE (APP); #if KDE_IS_VERSION(4,3,0) QList dummy; dummy.append (item); fi_actions->setItemListProperties (KFileItemListProperties (dummy)); // some versions of KDE appear to re-use the actions, others don't, and yet other are just plain broken (see this thread: https://mail.kde.org/pipermail/rkward-devel/2011-March/002770.html) // Therefore, we remove all actions, explicitly, each time the menu is shown, then add them again. QList menu_actions = menu->actions (); foreach (QAction* act, menu_actions) if (added_service_actions.contains (act)) menu->removeAction (act); added_service_actions.clear (); menu_actions = menu->actions (); fi_actions->addOpenWithActionsTo (menu, QString ()); fi_actions->addServiceActionsTo (menu); QList menu_actions_after = menu->actions (); foreach (QAction* act, menu_actions_after) if (!menu_actions.contains (act)) added_service_actions.append (act); #endif } // does not work in d-tor. Apparently it's too late, then void RKFileBrowserWidget::saveConfig () { RK_TRACE (APP); KConfigGroup config = KGlobal::config ()->group ("file browser window"); dir->writeConfig (config); } void RKFileBrowserWidget::setURL (const QString &url) { RK_TRACE (APP); urlbox->setUrl (url); dir->setUrl (url, true); } void RKFileBrowserWidget::urlChangedInView (const KUrl &url) { RK_TRACE (APP); urlbox->setUrl (url); } void RKFileBrowserWidget::urlChangedInCombo (const QString &url) { RK_TRACE (APP); dir->setUrl (url, true); } void RKFileBrowserWidget::urlChangedInCombo (const KUrl &url) { RK_TRACE (APP); dir->setUrl (url, true); } bool RKFileBrowserWidget::eventFilter (QObject* o, QEvent* e) { // don't trace here if (o == urlbox->completionBox () && e->type () == QEvent::Resize) { RK_TRACE (APP); // this hack (originally from a KDE 3 version of kate allows the completion popup to span beyond the border of the filebrowser widget itself KCompletionBox* box = urlbox->completionBox (); RK_ASSERT (box); int add = box->verticalScrollBar ()->isVisible () ? box->verticalScrollBar ()->width () : 0; box->setMinimumWidth (qMin (topLevelWidget ()->width (), box->sizeHintForColumn (0) + add)); return false; } return (KVBox::eventFilter (o, e)); } void RKFileBrowserWidget::fileActivated (const KFileItem& item) { RK_TRACE (APP); RKWorkplace::mainWorkplace ()->openAnyUrl (item.url ()); } #include "rkfilebrowser.moc" rkward-0.6.4/rkward/windows/rkwindowcatcher.h0000664000175000017500000002174312633754364020744 0ustar thomasthomas/*************************************************************************** rwindowcatcher.h - description ------------------- begin : Wed May 4 2005 copyright : (C) 2005-2013 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKWINDOWCATCHER_H #define RKWINDOWCATCHER_H //#define DISABLE_RKWINDOWCATCHER #ifndef DISABLE_RKWINDOWCATCHER #include /** This is a simple helper class helping in catching R X11 device windows. The start () and stop () functions are called from RInterface, and then this class takes care of handling those. The main difficulty to overcome in this context, is to find out, when an R X11 window is created, and what is its X Window id. The notes below are some thoughts on that matter. Probably mostly obsolete, now (the current approach is basically Plan C, and seems to work ok), but maybe Plans A or B or something similar will become necessary some day: Catch R X11 device windows - Plan A: - initialization function seems to be in_do_X11 - it might be possible to put a wrapper around this using R_setX11Routines - this wrapper could watch the list of devices (curDevice, numDevices), see also addDevice to find out how the list is kept internally - if a new device gets added grab its winId and capture - Plan B: - it looks like there's no way to get access to R_setX11Routines or at least the needed struct R_X11Routines. (?) - the level above that seems to be do_X11 - maybe we can modify the mapping from .Internal (X11) to do_X11 and insert wrapper from Plan A -> R_FunTab - proceed like in Plan A - less preferable as C-plugins might be able to call do_X11 directly (can they?) - Plan C: - modify at R level (override X11 ()) - notify app right before device is created - notify app right after device is created - least preferable solution as we can not be sure we catch every use. - but definitely most. This is dispatched via CurrentDevice ()->options("device"), and then evalued in R_GlobalEnv - remaining problem: how to get the window id given the device id? - http://tronche.com/gui/x/xlib/events/window-state-change/create.html#XCreateWindowEvent - for active / inactive: XPropertyEvent WM_NAME - we may catch this using KApplication::x11EventFilter - event filter should only be active during the wrapper (Plan A-C) - event filter should probably do some sanity checking - this should give us the window id corresponding to the x11-call @author Thomas Friedrichsmeier */ class RKWindowCatcher { public: /** ctor. Probably you'll only ever need one instance of RKWindowCatcher. */ RKWindowCatcher (); /** dtor */ ~RKWindowCatcher (); /** call this function to start looking out for new R X11 device windows. @param prev_cur_device the device number that was active before a new device window was (potentially) created */ void start (int prev_cur_device); /** end looking out for new R X11 windows. If a new window was in fact created, this is captured by creating an RKCaughtX11Window @param new_cur_device the new active device number, i.e. the device number of the created window */ void stop (int new_cur_device); /** called from the R backend when the device history needs to be updated @param params the serialized parameters as supplied from R */ void updateHistory (QStringList params); /** Kill an R device @param device_number R device number of the device to kil */ void killDevice (int device_number); private: int last_cur_device; }; #include "rkmdiwindow.h" #include "../rbackend/rcommandreceiver.h" #include class RKCaughtX11WindowPart; class KToggleAction; class KAction; class KSelectAction; class QXEmbedCopy; class QScrollArea; class KVBox; class RKProgressControl; class QX11EmbedContainer; class QWinHost; class KPassivePopup; class RKGraphicsDevice; /** An R onscreen graphics device window managed by rkward. Currently, this can be X11 devices (on X11), Windows devices (on Windows), and RK devices (anywhere). */ class RKCaughtX11Window : public RKMDIWindow, public RCommandReceiver { Q_OBJECT public: /** ctor @param window_to_embed the Window id of the R X11 device window to embed @param device_number the device number corresponding to that window */ RKCaughtX11Window (WId window_to_embed, int device_number); RKCaughtX11Window (RKGraphicsDevice *rkward_device, int device_number); /** dtor */ ~RKCaughtX11Window (); /** TODO? */ bool isModified () { return false; }; /** reimplemented from RKMDIWindow to switch to fixed size mode, and disable the dynamic_size_action */ void prepareToBeAttached (); /** see prepareToBeAttached (). Reenable the dynamic_size_action */ void prepareToBeDetached (); /** returns the window corresponding the to given R device number (or 0 if no such window exists) */ static RKCaughtX11Window* getWindow (int device_number) { return device_windows.value (device_number); }; void updateHistoryActions (int history_length, int position, const QStringList &labels); /** Set a status message to be shown in a popup inside the window. The message persists until the given R command has finished, or until this function is called with an empty string. This should be used, when the plot is currently out-of-date (e.g. when loading a plot from history), _not_ when the window is simply busy (e.g. when saving the current plot to history). */ void setStatusMessage (const QString& message, RCommand* command=0); /** static convencience wrapper around setStatusMessage (). Sets the message on the window corresponding to the given device number. * NOTE: If no device exists (or isn't known to the system), this function does nothing */ static void setStatusMessage (int dev_num, const QString &message, RCommand *command=0); public slots: void deviceInteractive (bool interactive, const QString &prompt); /** Fixed size action was (potentially) toggled. Update to the new state */ void fixedSizeToggled (); /** Switch to fixed size mode, and set size1 (currently 500*500) */ void setFixedSize1 (); /** Switch to fixed size mode, and set size2 (currently 1000*1000) */ void setFixedSize2 (); /** Switch to fixed size mode, and set size3 (currently 2000*2000) */ void setFixedSize3 (); /** Switch to fixed size mode, and set user specified size (size read from a dialog) */ void setFixedSizeManual (); void activateDevice (); void copyDeviceToOutput (); void printDevice (); void copyDeviceToRObject (); void duplicateDevice (); void stopInteraction (); /** history navigation */ void firstPlot (); void previousPlot (); void nextPlot (); void lastPlot (); void gotoPlot (int index); void forceAppendCurrentPlot (); void removeCurrentPlot (); void clearHistory (); void showPlotInfo (); /** reimplemented to keep window alive while saving history */ bool close (bool also_delete); void setKilledInR () { killed_in_r = true; }; private slots: void doEmbed (); private: void forceClose (); void commonInit (int device_number); void reEmbed (); void rCommandDone (RCommand *command); friend class RKCaughtX11WindowPart; // needs access to the actions int device_number; bool killed_in_r; bool close_attempted; WId embedded; KVBox *xembed_container; QScrollArea *scroll_widget; KVBox *box_widget; RKProgressControl *error_dialog; static QHash device_windows; #ifdef Q_WS_WIN QWinHost *capture; #elif defined Q_WS_X11 QX11EmbedContainer *capture; #else // a dummy to make things compile for now QWidget *capture; #endif RKGraphicsDevice *rk_native_device; bool dynamic_size; KToggleAction *dynamic_size_action; KAction *plot_prev_action; KAction *plot_next_action; KAction *plot_first_action; KAction *plot_last_action; KAction *plot_force_append_action; KAction *plot_remove_action; KAction *plot_clear_history_action; KAction *plot_properties_action; KSelectAction *plot_list_action; KAction *stop_interaction; KPassivePopup* status_popup; RCommand* status_change_command; int history_length; int history_position; }; /** Provides a KPart interface for RKCaughtX11Window. */ class RKCaughtX11WindowPart : public KParts::Part { public: /** constructor. @param window The RKCatehdX11Window for this part */ explicit RKCaughtX11WindowPart (RKCaughtX11Window *window); /** destructor */ ~RKCaughtX11WindowPart (); private: RKCaughtX11Window *window; }; #endif //DISABLE_RKWINDOWCATCHER #endif rkward-0.6.4/rkward/windows/rkdebugmessagewindow.cpp0000664000175000017500000001140712633754364022315 0ustar thomasthomas/*************************************************************************** rkdebugmessagewindow - description ------------------- begin : Sat Dec 01 2012 copyright : (C) 2012 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkdebugmessagewindow.h" #include #include #include #include #include #include #include #include "../misc/rkdummypart.h" #include "../debug.h" RKDebugMessageWindow* RKDebugMessageWindow::_instance = 0; RKDebugMessageWindow::RKDebugMessageWindow (QWidget* parent, bool tool_window, const char* name) : RKMDIWindow (parent, RKMDIWindow::DebugMessageWindow, tool_window, name) { RK_TRACE (APP); RK_ASSERT (!_instance); real_widget = 0; first = true; QVBoxLayout *layout = new QVBoxLayout (this); layout->setContentsMargins (0, 0, 0, 0); layout_widget = new KVBox (this); layout->addWidget (layout_widget); layout_widget->setFocusPolicy (Qt::StrongFocus); setPart (new RKDummyPart (this, layout_widget)); initializeActivationSignals (); } RKDebugMessageWindow::~RKDebugMessageWindow () { RK_TRACE (APP); } void RKDebugMessageWindow::showEvent (QShowEvent *e) { RK_TRACE (APP); if (!e->spontaneous ()) createWidget (); RKMDIWindow::showEvent (e); } void RKDebugMessageWindow::hideEvent (QHideEvent *e) { RK_TRACE (APP); if (!e->spontaneous ()) discardWidget (); RKMDIWindow::hideEvent (e); } void RKDebugMessageWindow::createWidget () { RK_TRACE (APP); if (!real_widget) { RK_DEBUG (APP, DL_INFO, "creating debug message viewer"); real_widget = new RKDebugMessageWindowWidget (layout_widget); setFocusProxy (layout_widget); if (first) { KMessageBox::information (this, i18n ("

This window is used for displaying RKWard related debug messages. It is targetted primarily at (plugin) developers. It does not offer any features for debugging R code.

" "

Note that the list of messages is cleared every time you close the window.

Type and severity level of messages can be controlled from Settings->Configure RKWard->Debug

"), i18n ("About this window"), "inforkdebugmessagewindow"); first = false; } } } void RKDebugMessageWindow::discardWidget () { RK_TRACE (APP); if (real_widget) { RK_DEBUG (APP, DL_INFO, "discarding debug message viewer"); delete real_widget; real_widget = 0; } } void RKDebugMessageWindow::newMessage (const int flags, const int level, const QString &message) { // Not tracing this! That might lead to infinite recursion! if (_instance && _instance->real_widget) _instance->real_widget->newMessage (flags, level, message); } RKDebugMessageWindowWidget::RKDebugMessageWindowWidget (QWidget *parent) : QWidget (parent) { RK_TRACE (APP); QVBoxLayout *v_layout = new QVBoxLayout (this); v_layout->setContentsMargins (0, 0, 0, 0); message_viewer = new QTextEdit (this); message_viewer->setUndoRedoEnabled (false); message_viewer->setReadOnly (true); message_viewer->setTextBackgroundColor (Qt::white); v_layout->addWidget (message_viewer); } RKDebugMessageWindowWidget::~RKDebugMessageWindowWidget () { RK_TRACE (APP); } void RKDebugMessageWindowWidget::newMessage (const int flags, const int level, const QString &message) { Q_UNUSED (flags); // Not tracing this! That might lead to infinite recursion! if (level == DL_TRACE) { message_viewer->setTextColor (Qt::gray); message_viewer->insertPlainText ("TRACE\t"); } else if (level == DL_DEBUG) { message_viewer->setTextColor (Qt::blue); message_viewer->insertPlainText ("DEBUG\t"); } else if (level == DL_INFO) { message_viewer->setTextColor (Qt::green); message_viewer->insertPlainText ("INFO\t"); } else if (level == DL_WARNING) { message_viewer->setTextColor (Qt::darkYellow); message_viewer->insertPlainText ("WARNING\t"); } else { message_viewer->setTextColor (Qt::red); message_viewer->insertPlainText ("ERROR\t"); } message_viewer->setTextColor (Qt::black); message_viewer->insertPlainText (message + '\n'); } rkward-0.6.4/rkward/windows/rkdebugmessagewindow.h0000664000175000017500000000450412633754364021762 0ustar thomasthomas/*************************************************************************** rkdebugmessagewindow - description ------------------- begin : Sat Dec 01 2012 copyright : (C) 2012 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKDEBUGMESSAGEWINDOW_H #define RKDEBUGMESSAGEWINDOW_H #include "rkmdiwindow.h" class RKDebugMessageWindowWidget; class QTextEdit; /** Tool window for displaying RKWard debug messages. Mainly targetted at plugin * developers. */ class RKDebugMessageWindow : public RKMDIWindow { public: RKDebugMessageWindow (QWidget *parent, bool tool_window, const char *name=0); ~RKDebugMessageWindow (); /** reimplemented to create the real widget only when the viewer is shown */ void showEvent (QShowEvent *e); /** reimplemented to discard the real widget only when the viewer is hidden */ void hideEvent (QHideEvent *e); static RKDebugMessageWindow *instance () { return _instance; }; static void newMessage (const int flags, const int level, const QString &message); private: void createWidget (); void discardWidget (); RKDebugMessageWindowWidget *real_widget; bool first; QWidget *layout_widget; friend class RKWardMainWindow; static RKDebugMessageWindow *_instance; }; /** The internal widget used in RKDebugMessageWindow */ class RKDebugMessageWindowWidget : public QWidget { public: explicit RKDebugMessageWindowWidget (QWidget *parent); ~RKDebugMessageWindowWidget (); void newMessage (const int flags, const int level, const QString &message); private: QTextEdit *message_viewer; }; #endif rkward-0.6.4/rkward/windows/rkhelpsearchwindow.h0000664000175000017500000000765612633754364021460 0ustar thomasthomas/*************************************************************************** rkhelpsearchwindow - description ------------------- begin : Fri Feb 25 2005 copyright : (C) 2005, 2006, 2007, 2009, 2010, 2011 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKHELPSEARCHWINDOW_H #define RKHELPSEARCHWINDOW_H #include #include #include "../rbackend/rcommandreceiver.h" #include "rkmdiwindow.h" class QFocusEvent; class QComboBox; class QCheckBox; class QPushButton; class RKHelpSearchResultsModel; class QTreeView; class QSortFilterProxyModel; class RCommandChain; /** Provides a UI interface for help-search. @author Pierre Ecochard */ class RKHelpSearchWindow : public RKMDIWindow, public RCommandReceiver { Q_OBJECT public: RKHelpSearchWindow (QWidget *parent, bool tool_window, const char *name=0); ~RKHelpSearchWindow (); void rCommandDone (RCommand *command); /** small convenience function to get context help for RKCommandEditorWindow and RKConsole. @param context_line The current line @param cursor_pos cursor position in the current line Will figure out the word under the cursor, and provide help on that (if there is such a word, and such help exists) */ void getContextHelp (const QString &context_line, int cursor_pos); void getFunctionHelp (const QString &function_name, const QString &package=QString(), const QString &type=QString ()); static RKHelpSearchWindow *mainHelpSearch () { return main_help_search; }; public slots: void slotFindButtonClicked(); void resultDoubleClicked (const QModelIndex& index); void updateInstalledPackages (); protected: /** reimplemnented from QWidget to make the input focus default to the input field */ void focusInEvent (QFocusEvent *e); private: QComboBox* field; QComboBox* fieldsList; QComboBox* packagesList; QCheckBox* caseSensitiveCheckBox; QCheckBox* fuzzyCheckBox; QPushButton* findButton; QTreeView* results_view; RKHelpSearchResultsModel* results; QSortFilterProxyModel* proxy_model; friend class RKWardMainWindow; static RKHelpSearchWindow *main_help_search; }; /** An item model meant for use by RKHelpSearchWindow. Since it is fairly specialized, it is unlikely to be of any use in any other context. NOTE: This class is pretty useless, really, we should just switch to a QTree/TableWidget with predefined model, whenever we need to make the next big change to the RKHelpSearchWindow. @author Thomas Friedrichsmeier */ class RKHelpSearchResultsModel : public QAbstractTableModel { public: explicit RKHelpSearchResultsModel (QObject *parent); ~RKHelpSearchResultsModel (); /** Set the results. The model will assume ownership of the results */ void setResults (const QStringList &new_results); int rowCount (const QModelIndex& parent=QModelIndex()) const; int columnCount (const QModelIndex& parent=QModelIndex()) const; QVariant data (const QModelIndex& index, int role=Qt::DisplayRole) const; QVariant headerData (int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const; QString resultsType (int row); private: QStringList topics; QStringList titles; QStringList packages; QStringList types; int result_count; }; #endif rkward-0.6.4/rkward/windows/rktoplevelwindowgui.h0000664000175000017500000000510512633754364021664 0ustar thomasthomas/*************************************************************************** rktoplevelwindowgui - description ------------------- begin : Tue Apr 24 2007 copyright : (C) 2007, 2009, 2011 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKTOPLEVELWINDOWGUI_H #define RKTOPLEVELWINDOWGUI_H #include #include class KXmlGuiWindow; class RKMDIWindow; class KAction; /** represents the common portions of the GUI for top level windows: The help menu, and the windows menu */ class RKTopLevelWindowGUI : public QObject, public KXMLGUIClient { Q_OBJECT public: explicit RKTopLevelWindowGUI (KXmlGuiWindow *for_window); ~RKTopLevelWindowGUI (); public slots: // windows menu /** Raise the help search window */ void showHelpSearch (); /** Activate the current (non tools) window in the workspace */ void activateDocumentView (); /** ensure output window is shown. */ void slotOutputShow (); // help menu /** Show the starting page of RKWard help */ void showRKWardHelp (); /** enter "what's this" mode */ void startWhatsThis (); /** Invokes R help (help.start ()) */ void invokeRHelp (); /** show instructions on reporting bugs in rkward */ void reportRKWardBug (); /** not quite sure, why I have to reimplement this from KMainWindow */ void showAboutApplication (); // settings menu /** configure key bindings. Reimplemented to show notice before the actual dialog. */ void configureShortcuts (); /** configure key bindings. Reimplemented to show notice before the actual dialog. */ void configureToolbars (); private slots: void toggleToolView (); void previousWindow (); void nextWindow (); private: KXmlGuiWindow *for_window; KAction *prev_action; KAction *next_action; void toggleToolView (RKMDIWindow *tool_window); }; #endif rkward-0.6.4/rkward/windows/detachedwindowcontainer.rc0000644000175000017500000000373512455741221022604 0ustar thomasthomas &File &Device &Edit &View &History &Run &Window &Activate &Settings &Help rkward-0.6.4/rkward/windows/robjectbrowser.h0000664000175000017500000000631612633754364020601 0ustar thomasthomas/*************************************************************************** robjectbrowser - description ------------------- begin : Thu Aug 19 2004 copyright : (C) 2004 - 2015 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef ROBJECTBROWSER_H #define ROBJECTBROWSER_H #include "rkmdiwindow.h" #include #include class RKObjectListView; class RKObjectListViewSettings; class QPushButton; class RObject; class RObjectBrowserInternal; class KVBox; /** This widget provides a browsable list of all objects in the R workspace Note: Most actual functionality is realized in RObjectBrowserInternal, which is created as soon as the RObjectBrowser is shown for the first time. @author Thomas Friedrichsmeier */ class RObjectBrowser : public RKMDIWindow { public: RObjectBrowser (QWidget *parent, bool tool_window, const char *name=0); ~RObjectBrowser (); void unlock (); static RObjectBrowser *mainBrowser () { return object_browser; }; /** reimplemented to create the real file browser widget only when the file browser is shown for the first time */ void showEvent (QShowEvent *e); private: RObjectBrowserInternal *internal; KVBox *layout_widget; bool locked; friend class RKWardMainWindow; static RObjectBrowser *object_browser; void initialize (); }; /** Provides most of the functionality of RObjectBrowser @author Thomas Friedrichsmeier */ class RObjectBrowserInternal : public QWidget { Q_OBJECT public: explicit RObjectBrowserInternal (QWidget *parent); ~RObjectBrowserInternal (); private slots: void updateButtonClicked (); void contextMenuCallback (RObject *object, bool *suppress); void popupHelp (); void popupEdit (); void popupCopy (); /** essentially like popupCopy, but does not ask for a name */ void popupCopyToGlobalEnv (); void popupView (); void popupDelete (); void popupUnload (); void popupRename (); /** when an object in the list is double clicked, insert its name in the current RKCommandEditor window */ void doubleClicked (const QModelIndex &index); protected: /** reimplemnented from QWidget to make show the globalenv object when activated (other than by mouse click) */ void focusInEvent (QFocusEvent *e); private: enum PopupActions { Help=0, Edit, View, Rename, Copy, CopyToGlobalEnv, Delete, Unload, LoadUnloadPackages, ActionCount }; QList actions; QPushButton *update_button; RKObjectListView *list_view; }; #endif rkward-0.6.4/rkward/windows/rkcommandeditorwindowpart.rc0000664000175000017500000000235412633754364023221 0ustar thomasthomas &Edit &Tools Move &Run &Settings &Run rkward-0.6.4/rkward/windows/rkcommandlog.h0000664000175000017500000000723412633754364020222 0ustar thomasthomas/*************************************************************************** rkcommandlog - description ------------------- begin : Sun Nov 3 2002 copyright : (C) 2002, 2004, 2005, 2006, 2007, 2009, 2010 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKCOMMANDLOG_H #define RKCOMMANDLOG_H #include #include #include "rkmdiwindow.h" #include "../settings/rksettings.h" #include "../rbackend/rcommandreceiver.h" class RCommand; struct ROutput; class RKCommandLogView; class RKCommandLogPart; /** \brief This widget shows all executed commands and their result @author Thomas Friedrichsmeier */ class RKCommandLog : public RKMDIWindow, public RCommandReceiver { Q_OBJECT public: /** Adds input to the log_view-window (i.e. commands issued) */ void addInput (RCommand *command); /** Adds output to the log_view-window (i.e. replies received) */ void newOutput (RCommand *command, ROutput *output_fragment); static RKCommandLog *getLog () { return rkcommand_log; }; RKCommandLogView *getView () { return log_view; }; protected: /** Command has finished. If the command has failed, it may be necessary to print some more information */ void rCommandDone (RCommand *command); RKCommandLog (QWidget *parent, bool tool_window, const char *name=0); ~RKCommandLog (); public slots: /** configures the log_view-window */ void configureLog (); /** clears the log_view-window */ void clearLog (); void runSelection (); void settingsChanged (RKSettings::SettingsPage page); private: void addInputNoCheck (RCommand *command); void addOutputNoCheck (RCommand *command, ROutput *output); void checkRaiseWindow (RCommand *command); /** internal helper function, called whenever a line/lines have been added. Check whether log is longer than maximum setting. Scroll to the bottom */ void linesAdded (); /** Used to keep track, which commands "input" has already been shown */ QList command_input_shown; /** On a given command, the log_view should not be raised more than once */ int last_raised_command; RKCommandLogView *log_view; friend class RKWardMainWindow; static RKCommandLog *rkcommand_log; }; /** Simply subclass of QTextEdit to override context menu handling */ class RKCommandLogView : public QTextEdit { Q_OBJECT public: explicit RKCommandLogView (RKCommandLog *parent); ~RKCommandLogView (); public slots: void selectAll (); signals: void popupMenuRequest (const QPoint &pos); protected: void contextMenuEvent (QContextMenuEvent *event); }; #include class KAction; /** Provides a part interface for the RKCommandLog */ class RKCommandLogPart : public KParts::Part { Q_OBJECT public: explicit RKCommandLogPart (RKCommandLog *for_log); ~RKCommandLogPart (); void initActions (); public slots: void doPopupMenu (const QPoint &pos); private: RKCommandLog *log; QAction *run_selection; QAction *copy; }; #endif rkward-0.6.4/rkward/windows/rktoplevelwindowgui.cpp0000664000175000017500000002076112633754364022224 0ustar thomasthomas/*************************************************************************** rktoplevelwindowgui - description ------------------- begin : Tue Apr 24 2007 copyright : (C) 2007, 2009, 2011 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rktoplevelwindowgui.h" #include #include #include #include #include #include #include #include #include "../rkconsole.h" #include "../windows/robjectbrowser.h" #include "../windows/rkfilebrowser.h" #include "../windows/rcontrolwindow.h" #include "../windows/rkhtmlwindow.h" #include "../windows/rkworkplaceview.h" #include "../windows/rkworkplace.h" #include "../windows/rkcommandlog.h" #include "../windows/rkhelpsearchwindow.h" #include "../windows/rkmdiwindow.h" #include "../misc/rkstandardicons.h" #include "../misc/rkprogresscontrol.h" #include "../misc/rkcommonfunctions.h" #include "../plugin/rkcomponentmap.h" #include "../dialogs/rkerrordialog.h" #include "../rbackend/rinterface.h" #include "../rkglobals.h" #include "../rkward.h" #include "../debug.h" RKTopLevelWindowGUI::RKTopLevelWindowGUI (KXmlGuiWindow *for_window) : QObject (for_window), KXMLGUIClient () { RK_TRACE (APP); RKTopLevelWindowGUI::for_window = for_window; setXMLFile ("rktoplevelwindowgui.rc"); // help menu QAction *help_invoke_r_help = actionCollection ()->addAction ("invoke_r_help", this, SLOT(invokeRHelp())); help_invoke_r_help->setText (i18n ("Help on R")); QAction *show_help_search = actionCollection ()->addAction ("show_help_search", this, SLOT(showHelpSearch())); show_help_search->setText (i18n ("Search R Help")); QAction *show_rkward_help = actionCollection ()->addAction (KStandardAction::HelpContents, "rkward_help", this, SLOT (showRKWardHelp())); show_rkward_help->setText (i18n ("Help on RKWard")); actionCollection ()->addAction (KStandardAction::AboutApp, "about_app", this, SLOT (showAboutApplication())); actionCollection ()->addAction (KStandardAction::WhatsThis, "whats_this", this, SLOT (startWhatsThis())); actionCollection ()->addAction (KStandardAction::ReportBug, "report_bug", this, SLOT (reportRKWardBug())); help_invoke_r_help->setStatusTip (i18n ("Shows the R help index")); show_help_search->setStatusTip (i18n ("Shows/raises the R Help Search window")); show_rkward_help->setStatusTip (i18n ("Show help on RKWard")); // window menu // NOTE: enabling / disabling the prev/next actions is not a good idea. It will cause the script windows to "accept" their shortcuts, when disabled prev_action = actionCollection ()->addAction ("prev_window", this, SLOT (previousWindow())); prev_action->setText (i18n ("Previous Window")); prev_action->setIcon (QIcon (RKCommonFunctions::getRKWardDataDir () + "icons/window_back.png")); prev_action->setShortcut (Qt::ControlModifier + Qt::Key_Tab); next_action = actionCollection ()->addAction ("next_window", this, SLOT (nextWindow())); next_action->setText (i18n ("Next Window")); next_action->setIcon (QIcon (RKCommonFunctions::getRKWardDataDir () + "icons/window_forward.png")); next_action->setShortcut (Qt::ControlModifier + Qt::ShiftModifier + Qt::Key_Tab); KAction *action; foreach (const RKToolWindowList::ToolWindowRepresentation& rep, RKToolWindowList::registeredToolWindows ()) { action = actionCollection ()->addAction ("window_show_" + rep.id, this, SLOT (toggleToolView())); action->setText (i18n ("Show/Hide %1", rep.window->shortCaption ())); action->setIcon (rep.window->windowIcon ()); action->setShortcut (rep.default_shortcut); action->setProperty ("rk_toolwindow_id", rep.id); } action = actionCollection ()->addAction ("window_activate_docview", this, SLOT(activateDocumentView())); action->setText (i18n ("Activate Document view")); action->setShortcut (Qt::AltModifier + Qt::Key_0); action = actionCollection ()->addAction ("output_show", this, SLOT (slotOutputShow())); action->setText (i18n ("Show &Output")); action->setIcon (RKStandardIcons::getIcon (RKStandardIcons::WindowOutput)); // settings KStandardAction::keyBindings (this, SLOT (configureShortcuts()), actionCollection ()); KStandardAction::configureToolbars (this, SLOT (configureToolbars()), actionCollection ()); } RKTopLevelWindowGUI::~RKTopLevelWindowGUI () { RK_TRACE (APP); } void RKTopLevelWindowGUI::configureShortcuts () { RK_TRACE (APP); KMessageBox::information (for_window, i18n ("For technical reasons, the following dialog allows you to configure the keyboard shortcuts only for those parts of RKWard that are currently active.\n\nTherefore, if you want to configure keyboard shortcuts e.g. for use inside the script editor, you need to open a script editor window, and activate it."), i18n ("Note"), "configure_shortcuts_kparts"); KShortcutsDialog dlg (KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsAllowed, qobject_cast (parent())); foreach (KXMLGUIClient *client, factory ()->clients ()) { if (client && !client->xmlFile ().isEmpty ()) dlg.addCollection (client->actionCollection()); } dlg.addCollection (RKComponentMap::getMap ()->actionCollection (), i18n ("RKWard Plugins")); dlg.configure (true); } void RKTopLevelWindowGUI::configureToolbars () { RK_TRACE (APP); KMessageBox::information (for_window, i18n ("For technical reasons, the following dialog allows you to configure the toolbar buttons only for those parts of RKWard that are currently active.\n\nTherefore, if you want to configure tool buttons e.g. for use inside the script editor, you need to open a script editor window, and activate it."), i18n ("Note"), "configure_toolbars_kparts"); for_window->configureToolbars (); } void RKTopLevelWindowGUI::invokeRHelp () { RK_TRACE (APP); RKGlobals::rInterface ()->issueCommand ("help.start ()", RCommand::App); RKWardMainWindow::getMain ()->topLevelWidget ()->raise (); } void RKTopLevelWindowGUI::startWhatsThis () { RK_TRACE (APP); QWhatsThis::enterWhatsThisMode (); } void RKTopLevelWindowGUI::reportRKWardBug () { RKErrorDialog::reportBug (for_window); } void RKTopLevelWindowGUI::showAboutApplication () { RK_TRACE (APP); KAboutApplicationDialog about (KCmdLineArgs::aboutData ()); about.exec (); } void RKTopLevelWindowGUI::toggleToolView (RKMDIWindow *tool_window) { RK_TRACE (APP); RK_ASSERT (tool_window); if (tool_window->isActive ()) { tool_window->close (false); activateDocumentView (); } else { tool_window->activate (true); } } void RKTopLevelWindowGUI::toggleToolView () { RK_TRACE (APP); QAction *act = dynamic_cast (sender ()); RK_ASSERT (act); RKMDIWindow *win = RKToolWindowList::findToolWindowById (act->property ("rk_toolwindow_id").toString ()); RK_ASSERT (win); toggleToolView (win); } void RKTopLevelWindowGUI::showHelpSearch () { RK_TRACE (APP); RKHelpSearchWindow::mainHelpSearch ()->activate (); } void RKTopLevelWindowGUI::showRKWardHelp () { RK_TRACE (APP); RKWorkplace::mainWorkplace ()->openHelpWindow (KUrl ("rkward://page/rkward_welcome"), true); } void RKTopLevelWindowGUI::activateDocumentView () { RK_TRACE (APP); RKMDIWindow *window = RKWorkplace::mainWorkplace ()->view ()->activePage (); if (window) window->activate (); } void RKTopLevelWindowGUI::slotOutputShow () { RK_TRACE (APP); RKWorkplace::mainWorkplace ()->openOutputWindow (KUrl ()); } void RKTopLevelWindowGUI::nextWindow () { RK_TRACE (APP); // well, this is sort of cumbersome, but the switcher widget gets keyboard focus, and so we need to register the window switching actions with it. RKWorkplace::getHistory ()->next (prev_action, next_action); } void RKTopLevelWindowGUI::previousWindow () { RK_TRACE (APP); RKWorkplace::getHistory ()->prev (prev_action, next_action); } #include "rktoplevelwindowgui.moc" rkward-0.6.4/rkward/windows/robjectbrowser.cpp0000664000175000017500000002442112633754364021131 0ustar thomasthomas/*************************************************************************** robjectbrowser - description ------------------- begin : Thu Aug 19 2004 copyright : (C) 2004 - 2015 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "robjectbrowser.h" #include #include #include #include #include #include #include #include #include #include "../rkward.h" #include "rkhelpsearchwindow.h" #include "../rkglobals.h" #include "../core/robjectlist.h" #include "../core/renvironmentobject.h" #include "../core/rkmodificationtracker.h" #include "../rbackend/rinterface.h" #include "../misc/rkobjectlistview.h" #include "../misc/rkdummypart.h" #include "../misc/rkstandardicons.h" #include "rkworkplace.h" #include "../dataeditor/rkeditor.h" #include "../debug.h" // static RObjectBrowser* RObjectBrowser::object_browser = 0; RObjectBrowser::RObjectBrowser (QWidget *parent, bool tool_window, const char *name) : RKMDIWindow (parent, WorkspaceBrowserWindow, tool_window, name) { RK_TRACE (APP); internal = 0; locked = true; QVBoxLayout *layout = new QVBoxLayout (this); layout->setContentsMargins (0, 0, 0, 0); layout_widget = new KVBox (this); layout->addWidget (layout_widget); layout_widget->setFocusPolicy (Qt::StrongFocus); RKDummyPart *part = new RKDummyPart (this, layout_widget); setPart (part); setMetaInfo (i18n ("R workspace browser"), "rkward://page/rkward_workspace_browser", RKSettings::PageObjectBrowser); initializeActivationSignals (); setCaption (i18n ("R Workspace")); } RObjectBrowser::~RObjectBrowser () { RK_TRACE (APP); } void RObjectBrowser::unlock () { RK_TRACE (APP); locked = false; if (!isHidden ()) { initialize (); } } void RObjectBrowser::showEvent (QShowEvent *e) { RK_TRACE (APP); initialize (); RKMDIWindow::showEvent (e); } void RObjectBrowser::initialize () { RK_TRACE (APP); if (internal) return; if (locked) return; RK_DEBUG (APP, DL_INFO, "creating workspace browser"); internal = new RObjectBrowserInternal (layout_widget); setFocusProxy (internal); setMinimumSize (internal->minimumSize ()); } ///////////////////////// RObjectBrowserInternal ///////////////////////////// RObjectBrowserInternal::RObjectBrowserInternal (QWidget *parent) : QWidget (parent) { RK_TRACE (APP); setFocusPolicy (Qt::ClickFocus); QVBoxLayout *vbox = new QVBoxLayout (this); vbox->setContentsMargins (0, 0, 0, 0); list_view = new RKObjectListView (true, this); vbox->addWidget (list_view->getSettings ()->filterWidget (this)); vbox->addWidget (list_view); update_button = new QPushButton (i18n ("Update"), this); vbox->addWidget (update_button); actions.insert (Help, new QAction (i18n ("Search Help"), this)); connect (actions[Help], SIGNAL(triggered(bool)), this, SLOT(popupHelp())); actions.insert (Edit, new QAction (i18n ("Edit"), this)); connect (actions[Edit], SIGNAL(triggered(bool)), this, SLOT(popupEdit())); actions.insert (View, new QAction (i18n ("View"), this)); connect (actions[View], SIGNAL(triggered(bool)), this, SLOT(popupView())); actions.insert (Rename, new QAction (i18n ("Rename"), this)); connect (actions[Rename], SIGNAL(triggered(bool)), this, SLOT(popupRename())); actions.insert (Copy, new QAction (i18n ("Copy to new symbol"), this)); connect (actions[Copy], SIGNAL(triggered(bool)), this, SLOT(popupCopy())); actions.insert (CopyToGlobalEnv, new QAction (i18n ("Copy to .GlobalEnv"), this)); connect (actions[CopyToGlobalEnv], SIGNAL(triggered(bool)), this, SLOT(popupCopyToGlobalEnv())); actions.insert (Delete, new QAction (i18n ("Delete"), this)); connect (actions[Delete], SIGNAL(triggered(bool)), this, SLOT(popupDelete())); actions.insert (Unload, new QAction (i18n ("Unload Package"), this)); connect (actions[Unload], SIGNAL(triggered(bool)), this, SLOT(popupUnload())); actions.insert (LoadUnloadPackages, new QAction (i18n ("Load / Unload Packages"), this)); connect (actions[LoadUnloadPackages], SIGNAL(triggered(bool)), RKWardMainWindow::getMain(), SLOT(slotFileLoadLibs())); QAction* sep = list_view->contextMenu ()->insertSeparator (list_view->contextMenu ()->actions ().value (0)); list_view->contextMenu ()->insertActions (sep, actions); connect (list_view, SIGNAL (aboutToShowContextMenu(RObject*,bool*)), this, SLOT (contextMenuCallback(RObject*,bool*))); connect (list_view, SIGNAL (doubleClicked(QModelIndex)), this, SLOT (doubleClicked(QModelIndex))); resize (minimumSizeHint ().expandedTo (QSize (400, 480))); list_view->initialize (); connect (update_button, SIGNAL (clicked()), this, SLOT (updateButtonClicked())); } RObjectBrowserInternal::~RObjectBrowserInternal () { RK_TRACE (APP); } void RObjectBrowserInternal::focusInEvent (QFocusEvent *e) { RK_TRACE (APP); list_view->getSettings ()->filterWidget (this)->setFocus (); if (e->reason () != Qt::MouseFocusReason) { list_view->setObjectCurrent (RObjectList::getGlobalEnv (), true); } } void RObjectBrowserInternal::updateButtonClicked () { RK_TRACE (APP); RObjectList::getObjectList ()->updateFromR (0); } void RObjectBrowserInternal::popupHelp () { RK_TRACE (APP); if (list_view->menuObject ()) RKHelpSearchWindow::mainHelpSearch ()->getFunctionHelp (list_view->menuObject ()->getShortName ()); } void RObjectBrowserInternal::popupEdit () { RK_TRACE (APP); if (list_view->menuObject ()) RKWorkplace::mainWorkplace ()->editObject (list_view->menuObject ()); } void RObjectBrowserInternal::popupCopy () { RK_TRACE (APP); bool ok; RObject *object = list_view->menuObject (); QString suggested_name = RObjectList::getGlobalEnv ()->validizeName (object->getShortName ()); QString name = KInputDialog::getText (i18n ("Copy object"), i18n ("Enter the name to copy to"), suggested_name, &ok, this); if (ok) { QString valid = RObjectList::getGlobalEnv ()->validizeName (name); if (valid != name) KMessageBox::sorry (this, i18n ("The name you specified was already in use or not valid. Renamed to %1", valid), i18n ("Invalid Name")); RKGlobals::rInterface ()->issueCommand (RObject::rQuote (valid) + " <- " + object->getFullName (), RCommand::App | RCommand::ObjectListUpdate); } } void RObjectBrowserInternal::popupCopyToGlobalEnv () { RK_TRACE (APP); RObject *object = list_view->menuObject (); QString name = object->getShortName (); QString valid = RObjectList::getGlobalEnv ()->validizeName (name); if (valid != name) KMessageBox::sorry (this, i18n ("An object named '%1' already exists in the GlobalEnv. Created the copy as '%2' instead.", name, valid), i18n ("Name already in use")); RKGlobals::rInterface ()->issueCommand (RObject::rQuote (valid) + " <- " + object->getFullName (), RCommand::App | RCommand::ObjectListUpdate); } void RObjectBrowserInternal::popupView () { RK_TRACE (APP); RKWorkplace::mainWorkplace ()->flushAllData (); RKWorkplace::mainWorkplace ()->newObjectViewer (list_view->menuObject ()); } void RObjectBrowserInternal::popupDelete () { RK_TRACE (APP); RKGlobals::tracker ()->removeObject (list_view->menuObject ()); } void RObjectBrowserInternal::popupUnload () { RK_TRACE (APP); RObject *object = list_view->menuObject (); RK_ASSERT (object); RK_ASSERT (object->isType (RObject::PackageEnv)); QStringList messages = RObjectList::getObjectList ()->detachPackages (QStringList (object->getShortName ())); if (!messages.isEmpty ()) KMessageBox::sorry (this, messages.join ("\n")); } void RObjectBrowserInternal::popupRename () { RK_TRACE (APP); bool ok; QString name = KInputDialog::getText (i18n ("Rename object"), i18n ("Enter the new name"), list_view->menuObject ()->getShortName (), &ok, this); if (ok) { QString valid = static_cast (list_view->menuObject ()->parentObject ())->validizeName (name); if (valid != name) KMessageBox::sorry (this, i18n ("The name you specified was already in use or not valid. Renamed to %1", valid), i18n ("Invalid Name")); RKGlobals::tracker ()->renameObject (list_view->menuObject (), valid); } } void RObjectBrowserInternal::contextMenuCallback (RObject *, bool *) { RK_TRACE (APP); RObject *object = list_view->menuObject (); if (!object) { RK_ASSERT (actions.size () == ActionCount); for (int i = 0; i < ActionCount; ++i) { actions[i]->setVisible (false); } actions[LoadUnloadPackages]->setVisible (true); return; } actions[Help]->setVisible (!(object->isType (RObject::ToplevelEnv) || object->isInGlobalEnv ())); actions[Edit]->setVisible (object->canEdit () && RKWorkplace::mainWorkplace ()->canEditObject (object)); actions[View]->setVisible (object->canRead ()); actions[Rename]->setVisible (object->canRename ()); actions[Copy]->setVisible (object->canRead () && (!object->isType (RObject::ToplevelEnv))); actions[CopyToGlobalEnv]->setVisible (object->canRead () && (!object->isInGlobalEnv()) && (!object->isType (RObject::ToplevelEnv))); actions[Delete]->setVisible (object->canRemove ()); actions[Unload]->setVisible (object->isType (RObject::PackageEnv)); actions[LoadUnloadPackages]->setVisible (object == RObjectList::getObjectList ()); } void RObjectBrowserInternal::doubleClicked (const QModelIndex& index) { RK_TRACE (APP); RObject *object = list_view->objectAtIndex (index); if (!object) return; if (object == RObjectList::getObjectList ()) return; if (object->canEdit () && RKWorkplace::mainWorkplace ()->canEditObject (object)) { RKWorkplace::mainWorkplace ()->editObject (object); } else { RKWorkplace::mainWorkplace ()->flushAllData (); RKWorkplace::mainWorkplace ()->newObjectViewer (object); } } #include "robjectbrowser.moc" rkward-0.6.4/rkward/windows/rkhelpwindow.rc0000664000175000017500000000147312633754364020436 0ustar thomasthomas &File &Edit &View rkward-0.6.4/rkward/windows/rkcommandlogpart.rc0000644000175000017500000000121612455741221021244 0ustar thomasthomas &Edit &Settings rkward-0.6.4/rkward/windows/rkcommandeditorwindow.h0000664000175000017500000002746312633754364022165 0ustar thomasthomas/*************************************************************************** rkcommandeditorwindow - description ------------------- begin : Mon Aug 30 2004 copyright : (C) 2004-2014 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKCOMMANDEDITORWINDOW_H #define RKCOMMANDEDITORWINDOW_H #include #include #include #include #include #include #include #include #include #if KDE_IS_VERSION(4,5,0) # include # include #else # include # include #endif #include #include "../windows/rkmdiwindow.h" class QEvent; class QCloseEvent; class QFrame; class QLabel; class KAction; class QAction; class KActionMenu; class RKCommandEditorWindow; class KActionCollection; /** This class provides a KPart interface to RKCommandEditorWindow. The reason to use this, is so the required menus/menu-items can be merged in on the fly. @author Thomas Friedrichsmeier */ class RKCommandEditorWindowPart : public KParts::Part { protected: friend class RKCommandEditorWindow; RKCommandEditorWindowPart (QWidget *parent); ~RKCommandEditorWindowPart (); }; /** classes wishing to use RKFunctionArgHinter should derive from this, and implement provideContext () */ class RKScriptContextProvider { public: RKScriptContextProvider () {}; virtual ~RKScriptContextProvider () {}; /** to be implemented in subclasses. Provide some context, i.e. text *preceding* the cursor position (probably a line, but you may provide chunks in arbitrary size). If line_rev is 0, provide the line, the cursor is in. If line_rev is greater than 0, provide context before that. @param context Place the context here @returns a chunk of context. A null QString(), if no context was available. */ virtual QString provideContext (int line_rev) = 0; }; class RObject; /** function argument hinting for RKCommandEditorWindow and RKConsole */ class RKFunctionArgHinter : public QObject { Q_OBJECT public: RKFunctionArgHinter (RKScriptContextProvider *provider, KTextEditor::View* view); ~RKFunctionArgHinter (); /** Try to show an arg hint now */ void tryArgHint (); /** Hide the arg hint (if shown) */ void hideArgHint (); public slots: /** Internal worker function for tryArgHint () */ void tryArgHintNow (); void updateArgHintWindow (); protected: /** The (keypress) events of the view are filtered to determine, when to show / hide an argument hint */ bool eventFilter (QObject *, QEvent *e); private: RKScriptContextProvider *provider; KTextEditor::View *view; /** A timer to refresh the hint window periodically. This is a bit sorry, but it's really hard to find out, when the view has been moved, or gains/loses focus. While possible, this approach uses much less code. */ QTimer updater; bool active; QLabel *arghints_popup; }; /** code completion model for RKCommandEditorWindow */ #if KDE_VERSION_MAJOR != 4 # error Adjust the versioning hack below! #endif // Unfortunately, MOC is not smart enough to understand the KDE_IS_VERSION macro #if KDE_VERSION_MINOR >= 2 # include # if KDE_VERSION_MINOR >= 5 class RKCodeCompletionModel : public KTextEditor::CodeCompletionModel, public KTextEditor::CodeCompletionModelControllerInterface3 { Q_OBJECT Q_INTERFACES(KTextEditor::CodeCompletionModelControllerInterface3) public: KTextEditor::Range completionRange (KTextEditor::View *view, const KTextEditor::Cursor &position); QString filterString (KTextEditor::View *, const KTextEditor::Range &, const KTextEditor::Cursor &) { return QString (); }; # else class RKCodeCompletionModel : public KTextEditor::CodeCompletionModel, public KTextEditor::CodeCompletionModelControllerInterface { Q_OBJECT Q_INTERFACES(KTextEditor::CodeCompletionModelControllerInterface) public: KTextEditor::Range completionRange (KTextEditor::View *view, const KTextEditor::Cursor &position); QString filterString (KTextEditor::View *, const KTextEditor::SmartRange &, const KTextEditor::Cursor &) { return QString (); }; # endif #else class RKCodeCompletionModel : public KTextEditor::CodeCompletionModel { #endif public: explicit RKCodeCompletionModel (RKCommandEditorWindow* parent); ~RKCodeCompletionModel (); void updateCompletionList (const QString& symbol); void completionInvoked (KTextEditor::View *, const KTextEditor::Range &, InvocationType); void executeCompletionItem (KTextEditor::Document *document, const KTextEditor::Range &word, int row) const; QVariant data (const QModelIndex& index, int role=Qt::DisplayRole) const; bool isEmpty () const { return names.isEmpty (); }; private: QList icons; QStringList names; QString current_symbol; RKCommandEditorWindow *command_editor; }; class QTimer; class RKJobSequence; /** \brief Provides an editor window for R-commands, as well as a text-editor window in general. While being called RKCommandEditorWindow, this class handles all sorts of text-files, both read/write and read-only. It is an MDI window that is added to the main window, based on KatePart. @author Pierre Ecochard */ class RKCommandEditorWindow : public RKMDIWindow, public RKScriptContextProvider { // we need the Q_OBJECT thing for some inherits ("RKCommandEditorWindow")-calls in rkward.cpp. Q_OBJECT public: /** constructor @param use_r_highlighting Initialize the view to use R syntax highlighting. Use, if you're going to edit an R syntax file */ explicit RKCommandEditorWindow (QWidget *parent = 0, bool use_r_highlighting=true, bool use_codehinting=true); /** destructor */ ~RKCommandEditorWindow (); /** open given URL. @param use_r_highlighting Initialize the view to use R syntax highlighting. Use, if you're going to edit an R syntax file @param encoding encoding to use. If QString (), the default encoding is used. @param read_only Open the file in read-only mode @param delete_on_close File should be deleted when closing the window. Only respected with read_only=true. */ bool openURL (const KUrl url, const QString& encoding=QString (), bool use_r_highlighting=true, bool read_only=false, bool delete_on_close=false); /** returns, whether the document was modified since the last save */ bool isModified (); /** insert the given text into the document at the current cursor position. Additionally, focuses the view */ void insertText (const QString &text); /** set the current text (clear all previous text, and sets new text) */ void setText (const QString &text); /** @see restoreScrollPosition (). Note: Currently this saves/restored the cursor position, not necessarily the scroll position. */ void saveScrollPosition (); /** @see saveScrollPosition (). Note: Currently this saves/restored the cursor position, not necessarily the scroll position. */ void restoreScrollPosition (); /** copy current selection. Wrapper for use by external classes */ void copy (); /** reimplemented from RKMDIWindow to return full path of file (if any) */ QString fullCaption (); void setReadOnly (bool ro); /** Return current url */ KUrl url (); QString provideContext (int line_rev); QString currentCompletionWord () const; void highlightLine (int linenum); public slots: /** update Tab caption according to the current url. Display the filename-component of the URL, or - if not available - a more elaborate description of the url. Also appends a "[modified]" if appropriate */ void updateCaption (KTextEditor::Document* = 0); /** called whenever it might be appropriate to show a code completion box. The box is not shown immediately, but only after a timeout (if at all) */ void tryCompletionProxy (KTextEditor::Document*); /** show a code completion box if appropriate. Use tryCompletionProxy () instead, which will call this function after a timeout */ void tryCompletion (); void setPopupMenu (); void focusIn (KTextEditor::View *); /** Show help about the current word. */ void showHelp (); /** run the currently selected command(s) or line */ void runCurrent (); /** run the entire script */ void runAll (); /** insert line break and run the (previous) line */ void enterAndSubmit (); void copyLinesToOutput (); /** selection has changed. Enable / disable actions accordingly */ void selectionChanged (KTextEditor::View* view); /** change to the directory of the current script */ void setWDToScript (); /** paste the given text at the current cursor position */ void paste (const QString &text); /** apply our customizations to the katepart GUI */ void fixupPartGUI (); QAction* fileSaveAction () { return file_save; }; QAction* fileSaveAsAction () { return file_save_as; }; protected: /** reimplemented from RKMDIWindow: give the editor window a chance to object to being closed (if unsaved) */ void closeEvent (QCloseEvent *e); private slots: /** mark current selection as a block */ void markBlock (); /** unmark a block */ void unmarkBlock (); /** run a block */ void runBlock (); void clearUnusedBlocks (); /** creates an autosave file */ void doAutoSave (); /** handler to control when autosaves should be created */ void autoSaveHandlerModifiedChanged (); /** handler to control when autosaves should be created */ void autoSaveHandlerTextChanged (); /** handle any errors during auto-saving */ void autoSaveHandlerJobFinished (RKJobSequence* seq); private: KTextEditor::Cursor saved_scroll_position; KTextEditor::Document *m_doc; KTextEditor::View *m_view; KTextEditor::CodeCompletionInterface *cc_iface; #if KDE_IS_VERSION(4,5,0) KTextEditor::MovingInterface *smart_iface; #else KTextEditor::SmartInterface *smart_iface; #endif RKFunctionArgHinter *hinter; RKCodeCompletionModel *completion_model; QTimer *completion_timer; void initializeActions (KActionCollection* ac); struct BlockRecord { #if KDE_IS_VERSION(4,5,0) KTextEditor::MovingRange* range; #else KTextEditor::SmartRange* range; #endif bool active; KTextEditor::Attribute::Ptr attribute; KAction* mark; KAction* unmark; KAction* run; }; QVector block_records; void initBlocks (); void addBlock (int index, const KTextEditor::Range& range); void removeBlock (int index, bool was_deleted=false); QAction *file_save, *file_save_as; KActionMenu* actionmenu_mark_block; KActionMenu* actionmenu_unmark_block; KActionMenu* actionmenu_run_block; KAction* action_run_all; KAction* action_run_current; KAction* action_setwd_to_script; KAction* action_help_function; KUrl previous_autosave_url; QTimer* autosave_timer; KUrl delete_on_close; }; /** Simple class to provide HTML highlighting for arbitrary R code. */ class RKCommandHighlighter { public: enum HighlightingMode { RInteractiveSession, RScript, Automatic }; static void copyLinesToOutput (KTextEditor::View *view, HighlightingMode mode); static void setHighlighting (KTextEditor::Document *doc, HighlightingMode mode); static QString commandToHTML (const QString r_command, HighlightingMode mode=RScript); private: static KTextEditor::Document* getDoc (); static KTextEditor::Document* _doc; }; #endif rkward-0.6.4/rkward/windows/rkdebugconsole.cpp0000664000175000017500000001432612633754364021106 0ustar thomasthomas/*************************************************************************** rkdebugconsole - description ------------------- begin : Wed Oct 19 2011 copyright : (C) 2011 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkdebugconsole.h" #include #include #include #include #include #include #include #include #include "../agents/rkdebughandler.h" #include "../misc/rkdummypart.h" #include "../misc/rkcommonfunctions.h" #include "../rkconsole.h" #include "../debug.h" RKDebugConsole* RKDebugConsole::_instance = 0; RKDebugConsole::RKDebugConsole (QWidget *parent, bool tool_window, const char *name) : RKMDIWindow (parent, DebugConsoleWindow, tool_window, name) { RK_TRACE (APP); QVBoxLayout *main_layout = new QVBoxLayout (this); main_layout->setContentsMargins (0, 0, 0, 0); QHBoxLayout *upper_layout = new QHBoxLayout (); main_layout->addLayout (upper_layout); context_view = new QTextEdit (this); context_view->setReadOnly (true); context_view->setAcceptRichText (false); upper_layout->addWidget (context_view); QVBoxLayout *button_layout = new QVBoxLayout (); upper_layout->addLayout (button_layout); step_button = new QPushButton (i18n ("Next"), this); connect (step_button, SIGNAL (clicked()), this, SLOT (stepButtonClicked())); button_layout->addWidget (step_button); step_out_button = new QPushButton (i18n ("Step out"), this); connect (step_out_button, SIGNAL (clicked()), this, SLOT (stepOutButtonClicked())); RKCommonFunctions::setTips (i18n ("

Continue until the caller of this function is reached (unless another debug statement is hit, earlier)

" "

Note: In some cases, the calling function will never be reached, because the call was the last step in the caller. " "In these cases, the behavior is identical to 'Continue'.

"), step_out_button); button_layout->addWidget (step_out_button); continue_button = new QPushButton (i18n ("Continue"), this); connect (continue_button, SIGNAL (clicked()), this, SLOT (continueButtonClicked())); button_layout->addWidget (continue_button); cancel_button = new QPushButton (i18n ("Cancel"), this); connect (cancel_button, SIGNAL (clicked()), this, SLOT (cancelButtonClicked())); button_layout->addWidget (cancel_button); button_layout->addStretch (); QHBoxLayout *lower_layout = new QHBoxLayout (); main_layout->addLayout (lower_layout); prompt_label = new QLabel (this); lower_layout->addWidget (prompt_label); reply_edit = new KHistoryComboBox (this); reply_edit->setSizePolicy (QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); connect (reply_edit, SIGNAL (returnPressed()), this, SLOT (sendReply())); lower_layout->addWidget (reply_edit); setFocusProxy (reply_edit); setFocusPolicy (Qt::StrongFocus); setPart (new RKDummyPart (this, this)); initializeActivationSignals (); connect (RKDebugHandler::instance (), SIGNAL (newDebugState()), this, SLOT (newDebugState())); newDebugState (); } RKDebugConsole::~RKDebugConsole () { RK_TRACE (APP); } void RKDebugConsole::newDebugState () { RK_TRACE (APP); bool enable = true; if (RKDebugHandler::instance ()->state () == RKDebugHandler::NotInDebugger) { context_view->setPlainText (i18n ("Not in a debugger context")); setEnabled (false); return; } else if (RKDebugHandler::instance ()->state () == RKDebugHandler::InDebugRun) { enable = false; reply_edit->setEnabled (false); } else { context_view->setPlainText (RKDebugHandler::instance ()->outputContext ()); prompt_label->setText (RKDebugHandler::instance ()->debugPrompt ()); reply_edit->setEnabled (true); // must come before focus QStringList ch = RKConsole::mainConsole ()->commandHistory (); QStringList ch_rev; // limit to 100 items (dropdown list!), and reverse to have most recent item first. for (int i = ch.size () - 1; i >= qMax (0, ch.size () - 101); --i) { ch_rev.append (ch[i]); } reply_edit->setMaxCount (ch_rev.size ()); reply_edit->setHistoryItems (ch_rev); activate (true); } setEnabled (true); step_button->setEnabled (enable); step_out_button->setEnabled (enable); continue_button->setEnabled (enable); cancel_button->setEnabled (enable); } void RKDebugConsole::sendReply () { RK_TRACE (APP); QString reply = reply_edit->currentText (); sendReply (reply); RKConsole::mainConsole ()->addCommandToHistory (reply); reply_edit->clear (); } void RKDebugConsole::stepOutButtonClicked () { RK_TRACE (APP); sendReply ("browserSetDebug(1)\ncont\n"); } void RKDebugConsole::stepButtonClicked () { RK_TRACE (APP); sendReply ("n\n"); } void RKDebugConsole::continueButtonClicked () { RK_TRACE (APP); sendReply ("cont\n"); } void RKDebugConsole::cancelButtonClicked () { RK_TRACE (APP); RKDebugHandler::instance ()->sendCancel (); } void RKDebugConsole::sendReply (const QString &reply) { RK_TRACE (APP); RKDebugHandler::instance ()->submitDebugString (reply); } bool RKDebugConsole::close (bool also_delete) { RK_TRACE (APP); if (RKDebugHandler::instance ()->state () != RKDebugHandler::NotInDebugger) { KMessageBox::sorry (this, i18n ("This window cannot be closed, while a debugger is active. If you have no idea what this means, and you want to get out, press the 'Cancel' button on the right hand side of this window.")); return false; } return RKMDIWindow::close (also_delete); } #include "rkdebugconsole.moc" rkward-0.6.4/rkward/windows/rkworkplace.h0000664000175000017500000002310212633754364020061 0ustar thomasthomas/*************************************************************************** rkworkplace - description ------------------- begin : Thu Sep 21 2006 copyright : (C) 2006-2013 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKWORKPLACE_H #define RKWORKPLACE_H #include #include #include #include #include #include "rkmdiwindow.h" #include "rktoolwindowlist.h" class RObject; class RCommandChain; class RKWorkplaceView; class RKEditor; class KActionCollection; class KAction; class RKToolWindowBar; class RKMDIWindowHistoryWidget; class RKGraphicsDevice; #define TOOL_WINDOW_BAR_COUNT 4 /** Simple class to store the history of recently used RKMDIWindow */ class RKMDIWindowHistory : public QObject { Q_OBJECT public: explicit RKMDIWindowHistory (QObject *parent); ~RKMDIWindowHistory (); void removeWindow (RKMDIWindow *window); /** pops the last window from the list, if it matches the given pointer */ void popLastWindow (RKMDIWindow *match); RKMDIWindow *previousDocumentWindow (); void next (KAction *prev_action, KAction *next_action); void prev (KAction *prev_action, KAction *next_action); public slots: void windowActivated (RKMDIWindow *window); private slots: void switcherDestroyed (); private: void updateSwitcher (); QList recent_windows; RKMDIWindowHistoryWidget *switcher; RKMDIWindowHistoryWidget *getSwitcher (KAction *prev_action, KAction *next_action); }; /** This class (only one instance will probably be around) keeps track of which windows are opened in the workplace, which are detached, etc. Also it is responsible for creating and manipulating those windows. It also provides a QWidget (RKWorkplace::view ()), which actually manages the document windows (only those, so far. I.e. this is a half-replacement for KMdi, which will be gone in KDE 4). Currently layout of the document windows is always tabbed. */ class RKWorkplace : public QWidget { Q_OBJECT public: /** ctor. @param parent: The parent widget for the workspace view (see view ()) */ explicit RKWorkplace (QWidget *parent); ~RKWorkplace (); void initActions (KActionCollection *ac, const char *left_id, const char *right_id); /** @returns a pointer to the view of the workplace. Since possibly the workplace layout might change, better not rely on this pointer being valid for long */ RKWorkplaceView *view () { return wview; }; /** convenience typedef: A list of RKMDIWindow s */ typedef QList RKWorkplaceObjectList; /** Returns a list of all windows in the workplace. */ RKWorkplaceObjectList getObjectList () { return windows; }; /** Returns a list of all windows with a given type and state */ RKWorkplaceObjectList getObjectList (int type, int state=RKMDIWindow::AnyWindowState); /** Attach an already created window. */ void attachWindow (RKMDIWindow *window); /** Dettach a window (it is removed from the view (), and placed in a top-level DetachedWindowContainer instead. */ void detachWindow (RKMDIWindow *window, bool was_attached=true); /** @returns a pointer to the current window. state specifies, which windows should be considered. */ RKMDIWindow *activeWindow (RKMDIWindow::State state); /** Opens the given url in the appropriate way. */ bool openAnyUrl (const KUrl &url, const QString &known_mimetype = QString (), bool force_external=false); /** Opens a new script editor @param url URL to load. Default option is to open an empty document @param encoding encoding to use. If QString (), the default encoding is used. @param use_r_highlighting Set R highlighting mode (vs. no highlighting)? Default is yes @param read_only Open the document read only? Default is false, i.e. Read-write @param force_caption Usually the caption is determined from the url of the file. If you specify a non-empty string here, that is used instead. @returns false if a local url could not be opened, true for all remote urls, and on success */ RKMDIWindow* openScriptEditor (const KUrl &url=KUrl (), const QString& encoding=QString (), bool use_r_highlighting=true, bool read_only=false, const QString &force_caption = QString (), bool delete_on_close=false); /** Opens a new help window, starting at the given url @param url URL to open @param only_once if true, checks whether any help window already shows this URL. If so, raise it, but do not open a new window. Else show the new window */ RKMDIWindow* openHelpWindow (const KUrl &url=KUrl (), bool only_once=false); /** Opens a new output window. Currently only a single output window will ever be created. Subsequent calls to the function will not create additional windows right now (but will raise / refresh the output window @param url currently ignored! */ RKMDIWindow* openOutputWindow (const KUrl &url=KUrl ()); void newX11Window (WId window_to_embed, int device_number); void newRKWardGraphisWindow (RKGraphicsDevice *dev, int device_number); void newObjectViewer (RObject *object); /** @returns true if there is a known editor for this type of object, false otherwise */ bool canEditObject (RObject *object); /** Creates a new editor of an appropriate type, and loads the given object into the editor @param object object to edit @returns a pointer to the editor */ RKEditor* editObject (RObject *object); /** Creates a new data.frame with the given name, and loads it in an editor. @see editObject() @param name Name of the data.frame to create @returns a pointer to the editor */ RKEditor* editNewDataFrame (const QString& name); /** tell all DataEditorWindow s to synchronize changes to the R backend // TODO: add RCommandChain parameter */ void flushAllData (); /** Close the active (attached) window. Safe to call even if there is no current active window (no effect in that case) */ void closeActiveWindow (); /** Close the given window, whether it is attached or detached. @param window window to close */ void closeWindow (RKMDIWindow *window); /** Closes all windows of the given type(s). Default call (no arguments) closes all windows @param type: A bitwise OR of RKWorkplaceObjectType @param state: A bitwise OR of RKWorkplaceObjectState */ void closeAll (int type=RKMDIWindow::AnyType, int state=RKMDIWindow::AnyWindowState); /** Write a description of all current windows to the R backend. This can later be read by restoreWorkplace (). Has no effect, if RKSettingsModuleGeneral::workplaceSaveMode () != RKSettingsModuleGeneral::SaveWorkplaceWithWorkspace @param chain command chain to place the command in */ void saveWorkplace (RCommandChain *chain=0); /** Load a description of windows from the R backend (created by saveWorkplace ()), and (try to) restore all windows accordingly Has no effect, if RKSettingsModuleGeneral::workplaceSaveMode () != RKSettingsModuleGeneral::SaveWorkplaceWithWorkspace @param chain command chain to place the command in */ void restoreWorkplace (RCommandChain *chain=0, bool merge=false); /** Like the other restoreWorkplace (), but takes the description as a parameter rather than reading from the R workspace. To be used, when RKSettingsModuleGeneral::workplaceSaveMode () == RKSettingsModuleGeneral::SaveWorkplaceWithSeesion @param description workplace description */ void restoreWorkplace (const QStringList &description); QStringList makeWorkplaceDescription (); /** In the current design there is only ever one workplace. Use this static function to reference it. @returns a pointer to the workplace */ static RKWorkplace *mainWorkplace () { return main_workplace; }; static RKMDIWindowHistory *getHistory () { return main_workplace->history; }; void placeToolWindows (); void setWorkspaceURL (const KUrl &url, bool keep_config=false); KUrl workspaceURL () const { return current_url; }; KConfigBase *workspaceConfig (); QString portableUrl (const KUrl &url); signals: /** emitted when the workspace Url has changed */ void workspaceUrlChanged (const KUrl& url); public slots: /** When windows are attached to the workplace, their QObject::destroyed () signal is connected to this slot. Thereby deleted objects are removed from the workplace automatically */ void removeWindow (QObject *window); void saveSettings (); private: KUrl current_url; KConfig *_workspace_config; /** current list of windows. @See getObjectList () */ RKWorkplaceObjectList windows; /** the view. @See view () */ RKWorkplaceView *wview; /** Internal function to add an existing window to the list of mdi windows */ void addWindow (RKMDIWindow *window, bool attached=true); /** static pointer to the workplace. @See mainWorkplace () */ static RKWorkplace *main_workplace; /** a window was removed. Try to activate some other window. */ void windowRemoved (); RKMDIWindowHistory *history; QSplitter *horiz_splitter; QSplitter *vert_splitter; RKToolWindowBar* tool_window_bars[TOOL_WINDOW_BAR_COUNT]; friend class RKToolWindowBar; void placeInToolWindowBar (RKMDIWindow *window, int position); }; #endif rkward-0.6.4/rkward/windows/rkmdiwindow.h0000664000175000017500000001446012633754364020102 0ustar thomasthomas/*************************************************************************** rkmdiwindow - description ------------------- begin : Tue Sep 26 2006 copyright : (C) 2006, 2007, 2008, 2009, 2010 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKMDIWINDOW_H #define RKMDIWINDOW_H #include #include #include #include "../settings/rksettings.h" class QEvent; class QPaintEvent; class RKWorkplace; class RKToolWindowBar; class RKMDIStandardActionClient : public KXMLGUIClient { public: RKMDIStandardActionClient (); ~RKMDIStandardActionClient (); }; /** Base class for rkward document mdi windows */ class RKMDIWindow : public QFrame { Q_OBJECT public: enum Type { DataEditorWindow=1 << 0, CommandEditorWindow=1 << 1, OutputWindow=1 << 2, HelpWindow=1 << 3, X11Window=1 << 4, ObjectWindow=1 << 5, ConsoleWindow=1 << 10, CommandLogWindow=1 << 11, WorkspaceBrowserWindow=1 << 12, SearchHelpWindow=1 << 13, PendingJobsWindow=1 << 14, FileBrowserWindow=1 << 15, DebugConsoleWindow=1 << 16, CallstackViewerWindow=1 << 17, DebugMessageWindow=1 << 18, DocumentWindow=1 << 29, ToolWindow=1 << 30, AnyType=DocumentWindow | ToolWindow }; enum State { Attached=1, Detached=2, AnyWindowState=Attached | Detached }; protected: /** constructor @param parent parent widget @param type Type of window (see RKMDIWindow::Type).*/ RKMDIWindow (QWidget *parent, int type, bool tool_window=false, const char *name=0); virtual ~RKMDIWindow (); public slots: /** Reimplemented from QWidget::setCaption () to emit the signal captionChanged () when the caption is changed. */ void setCaption (const QString &caption); public: /** @returns true, if the window's document was modified (and would need to be saved) */ virtual bool isModified () { return false; }; /** @returns A long / complete caption. Default implementation simply calls shortCaption () */ virtual QString fullCaption (); /** @returns A short caption (e.g. only the filename without the path). Default implementation simply calls QWidget::caption () */ virtual QString shortCaption (); /** @returns The corresponding KPart for this window */ KParts::Part *getPart () { return part; }; /** Is this window attached (or detached)? @returns true if attached, false if detached */ bool isAttached () const { return (state == Attached); }; /** Is this a tool window? */ bool isToolWindow () const { return (type & ToolWindow); }; /** Returns the type of this window */ bool isType (Type t) const { return (type & t); }; /** Activate (raise) this window, regardless of whether it is attached or detached @param with_focus Should the window also get keyboard focus? */ virtual void activate (bool with_focus=true); /** If your mdi window should perform any adjustments before being attached, reimplement this function. Default implementation does nothing, but raises an assert, if this is a tool window */ virtual void prepareToBeAttached (); /** If your mdi window should perform any adjustments before being detached, reimplement this function. Default implementation does nothing, but raises an assert, if this is a tool window */ virtual void prepareToBeDetached (); /** Tool windows will only hide themselves, and ignore the also_delete flag */ virtual bool close (bool also_delete); bool eventFilter (QObject *watched, QEvent *e); bool acceptsEventsFor (QObject *object); /** Whether the window is active. This seems to be more reliable than hasFocus () */ bool isActive (); /** Returns a pointer to an action collection suitable to place RKStandardAction in. This collection (and the corresponding KXMLGUIClient) is created on the fly. */ KActionCollection *standardActionCollection (); /** plugin-accessible properties of this object in the global context. Currently used only by RKEditorDataFrame to give information on the currently active data.frame. NOTE: ATM, you cannot set arbitrary properties. Only those supported in RKStandardComponent will have an effect. */ QString globalContextProperty (const QString& property) { return global_context_properties.value (property); }; signals: /** This signal is emitted, whenever the window caption was changed. @param RKMDIWindow* a pointer to this window */ void captionChanged (RKMDIWindow *); /** This signal is emitted, when the window was activated *with* focus */ void windowActivated (RKMDIWindow *); protected slots: void showWindowHelp (); void showWindowSettings (); protected: void setPart (KParts::Part *p) { part = p; }; void setMetaInfo (const QString& generic_window_name, const QString& help_url, RKSettings::SettingsPage settings_page=RKSettings::NoPage); void initializeActivationSignals (); void paintEvent (QPaintEvent *e); void windowActivationChange (bool); /** reimplemented from QWidget to emulate focus-follows-mouse behavior */ void enterEvent (QEvent *event); /** @see globalContextProperty() */ void setGlobalContextProperty (const QString& property, const QString& value) { global_context_properties.insert (property, value); }; friend class RKWorkplace; /** type of this window */ int type; private slots: void slotActivate (); private: friend class RKToolWindowBar; /** state of this window (attached / detached). This is usually set from the RKWorkplace */ KParts::Part *part; State state; RKToolWindowBar *tool_window_bar; bool active; RKMDIStandardActionClient *standard_client; /** @see globalContextProperty() */ QMap global_context_properties; QString generic_window_name; QString help_url; RKSettings::SettingsPage settings_page; }; #endif rkward-0.6.4/rkward/windows/rkcommandeditorwindow.cpp0000664000175000017500000013203112633754364022504 0ustar thomasthomas/*************************************************************************** rkcommandeditorwindow - description ------------------- begin : Mon Aug 30 2004 copyright : (C) 2004-2015 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkcommandeditorwindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../misc/rkcommonfunctions.h" #include "../misc/rkstandardicons.h" #include "../misc/rkstandardactions.h" #include "../misc/rkxmlguisyncer.h" #include "../misc/rkjobsequence.h" #include "../core/robjectlist.h" #include "../rbackend/rinterface.h" #include "../settings/rksettings.h" #include "../settings/rksettingsmodulecommandeditor.h" #include "../rkconsole.h" #include "../rkglobals.h" #include "../rkward.h" #include "rkhelpsearchwindow.h" #include "rkworkplace.h" #include "../debug.h" RKCommandEditorWindowPart::RKCommandEditorWindowPart (QWidget *parent) : KParts::Part (parent) { RK_TRACE (COMMANDEDITOR); setComponentData (KGlobal::mainComponent ()); setWidget (parent); setXMLFile ("rkcommandeditorwindowpart.rc"); } RKCommandEditorWindowPart::~RKCommandEditorWindowPart () { RK_TRACE (COMMANDEDITOR); } #define GET_HELP_URL 1 #define NUM_BLOCK_RECORDS 6 RKCommandEditorWindow::RKCommandEditorWindow (QWidget *parent, bool use_r_highlighting, bool use_codehinting) : RKMDIWindow (parent, RKMDIWindow::CommandEditorWindow) { RK_TRACE (COMMANDEDITOR); KTextEditor::Editor* editor = KTextEditor::editor("katepart"); RK_ASSERT (editor); m_doc = editor->createDocument (this); RK_ASSERT (m_doc); // yes, we want to be notified, if the file has changed on disk. // why, oh why is this not the default? // this needs to be set *before* the view is created! KTextEditor::ModificationInterface* em_iface = qobject_cast (m_doc); if (em_iface) em_iface->setModifiedOnDiskWarning (true); else RK_ASSERT (false); m_view = m_doc->createView (this); m_doc->editor ()->readConfig (); setFocusProxy (m_view); setFocusPolicy (Qt::StrongFocus); RKCommandEditorWindowPart* part = new RKCommandEditorWindowPart (m_view); part->insertChildClient (m_view); setPart (part); fixupPartGUI (); setMetaInfo (i18n ("Script Editor"), QString (), RKSettings::PageCommandEditor); initializeActions (part->actionCollection ()); initializeActivationSignals (); RKXMLGUISyncer::self()->registerChangeListener (m_view, this, SLOT (fixupPartGUI())); QHBoxLayout *layout = new QHBoxLayout (this); layout->setContentsMargins (0, 0, 0, 0); layout->addWidget(m_view); connect (m_doc, SIGNAL (documentUrlChanged(KTextEditor::Document*)), this, SLOT (updateCaption(KTextEditor::Document*))); connect (m_doc, SIGNAL (modifiedChanged(KTextEditor::Document*)), this, SLOT (updateCaption(KTextEditor::Document*))); // of course most of the time this causes a redundant call to updateCaption. Not if a modification is undone, however. connect (m_doc, SIGNAL (modifiedChanged(KTextEditor::Document*)), this, SLOT (autoSaveHandlerModifiedChanged())); connect (m_doc, SIGNAL (textChanged(KTextEditor::Document*)), this, SLOT (autoSaveHandlerTextChanged())); connect (m_view, SIGNAL (selectionChanged(KTextEditor::View*)), this, SLOT (selectionChanged(KTextEditor::View*))); // somehow the katepart loses the context menu each time it loses focus connect (m_view, SIGNAL (focusIn(KTextEditor::View*)), this, SLOT (focusIn(KTextEditor::View*))); completion_model = 0; cc_iface = 0; hinter = 0; if (use_r_highlighting) { RKCommandHighlighter::setHighlighting (m_doc, RKCommandHighlighter::RScript); if (use_codehinting) { cc_iface = qobject_cast (m_view); if (cc_iface) { cc_iface->setAutomaticInvocationEnabled (true); completion_model = new RKCodeCompletionModel (this); completion_timer = new QTimer (this); completion_timer->setSingleShot (true); connect (completion_timer, SIGNAL (timeout()), this, SLOT (tryCompletion())); connect (m_doc, SIGNAL (textChanged(KTextEditor::Document*)), this, SLOT (tryCompletionProxy(KTextEditor::Document*))); } else { RK_ASSERT (false); } hinter = new RKFunctionArgHinter (this, m_view); } } #if KDE_IS_VERSION(4,5,0) smart_iface = qobject_cast (m_doc); #else smart_iface = qobject_cast (m_doc); #endif initBlocks (); RK_ASSERT (smart_iface); autosave_timer = new QTimer (this); connect (autosave_timer, SIGNAL (timeout()), this, SLOT (doAutoSave())); updateCaption (); // initialize QTimer::singleShot (0, this, SLOT (setPopupMenu())); } RKCommandEditorWindow::~RKCommandEditorWindow () { RK_TRACE (COMMANDEDITOR); // NOTE: TODO: Ideally we'd only write out a changed config, but how to detect config changes? // Alternatively, only for the last closed script window m_doc->editor ()->writeConfig (); if (!url ().isEmpty ()) { KTextEditor::SessionConfigInterface *iface = qobject_cast (m_doc); QString p_url = RKWorkplace::mainWorkplace ()->portableUrl (m_doc->url ()); if (iface) { KConfigGroup conf (RKWorkplace::mainWorkplace ()->workspaceConfig (), QString ("SkriptDocumentSettings %1").arg (p_url)); iface->writeSessionConfig (conf); } iface = qobject_cast (m_view); if (iface) { KConfigGroup conf (RKWorkplace::mainWorkplace ()->workspaceConfig (), QString ("SkriptViewSettings %1").arg (p_url)); iface->writeSessionConfig (conf); } } delete hinter; delete m_doc; if (!delete_on_close.isEmpty ()) KIO::del (delete_on_close)->start (); } void RKCommandEditorWindow::fixupPartGUI () { RK_TRACE (COMMANDEDITOR); // strip down the katepart's GUI. remove some stuff we definitely don't need. RKCommonFunctions::removeContainers (m_view, QString ("bookmarks,tools_spelling,tools_spelling_from_cursor,tools_spelling_selection,switch_to_cmd_line").split (','), true); RKCommonFunctions::moveContainer (m_view, "Menu", "tools", "edit", true); } QAction *findAction (KTextEditor::View* view, const QString &actionName) { // katepart has more than one actionCollection QList acs = view->findChildren(); acs.append (view->actionCollection ()); foreach (KActionCollection* ac, acs) { QAction* found = ac->action (actionName); if (found) return found; } return 0; } void RKCommandEditorWindow::initializeActions (KActionCollection* ac) { RK_TRACE (COMMANDEDITOR); RKStandardActions::copyLinesToOutput (this, this, SLOT (copyLinesToOutput())); RKStandardActions::pasteSpecial (this, this, SLOT (paste(QString))); action_run_all = RKStandardActions::runAll (this, this, SLOT (runAll())); action_run_current = RKStandardActions::runCurrent (this, this, SLOT (runCurrent()), true); // NOTE: enter_and_submit is not currently added to the menu KAction *action = ac->addAction ("enter_and_submit", this, SLOT (enterAndSubmit())); action->setText (i18n ("Insert line break and run")); action->setShortcuts (KShortcut (Qt::AltModifier + Qt::Key_Return, Qt::AltModifier + Qt::Key_Enter)); action_help_function = RKStandardActions::functionHelp (this, this, SLOT (showHelp())); actionmenu_run_block = new KActionMenu (i18n ("Run block"), this); actionmenu_run_block->setDelayed (false); // KDE4: TODO does not work correctly in the tool bar. ac->addAction ("run_block", actionmenu_run_block); connect (actionmenu_run_block->menu(), SIGNAL (aboutToShow()), this, SLOT (clearUnusedBlocks())); actionmenu_mark_block = new KActionMenu (i18n ("Mark selection as block"), this); ac->addAction ("mark_block", actionmenu_mark_block); connect (actionmenu_mark_block->menu(), SIGNAL (aboutToShow()), this, SLOT (clearUnusedBlocks())); actionmenu_unmark_block = new KActionMenu (i18n ("Unmark block"), this); ac->addAction ("unmark_block", actionmenu_unmark_block); connect (actionmenu_unmark_block->menu(), SIGNAL (aboutToShow()), this, SLOT (clearUnusedBlocks())); action_setwd_to_script = ac->addAction ("setwd_to_script", this, SLOT (setWDToScript())); action_setwd_to_script->setText (i18n ("CD to script directory")); #if KDE_IS_VERSION(4,3,0) action_setwd_to_script->setHelpText (i18n ("Change the working directory to the directory of this script")); #endif action_setwd_to_script->setIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionCDToScript)); file_save = findAction (m_view, "file_save"); if (file_save) file_save->setText (i18n ("Save Script...")); file_save_as = findAction (m_view, "file_save_as"); if (file_save_as) file_save_as->setText (i18n ("Save Script As...")); } void RKCommandEditorWindow::initBlocks () { RK_TRACE (COMMANDEDITOR); if (!smart_iface) return; // may happen in KDE => 4.6 if compiled with KDE <= 4.4 RK_ASSERT (block_records.isEmpty ()); KActionCollection* ac = getPart ()->actionCollection (); int i = 0; QColor colors[NUM_BLOCK_RECORDS]; colors[i++] = QColor (255, 0, 0); colors[i++] = QColor (0, 255, 0); colors[i++] = QColor (0, 0, 255); colors[i++] = QColor (255, 255, 0); colors[i++] = QColor (255, 0, 255); colors[i++] = QColor (0, 255, 255); RK_ASSERT (i == NUM_BLOCK_RECORDS); // sorry for those idiotic shortcuts, but I just could not find any decent unused ones i = 0; QKeySequence shortcuts[NUM_BLOCK_RECORDS]; shortcuts[i++] = QKeySequence (Qt::AltModifier | Qt::ShiftModifier | Qt::Key_F1); shortcuts[i++] = QKeySequence (Qt::AltModifier | Qt::ShiftModifier | Qt::Key_F2); shortcuts[i++] = QKeySequence (Qt::AltModifier | Qt::ShiftModifier | Qt::Key_F3); shortcuts[i++] = QKeySequence (Qt::AltModifier | Qt::ShiftModifier | Qt::Key_F4); shortcuts[i++] = QKeySequence (Qt::AltModifier | Qt::ShiftModifier | Qt::Key_F5); shortcuts[i++] = QKeySequence (Qt::AltModifier | Qt::ShiftModifier | Qt::Key_F6); RK_ASSERT (i == NUM_BLOCK_RECORDS); for (i = 0; i < NUM_BLOCK_RECORDS; ++i) { BlockRecord record; QColor shaded = colors[i]; shaded.setAlpha (30); record.attribute = KTextEditor::Attribute::Ptr (new KTextEditor::Attribute ()); record.attribute->clearProperty (KTextEditor::Attribute::BackgroundFillWhitespace); record.attribute->setBackground (shaded); QPixmap colorsquare (16, 16); colorsquare.fill (colors[i]); QIcon icon (colorsquare); record.mark = ac->addAction ("markblock" + QString::number (i), this, SLOT (markBlock())); record.mark->setIcon (icon); record.mark->setData (i); actionmenu_mark_block->addAction (record.mark); record.unmark = ac->addAction ("unmarkblock" + QString::number (i), this, SLOT (unmarkBlock())); record.unmark->setIcon (icon); record.unmark->setData (i); actionmenu_unmark_block->addAction (record.unmark); record.run = ac->addAction ("runblock" + QString::number (i), this, SLOT (runBlock())); record.run->setShortcut (shortcuts[i]); record.run->setIcon (icon); record.run->setData (i); actionmenu_run_block->addAction (record.run); // these two not strictly needed due to removeBlock(), below. Silences a GCC warning, however. record.range = 0; record.active = false; block_records.append (record); removeBlock (i, true); // initialize to empty } RK_ASSERT (block_records.size () == NUM_BLOCK_RECORDS); } void RKCommandEditorWindow::focusIn (KTextEditor::View* v) { RK_TRACE (COMMANDEDITOR); RK_ASSERT (v == m_view); setPopupMenu (); } /** NOTE: this function still needed? - Still needed in KDE 4.3.4. */ void RKCommandEditorWindow::setPopupMenu () { RK_TRACE (COMMANDEDITOR); if (!getPart ()->factory ()) return; m_view->setContextMenu (static_cast (getPart ()->factory ()->container ("ktexteditor_popup", getPart ()))); } QString RKCommandEditorWindow::fullCaption () { RK_TRACE (COMMANDEDITOR); if (m_doc->url ().isEmpty ()) { return (shortCaption ()); } else { QString cap = m_doc->url ().pathOrUrl (); if (isModified ()) cap.append (i18n (" [modified]")); return (cap); } } void RKCommandEditorWindow::closeEvent (QCloseEvent *e) { if (isModified ()) { int status = KMessageBox::warningYesNo (this, i18n ("The document \"%1\" has been modified. Close it anyway?", windowTitle ()), i18n ("File not saved")); if (status != KMessageBox::Yes) { e->ignore (); return; } } QWidget::closeEvent (e); } void RKCommandEditorWindow::copy () { RK_TRACE (COMMANDEDITOR); QApplication::clipboard()->setText (m_view->selectionText ()); } void RKCommandEditorWindow::setReadOnly (bool ro) { RK_TRACE (COMMANDEDITOR); m_doc->setReadWrite (!ro); } bool RKCommandEditorWindow::openURL (const KUrl url, const QString& encoding, bool use_r_highlighting, bool read_only, bool delete_on_close){ RK_TRACE (COMMANDEDITOR); // encoding must be set *before* loading the file if (!encoding.isEmpty ()) m_doc->setEncoding (encoding); if (m_doc->openUrl (url)) { if (!delete_on_close) { // don't litter config with temporary files KTextEditor::SessionConfigInterface *iface = qobject_cast (m_doc); QString p_url = RKWorkplace::mainWorkplace ()->portableUrl (m_doc->url ()); if (iface) { KConfigGroup conf (RKWorkplace::mainWorkplace ()->workspaceConfig (), QString ("SkriptDocumentSettings %1").arg (p_url)); // HACK: Hmm. KTextEditor::Document's readSessionConfig() simply restores too much. Yes, I want to load bookmarks and stuff. // I do not want to mess with encoding, or risk loading a different url, after the doc is already loaded! if (!encoding.isEmpty () && (conf.readEntry ("Encoding", encoding) != encoding)) conf.writeEntry ("Encoding", encoding); if (conf.readEntry ("URL", url) != url) conf.writeEntry ("URL", url); /* HACK: What the...?! Somehow, at least on longer R scripts, stored Mode="Normal" in combination with R Highlighting * causes code folding to fail (KDE 4.8.4, http://sourceforge.net/p/rkward/bugs/122/). * Forcing Mode == Highlighting appears to help. */ if (use_r_highlighting) conf.writeEntry ("Mode", conf.readEntry ("Highlighting", "Normal")); iface->readSessionConfig (conf); } iface = qobject_cast (m_view); if (iface) { KConfigGroup conf (RKWorkplace::mainWorkplace ()->workspaceConfig (), QString ("SkriptViewSettings %1").arg (p_url)); iface->readSessionConfig (conf); } } if (use_r_highlighting) RKCommandHighlighter::setHighlighting (m_doc, RKCommandHighlighter::RScript); else RKCommandHighlighter::setHighlighting (m_doc, RKCommandHighlighter::Automatic); setReadOnly (read_only); updateCaption (); if (delete_on_close) { if (!read_only) { RK_ASSERT (false); return true; } RKCommandEditorWindow::delete_on_close = url; } return true; } return false; } void RKCommandEditorWindow::autoSaveHandlerModifiedChanged () { RK_TRACE (COMMANDEDITOR); if (!isModified ()) { autosave_timer->stop (); if (RKSettingsModuleCommandEditor::autosaveKeep ()) return; if (!previous_autosave_url.isValid ()) return; if (previous_autosave_url.isLocalFile ()) { QFile::remove (previous_autosave_url.toLocalFile ()); } else { RKJobSequence* dummy = new RKJobSequence (); dummy->addJob (KIO::del (previous_autosave_url)); connect (dummy, SIGNAL (finished(RKJobSequence*)), this, SLOT (autoSaveHandlerJobFinished(RKJobSequence*))); dummy->start (); } previous_autosave_url.clear (); } } void RKCommandEditorWindow::autoSaveHandlerTextChanged () { RK_TRACE (COMMANDEDITOR); if (!isModified ()) return; // may happen after load or undo if (!RKSettingsModuleCommandEditor::autosaveEnabled ()) return; if (!autosave_timer->isActive ()) { autosave_timer->start (RKSettingsModuleCommandEditor::autosaveInterval () * 60 * 1000); } } void RKCommandEditorWindow::doAutoSave () { RK_TRACE (COMMANDEDITOR); RK_ASSERT (isModified ()); KTemporaryFile save; save.setSuffix (RKSettingsModuleCommandEditor::autosaveSuffix ()); RK_ASSERT (save.open ()); QTextStream out (&save); out.setCodec ("UTF-8"); // make sure that all characters can be saved, without nagging the user out << m_doc->text (); save.close (); save.setAutoRemove (false); RKJobSequence* alljobs = new RKJobSequence (); // The KJob-Handling below seems to be a bit error-prone, at least for the file-protocol on Windows. // Thus, for the simple case of local files, we use QFile, instead. connect (alljobs, SIGNAL (finished(RKJobSequence*)), this, SLOT (autoSaveHandlerJobFinished(RKJobSequence*))); // backup the old autosave file in case something goes wrong during pushing the new one KUrl backup_autosave_url; if (previous_autosave_url.isValid ()) { backup_autosave_url = previous_autosave_url; backup_autosave_url.setFileName (backup_autosave_url.fileName () + '~'); if (previous_autosave_url.isLocalFile ()) { QFile::remove (backup_autosave_url.toLocalFile ()); QFile::copy (previous_autosave_url.toLocalFile (), backup_autosave_url.toLocalFile ()); } else { alljobs->addJob (KIO::file_move (previous_autosave_url, backup_autosave_url, -1, KIO::HideProgressInfo | KIO::Overwrite)); } } // push the newly written file if (url ().isValid ()) { KUrl autosave_url = url (); autosave_url.setFileName (autosave_url.fileName () + RKSettingsModuleCommandEditor::autosaveSuffix ()); if (autosave_url.isLocalFile ()) { QFile::remove (autosave_url.toLocalFile ()); save.copy (autosave_url.toLocalFile ()); save.remove (); } else { alljobs->addJob (KIO::file_move (KUrl::fromLocalFile (save.fileName ()), autosave_url, -1, KIO::HideProgressInfo | KIO::Overwrite)); } previous_autosave_url = autosave_url; } else { // i.e., the document is still "Untitled" previous_autosave_url = KUrl::fromLocalFile (save.fileName ()); } // remove the backup if (backup_autosave_url.isValid ()) { if (backup_autosave_url.isLocalFile ()) { QFile::remove (backup_autosave_url.toLocalFile ()); } else { alljobs->addJob (KIO::del (backup_autosave_url, KIO::HideProgressInfo)); } } alljobs->start (); // do not create any more autosaves until the text is changed, again autosave_timer->stop (); } void RKCommandEditorWindow::autoSaveHandlerJobFinished (RKJobSequence* seq) { RK_TRACE (COMMANDEDITOR); if (seq->hadError ()) { KMessageBox::detailedError (this, i18n ("An error occurred while trying to create an autosave of the script file '%1':", url ().url ()), "- " + seq->errors ().join ("\n- ")); } } KUrl RKCommandEditorWindow::url () { // RK_TRACE (COMMANDEDITOR); return (m_doc->url ()); } bool RKCommandEditorWindow::isModified() { RK_TRACE (COMMANDEDITOR); return m_doc->isModified(); } void RKCommandEditorWindow::insertText (const QString &text) { // KDE4: inline? RK_TRACE (COMMANDEDITOR); m_view->insertText (text); setFocus(); } void RKCommandEditorWindow::restoreScrollPosition () { RK_TRACE (COMMANDEDITOR); KTextEditor::Cursor c = saved_scroll_position; c.setLine (qMin (c.line (), m_doc->lines () - 1)); if (c.column () >= m_doc->lineLength (c.line ())) c.setColumn (0); m_view->setCursorPosition (c); } void RKCommandEditorWindow::saveScrollPosition () { RK_TRACE (COMMANDEDITOR); KTextEditor::Cursor c = m_view->cursorPosition (); if (!c.isValid ()) c = KTextEditor::Cursor::start (); saved_scroll_position = c; } void RKCommandEditorWindow::setText (const QString &text) { RK_TRACE (COMMANDEDITOR); bool old_rw = m_doc->isReadWrite (); if (!old_rw) m_doc->setReadWrite (true); m_doc->setText (text); KTextEditor::MarkInterface *markiface = qobject_cast (m_doc); if (markiface) markiface->clearMarks (); if (!old_rw) m_doc->setReadWrite (false); } void RKCommandEditorWindow::highlightLine (int linenum) { RK_TRACE (COMMANDEDITOR); KTextEditor::MarkInterface *markiface = qobject_cast (m_doc); if (!markiface) { RK_ASSERT (markiface); return; } bool old_rw = m_doc->isReadWrite (); if (!old_rw) m_doc->setReadWrite (true); markiface->addMark (linenum, KTextEditor::MarkInterface::Execution); m_view->setCursorPosition (KTextEditor::Cursor (linenum, 0)); if (!old_rw) m_doc->setReadWrite (false); } void RKCommandEditorWindow::updateCaption (KTextEditor::Document*) { RK_TRACE (COMMANDEDITOR); QString name = url ().fileName (); if (name.isEmpty ()) name = url ().prettyUrl (); if (name.isEmpty ()) name = i18n ("Unnamed"); if (isModified ()) name.append (i18n (" [modified]")); setCaption (name); // Well, these do not really belong, here, but need to happen on pretty much the same occasions: action_setwd_to_script->setEnabled (!url ().isEmpty ()); RKWardMainWindow::getMain ()->addScriptUrl (url ()); } void RKCommandEditorWindow::showHelp () { RK_TRACE (COMMANDEDITOR); KTextEditor::Cursor c = m_view->cursorPosition(); QString line = m_doc->line(c.line ()) + ' '; RKHelpSearchWindow::mainHelpSearch ()->getContextHelp (line, c.column()); } void RKCommandEditorWindow::tryCompletionProxy (KTextEditor::Document*) { if (RKSettingsModuleCommandEditor::completionEnabled ()) { if (cc_iface && cc_iface->isCompletionActive ()) { tryCompletion (); } else if (cc_iface) { completion_timer->start (RKSettingsModuleCommandEditor::completionTimeout ()); } } } QString RKCommandEditorWindow::currentCompletionWord () const { RK_TRACE (COMMANDEDITOR); // KDE4 TODO: This may no longer be needed, if the katepart gets fixed not to abort completions when the range // contains dots or other special characters KTextEditor::Cursor c = m_view->cursorPosition(); if (!c.isValid ()) return QString (); uint para=c.line(); uint cursor_pos=c.column(); QString current_line = m_doc->line (para); if (current_line.lastIndexOf ("#", cursor_pos) >= 0) return QString (); // do not hint while in comments return RKCommonFunctions::getCurrentSymbol (current_line, cursor_pos, false); } #if KDE_IS_VERSION(4,2,0) KTextEditor::Range RKCodeCompletionModel::completionRange (KTextEditor::View *view, const KTextEditor::Cursor &position) { if (!position.isValid ()) return KTextEditor::Range (); QString current_line = view->document ()->line (position.line ()); int start; int end; RKCommonFunctions::getCurrentSymbolOffset (current_line, position.column (), false, &start, &end); return KTextEditor::Range (position.line (), start, position.line (), end); } #endif void RKCommandEditorWindow::tryCompletion () { // TODO: merge this with RKConsole::doTabCompletion () somehow RK_TRACE (COMMANDEDITOR); if ((!cc_iface) || (!completion_model)) { RK_ASSERT (false); return; } KTextEditor::Cursor c = m_view->cursorPosition(); uint para=c.line(); uint cursor_pos=c.column(); QString current_line = m_doc->line (para); int start; int end; RKCommonFunctions::getCurrentSymbolOffset (current_line, cursor_pos, false, &start, &end); if (end > cursor_pos) return; // Only hint when at the end of a word/symbol: https://mail.kde.org/pipermail/rkward-devel/2015-April/004122.html KTextEditor::Range range = KTextEditor::Range (para, start, para, end); QString word; if (range.isValid ()) word = m_doc->text (range); if (current_line.lastIndexOf ("#", cursor_pos) >= 0) word.clear (); // do not hint while in comments if (word.length () >= RKSettingsModuleCommandEditor::completionMinChars ()) { completion_model->updateCompletionList (word); if (completion_model->isEmpty ()) { cc_iface->abortCompletion (); } else { if (!cc_iface->isCompletionActive ()) { cc_iface->startCompletion (range, completion_model); } } } else { cc_iface->abortCompletion (); } } QString RKCommandEditorWindow::provideContext (int line_rev) { RK_TRACE (COMMANDEDITOR); KTextEditor::Cursor c = m_view->cursorPosition(); int current_line_num=c.line(); int cursor_pos=c.column(); if (line_rev > current_line_num) return QString (); QString ret = m_doc->line (current_line_num - line_rev); if (line_rev == 0) ret = ret.left (cursor_pos); return ret; } void RKCommandEditorWindow::paste (const QString& text) { RK_TRACE (COMMANDEDITOR); m_view->insertText (text); } void RKCommandEditorWindow::setWDToScript () { RK_TRACE (COMMANDEDITOR); RK_ASSERT (!url ().isEmpty ()); QString dir = url ().directory (); #ifdef Q_OS_WIN // KURL::directory () returns a leading slash on windows as of KDElibs 4.3 while (dir.startsWith ('/')) dir.remove (0, 1); #endif RKConsole::pipeUserCommand ("setwd (\"" + dir + "\")"); } void RKCommandEditorWindow::runCurrent () { RK_TRACE (COMMANDEDITOR); if (m_view->selection ()) { RKConsole::pipeUserCommand (m_view->selectionText ()); } else { KTextEditor::Cursor c = m_view->cursorPosition(); QString command = m_doc->line (c.line()); if (!command.isEmpty ()) RKConsole::pipeUserCommand (command + '\n'); // advance to next line (NOTE: m_view->down () won't work on auto-wrapped lines) c.setLine(c.line() + 1); m_view->setCursorPosition (c); } } void RKCommandEditorWindow::enterAndSubmit () { RK_TRACE (COMMANDEDITOR); KTextEditor::Cursor c = m_view->cursorPosition (); int line = c.line (); m_doc->insertText (c, "\n"); QString command = m_doc->line (line); if (!command.isEmpty ()) RKConsole::pipeUserCommand (command + '\n'); } void RKCommandEditorWindow::copyLinesToOutput () { RK_TRACE (COMMANDEDITOR); RKCommandHighlighter::copyLinesToOutput (m_view, RKCommandHighlighter::RScript); } void RKCommandEditorWindow::runAll () { RK_TRACE (COMMANDEDITOR); QString command = m_doc->text (); if (command.isEmpty ()) return; RKConsole::pipeUserCommand (command); } void RKCommandEditorWindow::runBlock () { RK_TRACE (COMMANDEDITOR); QAction* action = qobject_cast(sender ()); if (!action) { RK_ASSERT (false); return; } clearUnusedBlocks (); // this block might have been removed meanwhile int index = action->data ().toInt (); RK_ASSERT ((index >= 0) && (index < block_records.size ())); if (block_records[index].active) { QString command = m_doc->text (*(block_records[index].range)); if (command.isEmpty ()) return; RKConsole::pipeUserCommand (command); } } void RKCommandEditorWindow::markBlock () { RK_TRACE (COMMANDEDITOR); QAction* action = qobject_cast(sender ()); if (!action) { RK_ASSERT (false); return; } int index = action->data ().toInt (); RK_ASSERT ((index >= 0) && (index < block_records.size ())); if (m_view->selection ()) { addBlock (index, m_view->selectionRange ()); } else { RK_ASSERT (false); } } void RKCommandEditorWindow::unmarkBlock () { RK_TRACE (COMMANDEDITOR); QAction* action = qobject_cast(sender ()); if (!action) { RK_ASSERT (false); return; } int index = action->data ().toInt (); RK_ASSERT ((index >= 0) && (index < block_records.size ())); removeBlock (index); } void RKCommandEditorWindow::clearUnusedBlocks () { RK_TRACE (COMMANDEDITOR); for (int i = 0; i < block_records.size (); ++i) { if (block_records[i].active) { // TODO: do we need to check whether the range was deleted? Does the katepart do such evil things? #if KDE_IS_VERSION(4,5,0) if (block_records[i].range->isEmpty ()) { #else if (!block_records[i].range->isValid () || block_records[i].range->isEmpty ()) { #endif removeBlock (i, true); } } } } void RKCommandEditorWindow::addBlock (int index, const KTextEditor::Range& range) { RK_TRACE (COMMANDEDITOR); if (!smart_iface) return; // may happen in KDE => 4.6 if compiled with KDE <= 4.4 RK_ASSERT ((index >= 0) && (index < block_records.size ())); clearUnusedBlocks (); removeBlock (index); #if KDE_IS_VERSION(4,5,0) KTextEditor::MovingRange* srange = smart_iface->newMovingRange (range); srange->setInsertBehaviors (KTextEditor::MovingRange::ExpandRight); #else KTextEditor::SmartRange* srange = smart_iface->newSmartRange (range); srange->setInsertBehavior (KTextEditor::SmartRange::ExpandRight); #endif QString actiontext = i18n ("%1 (Active)", index + 1); block_records[index].range = srange; srange->setAttribute (block_records[index].attribute); block_records[index].active = true; block_records[index].mark->setText (actiontext); block_records[index].unmark->setText (actiontext); block_records[index].unmark->setEnabled (true); block_records[index].run->setText (actiontext); block_records[index].run->setEnabled (true); #if !KDE_IS_VERSION(4,5,0) smart_iface->addHighlightToView (m_view, srange); #endif } void RKCommandEditorWindow::removeBlock (int index, bool was_deleted) { RK_TRACE (COMMANDEDITOR); if (!smart_iface) return; // may happen in KDE => 4.6 if compiled with KDE <= 4.4 RK_ASSERT ((index >= 0) && (index < block_records.size ())); if (!was_deleted) { #if !KDE_IS_VERSION(4,5,0) smart_iface->removeHighlightFromView (m_view, block_records[index].range); #endif delete (block_records[index].range); } QString actiontext = i18n ("%1 (Unused)", index + 1); block_records[index].range = 0; block_records[index].active = false; block_records[index].mark->setText (actiontext); block_records[index].unmark->setText (actiontext); block_records[index].unmark->setEnabled (false); block_records[index].run->setText (actiontext); block_records[index].run->setEnabled (false); } void RKCommandEditorWindow::selectionChanged (KTextEditor::View* view) { RK_TRACE (COMMANDEDITOR); RK_ASSERT (view == m_view); if (view->selection ()) { actionmenu_mark_block->setEnabled (true); } else { actionmenu_mark_block->setEnabled (false); } } //////////////////////// RKFunctionArgHinter ////////////////////////////// #include #include #include "../core/rfunctionobject.h" RKFunctionArgHinter::RKFunctionArgHinter (RKScriptContextProvider *provider, KTextEditor::View* view) { RK_TRACE (COMMANDEDITOR); RKFunctionArgHinter::provider = provider; RKFunctionArgHinter::view = view; const QObjectList children = view->children (); for (QObjectList::const_iterator it = children.constBegin(); it != children.constEnd (); ++it) { QObject *obj = *it; obj->installEventFilter (this); } arghints_popup = new QLabel (0, Qt::ToolTip); arghints_popup->setMargin (2); // HACK trying hard to trick the style into using the correct color // ... and sometimes we still get white on yellow in some styles. Sigh... // A simple heuristic tries to detect the worst cases of unreasonably low contrast, and forces black on light blue, then. QPalette p = QToolTip::palette (); QColor b = p.color (QPalette::Inactive, QPalette::ToolTipBase); QColor f = p.color (QPalette::Inactive, QPalette::ToolTipText); if ((qAbs (f.greenF () - b.greenF ()) + qAbs (f.redF () - b.redF ()) + qAbs (f.yellowF () - b.yellowF ())) < .6) { f = Qt::black; b = QColor (192, 219, 255); } p.setColor (QPalette::Inactive, QPalette::WindowText, f); p.setColor (QPalette::Inactive, QPalette::Window, b); p.setColor (QPalette::Inactive, QPalette::ToolTipText, f); p.setColor (QPalette::Inactive, QPalette::ToolTipBase, b); arghints_popup->setForegroundRole (QPalette::ToolTipText); arghints_popup->setBackgroundRole (QPalette::ToolTipBase); arghints_popup->setPalette (p); arghints_popup->setFrameStyle (QFrame::Box); arghints_popup->setLineWidth (1); arghints_popup->setWordWrap (true); arghints_popup->setWindowOpacity (arghints_popup->style ()->styleHint (QStyle::SH_ToolTipLabel_Opacity, 0, arghints_popup) / 255.0); arghints_popup->hide (); active = false; connect (&updater, SIGNAL (timeout()), this, SLOT (updateArgHintWindow())); } RKFunctionArgHinter::~RKFunctionArgHinter () { RK_TRACE (COMMANDEDITOR); delete arghints_popup; } void RKFunctionArgHinter::tryArgHint () { RK_TRACE (COMMANDEDITOR); if (!RKSettingsModuleCommandEditor::argHintingEnabled ()) return; // do this in the next event cycle to make sure any inserted characters have truly been inserted QTimer::singleShot (0, this, SLOT (tryArgHintNow())); } void RKFunctionArgHinter::tryArgHintNow () { RK_TRACE (COMMANDEDITOR); // find the active opening brace int line_rev = -1; int brace_level = 1; int potential_symbol_end = -1; QString full_context; while (potential_symbol_end < 0) { QString context_line = provider->provideContext (++line_rev); if (context_line.isNull ()) break; full_context.prepend (context_line); int pos = context_line.length (); while (--pos >= 0) { QChar c = full_context.at (pos); if (c == ')') ++brace_level; else if (c == '(') { --brace_level; if (brace_level == 0) { potential_symbol_end = pos - 1; break; } } } } // now find out where the symbol to the left of the opening brace ends // there cannot be a line-break between the opening brace, and the symbol name (or can there?), so no need to fetch further context while ((potential_symbol_end >= 0) && full_context.at (potential_symbol_end).isSpace ()) { --potential_symbol_end; } if (potential_symbol_end <= 0) { hideArgHint (); return; } // now identify the symbol and object (if any) QString effective_symbol = RKCommonFunctions::getCurrentSymbol (full_context, potential_symbol_end+1); if (effective_symbol.isEmpty ()) { hideArgHint (); return; } RObject *object = RObjectList::getObjectList ()->findObject (effective_symbol); if ((!object) || (!object->isType (RObject::Function))) { hideArgHint (); return; } // initialize and show popup arghints_popup->setText (effective_symbol + " (" + static_cast (object)->printArgs () + ')'); arghints_popup->resize (arghints_popup->sizeHint () + QSize (2, 2)); active = true; updater.start (50); updateArgHintWindow (); } void RKFunctionArgHinter::updateArgHintWindow () { RK_TRACE (COMMANDEDITOR); if (!active) return; arghints_popup->move (view->mapToGlobal (view->cursorPositionCoordinates () + QPoint (0, arghints_popup->fontMetrics ().lineSpacing () + arghints_popup->margin ()*2))); if (view->hasFocus ()) arghints_popup->show (); else arghints_popup->hide (); } void RKFunctionArgHinter::hideArgHint () { RK_TRACE (COMMANDEDITOR); arghints_popup->hide (); active = false; updater.stop (); } bool RKFunctionArgHinter::eventFilter (QObject *, QEvent *e) { if (e->type () == QEvent::KeyPress || e->type () == QEvent::ShortcutOverride) { RK_TRACE (COMMANDEDITOR); // avoid loads of empty traces, putting this here QKeyEvent *k = static_cast (e); if (k->key() == Qt::Key_Enter || k->key() == Qt::Key_Return || k->key () == Qt::Key_Up || k->key () == Qt::Key_Down || k->key () == Qt::Key_Left || k->key () == Qt::Key_Right || k->key () == Qt::Key_Home || k->key () == Qt::Key_Tab) { hideArgHint (); } else if (k->key () == Qt::Key_Backspace || k->key () == Qt::Key_Delete){ tryArgHint (); } else { QString text = k->text (); if ((text == "(") || (text == ")") || (text == ",")) { tryArgHint (); } } } return false; } //////////////////////// RKCodeCompletionModel //////////////////// RKCodeCompletionModel::RKCodeCompletionModel (RKCommandEditorWindow *parent) : KTextEditor::CodeCompletionModel (parent) { RK_TRACE (COMMANDEDITOR); setRowCount (0); command_editor = parent; } RKCodeCompletionModel::~RKCodeCompletionModel () { RK_TRACE (COMMANDEDITOR); } void RKCodeCompletionModel::updateCompletionList (const QString& symbol) { RK_TRACE (COMMANDEDITOR); if (current_symbol == symbol) return; // already up to date RObject::RObjectSearchMap map; RObjectList::getObjectList ()->findObjectsMatching (symbol, &map); int count = map.size (); icons.clear (); names.clear (); #if QT_VERSION >= 0x040700 icons.reserve (count); names.reserve (count); #endif // copy the map to two lists. For one thing, we need an int indexable storage, for another, caching this information is safer // in case objects are removed while the completion mode is active. for (RObject::RObjectSearchMap::const_iterator it = map.constBegin (); it != map.constEnd (); ++it) { icons.append (RKStandardIcons::iconForObject (it.value ())); names.append (it.value ()->getBaseName ()); } setRowCount (count); current_symbol = symbol; reset (); } void RKCodeCompletionModel::completionInvoked (KTextEditor::View*, const KTextEditor::Range&, InvocationType) { RK_TRACE (COMMANDEDITOR); // we totally ignore whichever range the katepart thinks we should offer a completion on. // it is often wrong, esp, when there are dots in the symbol // KDE4 TODO: This may no longer be needed, if the katepart gets fixed not to abort completions when the range // contains dots or other special characters updateCompletionList (command_editor->currentCompletionWord ()); } void RKCodeCompletionModel::executeCompletionItem (KTextEditor::Document *document, const KTextEditor::Range &word, int row) const { RK_TRACE (COMMANDEDITOR); RK_ASSERT (names.size () > row); document->replaceText (word, names[row]); } QVariant RKCodeCompletionModel::data (const QModelIndex& index, int role) const { int col = index.column (); int row = index.row (); if (index.parent ().isValid ()) return QVariant (); if ((role == Qt::DisplayRole) || (role==KTextEditor::CodeCompletionModel::CompletionRole)) { if (col == KTextEditor::CodeCompletionModel::Name) { return (names.value (row)); } } else if (role == Qt::DecorationRole) { if (col == KTextEditor::CodeCompletionModel::Icon) { return (icons.value (row)); } } return QVariant (); } // static KTextEditor::Document* RKCommandHighlighter::_doc = 0; KTextEditor::Document* RKCommandHighlighter::getDoc () { if (_doc) return _doc; RK_TRACE (COMMANDEDITOR); KTextEditor::Editor* editor = KTextEditor::editor("katepart"); RK_ASSERT (editor); _doc = editor->createDocument (RKWardMainWindow::getMain ()); // NOTE: In KDE 4.4.5, a (dummy) view is needed to access highlighting attributes. According to a katepart error message, this will be fixed, eventually. // TODO: check whether this is fixed in some later version of KDE QWidget* view = _doc->createView (0); view->hide (); RK_ASSERT (_doc); return _doc; } #if KDE_IS_VERSION(4,4,0) # include #include ////////// // NOTE: Most of the exporting code is copied from the katepart HTML exporter plugin more or less verbatim! (Source license: LGPL v2) ////////// QString exportText(const QString& text, const KTextEditor::Attribute::Ptr& attrib, const KTextEditor::Attribute::Ptr& m_defaultAttribute) { if ( !attrib || !attrib->hasAnyProperty() || attrib == m_defaultAttribute ) { return (Qt::escape(text)); } QString ret; if ( attrib->fontBold() ) { ret.append (""); } if ( attrib->fontItalic() ) { ret.append (""); } bool writeForeground = attrib->hasProperty(QTextCharFormat::ForegroundBrush) && (!m_defaultAttribute || attrib->foreground().color() != m_defaultAttribute->foreground().color()); bool writeBackground = attrib->hasProperty(QTextCharFormat::BackgroundBrush) && (!m_defaultAttribute || attrib->background().color() != m_defaultAttribute->background().color()); if ( writeForeground || writeBackground ) { ret.append (QString("") .arg(writeForeground ? QString(QLatin1String("color:") + attrib->foreground().color().name() + QLatin1Char(';')) : QString()) .arg(writeBackground ? QString(QLatin1String("background:") + attrib->background().color().name() + QLatin1Char(';')) : QString())); } ret.append (Qt::escape(text)); if ( writeBackground || writeForeground ) { ret.append (""); } if ( attrib->fontItalic() ) { ret.append (""); } if ( attrib->fontBold() ) { ret.append (""); } return ret; } QString RKCommandHighlighter::commandToHTML (const QString r_command, HighlightingMode mode) { KTextEditor::Document* doc = getDoc (); KTextEditor::HighlightInterface *iface = qobject_cast (_doc); RK_ASSERT (iface); if (!iface) return (QString ("
") + r_command + "
"); doc->setText (r_command); if (r_command.endsWith ('\n')) doc->removeLine (doc->lines () - 1); setHighlighting (doc, mode); QString ret; QString opening; KTextEditor::Attribute::Ptr m_defaultAttribute = iface->defaultStyle(KTextEditor::HighlightInterface::dsNormal); if ( !m_defaultAttribute ) { opening = "
";
	} else {
		opening = QString("
")
				.arg(m_defaultAttribute->fontBold() ? "font-weight:bold;" : "")
				.arg(m_defaultAttribute->fontItalic() ? "text-style:italic;" : "");
				// Note: copying the default text/background colors is pointless in our case, and leads to subtle inconsistencies.
	}

	const KTextEditor::Attribute::Ptr noAttrib(0);

	if (mode == RScript) ret = opening.arg ("code");
	enum {
		Command,
		Output,
		Warning,
		None
	} previous_chunk = None;
	for (int i = 0; i < doc->lines (); ++i)
	{
		const QString &line = doc->line(i);
		QList attribs = iface->lineAttributes(i);
		int lineStart = 0;

		if (mode == RInteractiveSession) {
			if (line.startsWith ("> ") || line.startsWith ("+ ")) {
				lineStart = 2;	// skip the prompt. Prompt will be indicated by the CSS, instead
				if (previous_chunk != Command) {
					if (previous_chunk != None) ret.append ("
"); ret.append (opening.arg ("code")); previous_chunk = Command; } } else { if (previous_chunk != Output) { if (previous_chunk != None) ret.append ("
"); ret.append (opening.arg ("output_normal")); previous_chunk = Output; } ret.append (Qt::escape (line) + '\n'); // don't copy output "highlighting". It is set using CSS, instead continue; } } int handledUntil = lineStart; int remainingChars = line.length(); foreach ( const KTextEditor::HighlightInterface::AttributeBlock& block, attribs ) { if ((block.start + block.length) <= handledUntil) continue; int start = qMax(block.start, lineStart); if ( start > handledUntil ) { ret += exportText( line.mid( handledUntil, start - handledUntil ), noAttrib, m_defaultAttribute ); } int end = qMin (block.start + block.length, remainingChars); int length = end - start; ret += exportText( line.mid( start, length ), block.attribute, m_defaultAttribute); handledUntil = end; } if ( handledUntil < lineStart + remainingChars ) { ret += exportText( line.mid( handledUntil, remainingChars ), noAttrib, m_defaultAttribute ); } if (i < (doc->lines () - 1)) ret.append ("\n"); } ret.append ("
\n"); return ret; } #else // KDE < 4.4: No Highlighting Interface QString RKCommandHighlighter::commandToHTML (const QString r_command, HighlightingMode) { return (QString ("
") + r_command + "
"); } #endif /** set syntax highlighting-mode to R syntax. Outside of class, in order to allow use from the on demand code highlighter */ void RKCommandHighlighter::setHighlighting (KTextEditor::Document *doc, HighlightingMode mode) { RK_TRACE (COMMANDEDITOR); QString mode_string; if (mode == RScript) mode_string = "R Script"; else if (mode == RInteractiveSession) mode_string = "R interactive session"; else { QString fn = doc->url ().fileName ().toLower (); if (fn.endsWith (".pluginmap") || fn.endsWith (".rkh") || fn.endsWith (".xml") || fn.endsWith (".inc")) { mode_string = "XML"; } else if (fn.endsWith (".js")) { mode_string = "JavaScript"; } else { return; } } if (!(doc->setHighlightingMode (mode_string) && doc->setMode (mode_string))) RK_DEBUG (COMMANDEDITOR, DL_ERROR, "R syntax highlighting definition ('%s')not found!", qPrintable (mode_string)); } void RKCommandHighlighter::copyLinesToOutput (KTextEditor::View *view, HighlightingMode mode) { RK_TRACE (COMMANDEDITOR); // expand selection to full lines (or current line) KTextEditor::Document *doc = view->document (); KTextEditor::Range sel = view->selectionRange (); if (!sel.isValid ()) { KTextEditor::Cursor pos = view->cursorPosition (); sel.setRange (KTextEditor::Cursor (pos.line (), 0), KTextEditor::Cursor (pos.line (), doc->lineLength (pos.line ()))); } else { sel.setRange (KTextEditor::Cursor (sel.start ().line (), 0), KTextEditor::Cursor (sel.end ().line (), doc->lineLength (sel.end ().line ()))); } // highlight and submit QString highlighted = commandToHTML (doc->text (sel), mode); if (!highlighted.isEmpty ()) { RKGlobals::rInterface ()->issueCommand (".rk.cat.output (" + RObject::rQuote (highlighted) + ")\n", RCommand::App | RCommand::Silent); } } #include "rkcommandeditorwindow.moc" rkward-0.6.4/rkward/windows/rkoutputwindow.rc0000664000175000017500000000146412633754364021046 0ustar thomasthomas &File &Edit &View rkward-0.6.4/rkward/windows/rkcallstackviewer.cpp0000664000175000017500000001330012633754364021607 0ustar thomasthomas/*************************************************************************** rkcallstackviewer - description ------------------- begin : Wed Oct 19 2011 copyright : (C) 2011 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkcallstackviewer.h" #include #include #include #include #include #include #include #include "../misc/rkdummypart.h" #include "../agents/rkdebughandler.h" #include "rkcommandeditorwindow.h" #include "../debug.h" RKCallstackViewer* RKCallstackViewer::_instance = 0; RKCallstackViewer::RKCallstackViewer (QWidget *parent, bool tool_window, const char *name) : RKMDIWindow (parent, RKMDIWindow::CallstackViewerWindow, tool_window, name) { RK_TRACE (APP); real_widget = 0; QVBoxLayout *layout = new QVBoxLayout (this); layout->setContentsMargins (0, 0, 0, 0); layout_widget = new KVBox (this); layout->addWidget (layout_widget); layout_widget->setFocusPolicy (Qt::StrongFocus); setPart (new RKDummyPart (this, layout_widget)); initializeActivationSignals (); connect (RKDebugHandler::instance (), SIGNAL (newDebugState()), this, SLOT (newDebugState())); } RKCallstackViewer::~RKCallstackViewer () { RK_TRACE (APP); } void RKCallstackViewer::showEvent (QShowEvent *e) { RK_TRACE (APP); createRealWidget (); RKMDIWindow::showEvent (e); } void RKCallstackViewer::createRealWidget () { RK_TRACE (APP); if (!real_widget) { RK_DEBUG (APP, DL_INFO, "creating callstack viewer"); real_widget = new RKCallstackViewerWidget (layout_widget); setFocusProxy (real_widget); } } void RKCallstackViewer::newDebugState () { RK_TRACE (APP); if (!real_widget) createRealWidget (); else real_widget->updateState (); if (RKDebugHandler::instance ()->state () == RKDebugHandler::InDebugPrompt) activate (); } RKCallstackViewerWidget::RKCallstackViewerWidget (QWidget *parent) : QWidget (parent) { RK_TRACE (APP); QHBoxLayout *h_layout = new QHBoxLayout (this); h_layout->setContentsMargins (0, 0, 0, 0); QVBoxLayout *v_layout = new QVBoxLayout (); h_layout->addLayout (v_layout); h_layout->setStretchFactor (v_layout, 1); QLabel *label = new QLabel (i18n ("Active calls"), this); v_layout->addWidget (label); frame_selector = new QListWidget (this); frame_selector->setSelectionMode (QAbstractItemView::SingleSelection); connect (frame_selector, SIGNAL (currentRowChanged(int)), this, SLOT (frameChanged(int))); v_layout->addWidget (frame_selector); v_layout = new QVBoxLayout (); h_layout->addLayout (v_layout); h_layout->setStretchFactor (v_layout, 2); frame_info = new QLabel (this); frame_info->setWordWrap (true); v_layout->addWidget (frame_info); frame_source = new RKCommandEditorWindow (this, true); v_layout->addWidget (frame_source); updateState (); } RKCallstackViewerWidget::~RKCallstackViewerWidget () { RK_TRACE (APP); } void RKCallstackViewerWidget::updateState () { RK_TRACE (APP); if (RKDebugHandler::instance ()->state () == RKDebugHandler::NotInDebugger) { QString info = i18n ("Not in a debugger context"); frame_source->setText (info); frame_selector->clear (); frame_info->setText ("" + info + ""); } else if (RKDebugHandler::instance ()->state () == RKDebugHandler::InDebugPrompt) { frame_selector->clear (); frame_selector->setEnabled (true); frame_selector->insertItems (0, RKDebugHandler::instance ()->calls ()); frame_selector->setCurrentRow (frame_selector->count () - 1); } else { frame_selector->setEnabled (false); } } void RKCallstackViewerWidget::frameChanged (int frame_number) { RK_TRACE (APP); if (RKDebugHandler::instance ()->state () == RKDebugHandler::NotInDebugger) return; frame_info->setText (i18n ("Current call: %1
Environment: %2
Local objects: %3", Qt::escape (RKDebugHandler::instance ()->calls ().value (frame_number)), Qt::escape (RKDebugHandler::instance ()->environments ().value (frame_number)), Qt::escape (RKDebugHandler::instance ()->locals ().value (frame_number).split ('\n').join (", ")))); frame_source->setText (RKDebugHandler::instance ()->functions ().value (frame_number) + '\n'); int line = RKDebugHandler::instance ()->relativeSourceLines ().value (frame_number, 0); if (line > 0) frame_source->highlightLine (line - 1); else if (frame_number < RKDebugHandler::instance ()->calls ().size () - 1) { // no (valid) source reference available? Make an effort to locate (candidate line(s) for) the call QStringList lines = RKDebugHandler::instance ()->functions ().value (frame_number).split ('\n'); QString call = RKDebugHandler::instance ()->calls ().value (frame_number + 1); call = call.left (call.indexOf ('(')).trimmed (); QRegExp call_exp (QRegExp::escape (call) + "\\s*\\("); for (int i = lines.size () - 1; i >= 0; --i) { if (lines.at (i).contains (call_exp)) frame_source->highlightLine (i); } } } #include "rkcallstackviewer.moc" rkward-0.6.4/rkward/windows/rcontrolwindow.cpp0000664000175000017500000001246612633754364021175 0ustar thomasthomas/*************************************************************************** rcontrolwindow - description ------------------- begin : Wed Oct 12 2005 copyright : (C) 2005, 2007, 2009 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rcontrolwindow.h" #include #include #include #include #include #include #include "../settings/rksettings.h" #include "../misc/rkdummypart.h" #include "../rbackend/rinterface.h" #include "../rbackend/rcommand.h" #include "../rbackend/rcommandstack.h" #include "../rkglobals.h" #include "../rkward.h" #include "../debug.h" //static RControlWindow *RControlWindow::control_window = 0; RControlWindow::RControlWindow (QWidget *parent, bool tool_window, const char *name) : RKMDIWindow (parent, PendingJobsWindow, tool_window, name) { RK_TRACE (APP); setPart (new RKDummyPart (0, this)); initializeActivationSignals (); setFocusPolicy (Qt::ClickFocus); QVBoxLayout *main_vbox = new QVBoxLayout (this); QHBoxLayout *button_hbox = new QHBoxLayout (); button_hbox->setContentsMargins (0, 0, 0, 0); main_vbox->addLayout (button_hbox); QPushButton *configure_r_button = new QPushButton (i18n ("Configure R backend"), this); connect (configure_r_button, SIGNAL (clicked()), this, SLOT (configureButtonClicked())); button_hbox->addWidget (configure_r_button); button_hbox->addStretch (); pause_button = new QPushButton (i18n ("Pause execution"), this); connect (pause_button, SIGNAL (clicked()), this, SLOT (pauseButtonClicked())); button_hbox->addWidget (pause_button); button_hbox->addStretch (); cancel_button = new QPushButton (i18n ("Cancel selected commands"), this); connect (cancel_button, SIGNAL (clicked()), this, SLOT (cancelButtonClicked())); button_hbox->addWidget (cancel_button); commands_view = new QTreeView (this); commands_view->setSortingEnabled (false); commands_view->header ()->setMovable (false); commands_view->header ()->setStretchLastSection (false); commands_view->setSelectionMode (QAbstractItemView::ExtendedSelection); main_vbox->addWidget (commands_view); paused = false; } RControlWindow::~RControlWindow () { RK_TRACE (APP); if (commands_view->model ()) { commands_view->setModel (0); RCommandStackModel::getModel ()->removeListener (); } } void RControlWindow::showEvent (QShowEvent *e) { RK_TRACE (APP); if (!commands_view->model ()) { RCommandStackModel::getModel ()->addListener (); commands_view->setModel (RCommandStackModel::getModel ()); commands_view->header ()->setResizeMode (0, QHeaderView::Stretch); // can't do this in the ctor, as column 0 does not yet exist commands_view->expandAll (); } RKMDIWindow::showEvent (e); } void RControlWindow::hideEvent (QHideEvent *e) { RK_TRACE (APP); if (commands_view->model ()) { commands_view->setModel (0); RCommandStackModel::getModel ()->removeListener (); } RKMDIWindow::hideEvent (e); } void RControlWindow::cancelButtonClicked () { RK_TRACE (APP); RCommandStackModel::getModel ()->index (0, 0, QModelIndex ()); // side-effect of locking the mutex QModelIndexList list = commands_view->selectionModel ()->selectedIndexes (); bool some_not_cancelable = false; // find out all the RCommands selected (not the chains) for (QModelIndexList::const_iterator it = list.constBegin (); it != list.constEnd (); ++it) { if ((*it).column ()) continue; // only react once per row RCommandChain* coc = static_cast ((*it).internalPointer ()); RK_ASSERT (coc); RCommand* command = coc->toCommand (); if (command) { if (!(command->type () & RCommand::Sync)) { RKGlobals::rInterface ()->cancelCommand (command); } else { some_not_cancelable = true; } } } if (some_not_cancelable) { KMessageBox::information (this, i18n ("Some of the commands you were trying to cancel are marked as \"sync\" (letter 'S' in the type column). Cancelling such commands could lead to loss of data. These commands have _not_ been cancelled."), i18n ("Some commands not cancelled"), "cancel_sync"); } } void RControlWindow::pauseButtonClicked () { RK_TRACE (APP); if (paused) { RKGlobals::rInterface ()->pauseProcessing (false); pause_button->setText (i18n ("Pause execution")); paused = false; } else { RKGlobals::rInterface ()->pauseProcessing (true); pause_button->setText (i18n ("Resume execution")); paused = true; } } void RControlWindow::configureButtonClicked () { RK_TRACE (APP); RKSettings::configureSettings (RKSettings::PageR, this); } #include "rcontrolwindow.moc" rkward-0.6.4/rkward/windows/rkhtmlwindow.h0000664000175000017500000001676512633754364020307 0ustar thomasthomas/*************************************************************************** rkhtmlwindow - description ------------------- begin : Wed Oct 12 2005 copyright : (C) 2005, 2006, 2007, 2009, 2011, 2014, 2015 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKHTMLWINDOW_H #define RKHTMLWINDOW_H #include #include #include #include #include "../windows/rkmdiwindow.h" class KActionCollection; class KRecentFilesAction; class QAction; class QDomElement; class RKComponentHandle; class XMLHelper; class RKHTMLWindowPart; class KWebView; class KTemporaryFile; class RKHTMLWindow; class RKFindBar; class RKWebPage : public KWebPage { Q_OBJECT public: explicit RKWebPage (RKHTMLWindow* window); void load (const QUrl& url); signals: void pageInternalNavigation (const QUrl& url); protected: /** reimplemented to always emit linkClicked() for pages that need special handling (importantly, rkward://-urls). */ bool acceptNavigationRequest (QWebFrame* frame, const QNetworkRequest& request, NavigationType type); /** reimplemented to schedule new window creation for the next page to load */ QWebPage* createWindow (WebWindowType type); private: RKHTMLWindow *window; bool new_window; bool direct_load; }; /** \brief Show html files. Provide a window for viewing HTML pages. It is used as a base for several purposes: Display R-help (in HTML format), display RKWard help pages, display generic HTML, display RKWard output. @author Pierre Ecochard */ class RKHTMLWindow : public RKMDIWindow { Q_OBJECT public: enum WindowMode { Undefined, HTMLHelpWindow, HTMLOutputWindow }; /** constructor. @param parent parent QWidget, usually RKGlobals::rkApp () or similar */ explicit RKHTMLWindow (QWidget *parent, WindowMode mode=HTMLHelpWindow); /** destructor */ ~RKHTMLWindow (); /** open given URL. Returns false, if the URL is not an existing local file. Loading a non-local URL may succeed, even if this returns false! */ bool openURL (const KUrl &url); /** takes care of special handling, if the url is an rkward://-url. Does nothing and returns false, otherwise. * If window is not 0, and the url is a help window, open it, there (otherwise in a new window). * TODO: move to RKWorkplace? As this can really open a bunch of different things, although generally _from_ an html window. */ static bool handleRKWardURL (const KUrl &url, RKHTMLWindow *window=0); void openRKHPage (const KUrl &url); bool isModified (); /** Return current url */ KUrl url (); /** Return current url in a restorable way, i.e. for help pages, abstract the session specific part of the path */ KUrl restorableUrl (); WindowMode mode () { return window_mode; }; public slots: void slotPrint (); void slotSave (); void saveRequested (const QNetworkRequest& request); void slotForward (); void slotBack (); void selectionChanged (); void runSelection (); /** flush current output. */ void flushOutput (); /** Reload current page.*/ void refresh (); void zoomIn (); void zoomOut (); void setTextEncoding (QTextCodec* encoding); private slots: void scrollToBottom (); void mimeTypeDetermined (KIO::Job*, const QString& type); void internalNavigation (const QUrl& new_url); void makeContextMenu (const QPoint& pos); void findRequest (const QString& text, bool backwards, const RKFindBar *findbar, bool* found); private: friend class RKHTMLWindowPart; KWebView* view; RKWebPage* page; RKFindBar* findbar; bool have_highlight; /** In case the part is a khtmlpart: A ready-cast pointer to that. 0 otherwise (if a webkit part is in use) */ RKHTMLWindowPart *part; /** update caption according to given URL */ virtual void updateCaption (const KUrl &url); /** called from openURL. Takes care of updating caption, and updating back/forward actions, if available */ void changeURL (const KUrl &url); struct VisitedLocation { KUrl url; QPoint scroll_position; }; QList url_history; void openLocationFromHistory (VisitedLocation &loc); int current_history_position; bool url_change_is_from_history; // dirty!!! KUrl current_url; void startNewCacheFile (); KTemporaryFile *current_cache_file; WindowMode window_mode; void useMode (WindowMode); void fileDoesNotExistMessage (); void saveBrowserState (VisitedLocation *state); void restoreBrowserState (VisitedLocation *state); }; class RKHTMLWindowPart : public KParts::Part { Q_OBJECT public: explicit RKHTMLWindowPart (RKHTMLWindow *window); ~RKHTMLWindowPart () {}; void setOutputWindowSkin (); void setHelpWindowSkin (); void initActions (); private: friend class RKHTMLWindow; RKHTMLWindow *window; // general actions QAction *run_selection; QAction* print; // actions in output window mode QAction* outputFlush; QAction* outputRefresh; // actions in help window mode QAction *back; QAction *forward; QAction* save_page; }; /** \brief Renders RKWard help pages. @author Thomas Friedrichsmeier */ class RKHelpRenderer { public: /** ctor */ explicit RKHelpRenderer (QIODevice *_device) { device = _device; help_xml = 0; component_xml = 0; }; /** destructor */ ~RKHelpRenderer () {}; XMLHelper *help_xml; XMLHelper *component_xml; QDomElement help_doc_element; QDomElement component_doc_element; // for dealing with rkward://[page|component]-pages bool renderRKHelp (const KUrl &url); QString renderHelpFragment (QDomElement &fragment); QString resolveLabel (const QString &id) const; QString prepareHelpLink (const QString &href, const QString &text); QString componentPathToId (QString path); RKComponentHandle *componentPathToHandle (QString path); QString startSection (const QString &name, const QString &title, const QString &shorttitle, QStringList *anchors, QStringList *anchor_names); QIODevice *device; void writeHTML (const QString &string); }; #include #include /** Takes care of showing / refreshing output windows as needed. */ class RKOutputWindowManager : public QObject { Q_OBJECT public: static RKOutputWindowManager *self (); void registerWindow (RKHTMLWindow *window); /** R may produce output while no output window is active. This allows to set the file that should be monitored for such changes (called from within rk.set.html.output.file()). */ void setCurrentOutputPath (const QString &path); /** return a pointer to the current output. If there is no output window, one will be created (and shown) automatically */ RKHTMLWindow* getCurrentOutputWindow (); private: RKOutputWindowManager (); ~RKOutputWindowManager (); static RKOutputWindowManager *_self; QString current_default_path; KDirWatch *file_watcher; QMultiHash windows; private slots: void fileChanged (const QString &path); void windowDestroyed (QObject *window); }; #endif rkward-0.6.4/rkward/windows/CMakeLists.txt0000644000175000017500000000165312455741221020117 0ustar thomasthomasINCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${KDE4_INCLUDE_DIR} ${QT_INCLUDES}) ########### next target ############### SET(windows_STAT_SRCS rkcommandeditorwindow.cpp rkdebugconsole.cpp rkcallstackviewer.cpp rkhtmlwindow.cpp rcontrolwindow.cpp detachedwindowcontainer.cpp rkmdiwindow.cpp rkworkplaceview.cpp rkworkplace.cpp rkwindowcatcher.cpp rkcommandlog.cpp rkhelpsearchwindow.cpp rktoplevelwindowgui.cpp rkfilebrowser.cpp rktoolwindowbar.cpp rktoolwindowlist.cpp robjectbrowser.cpp rkdebugmessagewindow.cpp ) QT4_AUTOMOC(${windows_STAT_SRCS}) ADD_LIBRARY(windows STATIC ${windows_STAT_SRCS}) ########### install files ############### INSTALL(FILES rkcommandeditorwindowpart.rc rkoutputwindow.rc rkhelpwindow.rc detachedwindowcontainer.rc rkcatchedx11windowpart.rc rkcommandlogpart.rc rktoplevelwindowgui.rc rkstandardactions.rc DESTINATION ${DATA_INSTALL_DIR}/rkward) rkward-0.6.4/rkward/windows/rkcallstackviewer.h0000664000175000017500000000452012633754364021260 0ustar thomasthomas/*************************************************************************** rkcallstackviewer - description ------------------- begin : Wed Oct 19 2011 copyright : (C) 2011 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKCALLSTACKVIEWER_H #define RKCALLSTACKVIEWER_H #include "rkmdiwindow.h" class RKCallstackViewerWidget; class RKCommandEditorWindow; class QListWidget; class QLabel; /** The call stack (tool) window. In order to save some startup time, the widget is not really created until it is first shown. Hence, this is mostly just a wrapper around RKCallstackViewerWidget */ class RKCallstackViewer : public RKMDIWindow { Q_OBJECT public: RKCallstackViewer (QWidget *parent, bool tool_window, const char *name=0); ~RKCallstackViewer (); /** reimplemented to create the real widget only when the viewer is shown for the first time */ void showEvent (QShowEvent *e); static RKCallstackViewer *instance () { return _instance; }; public slots: void newDebugState (); private: void createRealWidget (); RKCallstackViewerWidget *real_widget; QWidget *layout_widget; friend class RKWardMainWindow; static RKCallstackViewer *_instance; }; /** The internal widget used in RKCallstackViewer */ class RKCallstackViewerWidget : public QWidget { Q_OBJECT public: explicit RKCallstackViewerWidget (QWidget *parent); ~RKCallstackViewerWidget (); void updateState (); private slots: void frameChanged (int frame_number); private: QListWidget *frame_selector; QLabel *frame_info; RKCommandEditorWindow *frame_source; }; #endif rkward-0.6.4/rkward/windows/rkstandardactions.rc0000664000175000017500000000440012633754364021430 0ustar thomasthomas rkward-0.6.4/rkward/windows/rktoolwindowbar.h0000664000175000017500000000544412633754364020775 0ustar thomasthomas/*************************************************************************** rktoolwindowbar - description ------------------- begin : Fri Oct 12 2007 copyright : (C) 2007, 2011 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ /* This code is based substantially on kate's katemdi! */ #ifndef RKTOOLWINDOWBAR_H #define RKTOOLWINDOWBAR_H #include #include #include class QSplitter; class QObject; class QEvent; class QAction; class KHBox; class RKMDIWindow; /** This class represents one of the bar which tool windows can dock into (top, left, bottom, right). It contains heavy copying from Kate's katemdi SideBar class. I wish this was available as a library, but it isn't, yet. Some more would need to be copied for full functionality (session saving / restoring), but for now, I focused on the bare essentials */ class RKToolWindowBar : public KMultiTabBar { Q_OBJECT public: RKToolWindowBar (KMultiTabBar::KMultiTabBarPosition position, QWidget *parent); ~RKToolWindowBar (); void setSplitter (QSplitter *splitter); void addWidget (RKMDIWindow *widget); void removeWidget (RKMDIWindow *widget); void showWidget (RKMDIWindow *widget); void hideWidget (RKMDIWindow *widget); void restoreSize (const KConfigGroup &cg); void saveSize (KConfigGroup &cg) const; private slots: void tabClicked (int id); void changeAttachment (); void moveToolWindow (int target); void addRemoveToolWindow (QAction* action); void splitterMoved (int, int); protected: /** handle RMB clicks on individual buttons */ bool eventFilter (QObject *obj, QEvent *ev); /** handle RMB clicks on the bar itself */ void contextMenuEvent (QContextMenuEvent *event); private: friend class RKWorkplace; void reclaimDetached (RKMDIWindow *window); int getSplitterSize () const; void setSplitterSize (int new_size); QMap widget_to_id; RKMDIWindow* idToWidget (int id) const; QSplitter* splitter; KHBox* container; int last_known_size; int id_of_popup; }; #endif rkward-0.6.4/rkward/windows/rkdebugconsole.h0000664000175000017500000000402512633754364020546 0ustar thomasthomas/*************************************************************************** rkdebugconsole - description ------------------- begin : Wed Oct 19 2011 copyright : (C) 2011 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RKDEBUGCONSOLE_H #define RKDEBUGCONSOLE_H #include "rkmdiwindow.h" class QPushButton; class KHistoryComboBox; class QTextEdit; class QLabel; /** A very simple debugger console */ class RKDebugConsole : public RKMDIWindow { Q_OBJECT public: RKDebugConsole (QWidget *parent, bool tool_window, const char *name=0); ~RKDebugConsole (); static RKDebugConsole *instance () { return _instance; }; // reimplemented to refuse closing while inside the debugger bool close (bool auto_delete); public slots: void newDebugState (); private slots: void sendReply (); void stepButtonClicked (); void stepOutButtonClicked (); void continueButtonClicked (); void cancelButtonClicked (); private: void sendReply (const QString &reply); QTextEdit* context_view; KHistoryComboBox* reply_edit; QLabel* prompt_label; QPushButton* step_button; QPushButton* step_out_button; QPushButton* continue_button; QPushButton* cancel_button; friend class RKWardMainWindow; static RKDebugConsole *_instance; }; #endif rkward-0.6.4/rkward/windows/detachedwindowcontainer.cpp0000664000175000017500000001342012633754364022766 0ustar thomasthomas/*************************************************************************** detachedwindowcontainer - description ------------------- begin : Wed Oct 21 2005 copyright : (C) 2005, 2007, 2009, 2010 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "detachedwindowcontainer.h" #include #include #include #include #include #include #include #include #include #include "rktoplevelwindowgui.h" #include "../rkward.h" #include "../misc/rkstandardicons.h" #include "../misc/rkxmlguisyncer.h" #include "rkworkplace.h" #include "../rkglobals.h" #include "../debug.h" /* Warning! Do not pass a parent widget to the KParts::MainWindow. Otherwise there will be strange crahes while removing the KXMLGUIClients! (In this case: Open a help window, and detach it. Open another help window attached. Close the detached one, then close the attached one -> crash; KDE 3.5.5) */ DetachedWindowContainer::DetachedWindowContainer (RKMDIWindow *widget_to_capture) : KParts::MainWindow () { RK_TRACE (APP); actionCollection ()->addAction (KStandardAction::Close, "dwindow_close", this, SLOT(close())); QAction *reattach = actionCollection ()->addAction ("dwindow_attach", this, SLOT(slotReattach())); reattach->setText (i18n ("Attach to main window")); reattach->setIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionAttachWindow)); setHelpMenuEnabled (false); // create own GUI setXMLFile ("detachedwindowcontainer.rc"); insertChildClient (toplevel_actions = new RKTopLevelWindowGUI (this)); statusBar ()->hide (); createShellGUI (true); RKXMLGUISyncer::self ()->watchXMLGUIClientUIrc (this); // copy main window toolbar settings QMap main_window_toolbar_styles; foreach (KToolBar *bar, RKWardMainWindow::getMain ()->toolBars ()) { main_window_toolbar_styles.insert (bar->objectName (), bar->toolButtonStyle ()); } // capture widget // NOTE: If the window has not been shown, before, its geometry() is always 640*480, unconditionally. setGeometry (widget_to_capture->frameGeometry ()); if (!widget_to_capture->isWindow ()) move (widget_to_capture->mapToGlobal (widget_to_capture->pos ())); #ifdef Q_WS_WIN // fix for detached tool windows positioned with the frame outside the screen ensurePolished (); QPoint adjust = pos (); if (adjust.x () < 0) adjust.setX (0); if (adjust.y () < 0) adjust.setY (0); if (adjust != pos ()) move (adjust); #endif widget_to_capture->setParent (this); setCentralWidget (widget_to_capture); widget_to_capture->show (); createGUI (widget_to_capture->getPart ()); captured = widget_to_capture; hideEmptyMenus (); // hide empty menus now, and after any reloads // the signal is available since KDE 4.1.3, but we can tolerate a bit of aesthethic malfunction on earlier versions connect (guiFactory (), SIGNAL(makingChanges(bool)), this, SLOT(hideEmptyMenus(bool))); // sanitize toolbars foreach (KToolBar *bar, toolBars ()) { if (main_window_toolbar_styles.contains (bar->objectName ())) { bar->setToolButtonStyle (main_window_toolbar_styles[bar->objectName ()]); } else { RK_ASSERT (false); } } // should self-destruct, when child widget is destroyed connect (widget_to_capture, SIGNAL (destroyed(QObject*)), this, SLOT (viewDestroyed(QObject*))); connect (widget_to_capture, SIGNAL (captionChanged(RKMDIWindow*)), this, SLOT (updateCaption(RKMDIWindow*))); setCaption (widget_to_capture->fullCaption ()); // has to come after createGUI! } DetachedWindowContainer::~DetachedWindowContainer () { RK_TRACE (APP); } void DetachedWindowContainer::hideEmptyMenus (bool ignore) { if (ignore) return; RK_TRACE (APP); // remove empty menus (we had to define them in detachedwindowcontainer.rc in order to force a sane menu order) QStringList menu_names; menu_names << "file" << "device" << "history" << "edit" << "run" << "view" << "settings"; foreach (const QString& name, menu_names) { QMenu* menu = dynamic_cast(guiFactory ()->container (name, this)); if (menu) menu->menuAction ()->setVisible (!menu->isEmpty ()); } } void DetachedWindowContainer::viewDestroyed (QObject *) { RK_TRACE (APP); hide (); deleteLater (); } void DetachedWindowContainer::updateCaption (RKMDIWindow *widget) { RK_TRACE (APP); RK_ASSERT (widget == captured); setCaption (widget->fullCaption ()); } void DetachedWindowContainer::slotSetStatusBarText (const QString &text) { RK_TRACE (APP); QString ntext = text.trimmed (); ntext.replace ("", QString ()); // WORKAROUND: what the ?!? is going on? The KTHMLPart seems to post such messages. statusBar ()->showMessage (ntext); statusBar ()->show (); } void DetachedWindowContainer::slotReattach () { RK_TRACE (APP); RKWorkplace::mainWorkplace ()->attachWindow (captured); } void DetachedWindowContainer::closeEvent (QCloseEvent *e) { RK_TRACE (APP); if (captured->close (true)) { e->accept (); } else { e->ignore (); } } #include "detachedwindowcontainer.moc" rkward-0.6.4/rkward/windows/rkcommandlog.cpp0000664000175000017500000002261312633754364020553 0ustar thomasthomas/*************************************************************************** rkcommandlog - description ------------------- begin : Sun Nov 3 2002 copyright : (C) 2002, 2004, 2005 2006, 2007, 2009, 2010 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkcommandlog.h" #include "../rbackend/rinterface.h" #include "../rkglobals.h" #include "../rkconsole.h" #include "../settings/rksettingsmodulewatch.h" #include "../misc/rkstandardactions.h" #include "rkcommandeditorwindow.h" #include #include #include #include #include #include #include #include #include #include "../debug.h" //static RKCommandLog *RKCommandLog::rkcommand_log = 0; RKCommandLog::RKCommandLog (QWidget *parent, bool tool_window, const char *name) : RKMDIWindow (parent, CommandLogWindow, tool_window, name) { RK_TRACE (APP); log_view = new RKCommandLogView (this); log_view->setLineWrapMode (QTextEdit::NoWrap); log_view->setUndoRedoEnabled (false); log_view->setReadOnly (true); QHBoxLayout *layout = new QHBoxLayout (this); layout->setContentsMargins (0, 0, 0, 0); layout->addWidget (log_view); setCaption (i18n ("Command log")); clearLog (); last_raised_command = 0; RKCommandLogPart *part = new RKCommandLogPart (this); setPart (part); part->initActions (); initializeActivationSignals (); setFocusPolicy (Qt::ClickFocus); connect (RKSettings::tracker (), SIGNAL (settingsChanged(RKSettings::SettingsPage)), this, SLOT (settingsChanged(RKSettings::SettingsPage))); settingsChanged (RKSettings::PageWatch); } RKCommandLog::~RKCommandLog(){ RK_TRACE (APP); RK_ASSERT (command_input_shown.isEmpty ()); } void RKCommandLog::addInput (RCommand *command) { RK_TRACE (APP); if (!RKSettingsModuleWatch::shouldShowInput (command)) return; // commands submitted via the console are often incomplete. We delay showing the input, until the command is finished. if (command->type () & RCommand::Console) return; addInputNoCheck (command); } void RKCommandLog::addInputNoCheck (RCommand *command) { RK_TRACE (APP); if (command_input_shown.contains (command)) return; // already shown // TODO: make colors/styles configurable if (command->type () & RCommand::User) { log_view->setTextColor (Qt::red); } else if (command->type () & RCommand::Sync) { log_view->setTextColor (Qt::gray); } else if (command->type () & RCommand::Plugin) { log_view->setTextColor (Qt::blue); } log_view->setFontItalic (true); log_view->insertPlainText (command->command () + '\n'); checkRaiseWindow (command); linesAdded (); log_view->setFontItalic (false); command_input_shown.append (command); } void RKCommandLog::addOutputNoCheck (RCommand *command, ROutput *output) { RK_TRACE (APP); if (command->type () & RCommand::User) { log_view->setTextColor (Qt::red); } else if (command->type () & RCommand::Sync) { log_view->setTextColor (Qt::gray); } else if (command->type () & RCommand::Plugin) { log_view->setTextColor (Qt::blue); } log_view->setFontWeight (QFont::Bold); if (output->type != ROutput::Output) { QTextBlockFormat f; f.setBackground (QBrush (QColor (255, 200, 200))); log_view->textCursor ().mergeBlockFormat (f); } log_view->insertPlainText (output->output); if (output->type != ROutput::Output) { QTextBlockFormat f; f.setBackground (QBrush (QColor (255, 255, 255))); log_view->textCursor ().mergeBlockFormat (f); } log_view->setFontWeight (QFont::Normal); log_view->setTextColor (Qt::black); checkRaiseWindow (command); linesAdded (); } void RKCommandLog::checkRaiseWindow (RCommand *command) { // called during output. do not trace if (command->id () == last_raised_command) return; if (!RKSettingsModuleWatch::shouldRaiseWindow (command)) return; if (command->type () & RCommand::Console) return; last_raised_command = command->id (); activate (false); } void RKCommandLog::newOutput (RCommand *command, ROutput *output_fragment) { RK_TRACE (APP); if (!RKSettingsModuleWatch::shouldShowOutput (command)) return; if (RKSettingsModuleWatch::shouldShowInput (command)) addInputNoCheck (command); addOutputNoCheck (command, output_fragment); } void RKCommandLog::rCommandDone (RCommand *command) { RK_TRACE (APP); if (command->type () & RCommand::Console) { if (command->errorIncomplete ()) return; addInputNoCheck (command); } // the case we have to deal with here, is that the command/output has not been shown, yet, but should, due to errors if (command->hasWarnings() || command->failed()) { if (RKSettingsModuleWatch::shouldShowError (command)) { if (!RKSettingsModuleWatch::shouldShowInput (command)) addInputNoCheck (command); if (!RKSettingsModuleWatch::shouldShowOutput (command)) { ROutputList out_list = command->getOutput (); for (ROutputList::const_iterator it = out_list.constBegin (); it != out_list.constEnd (); ++it) { addOutputNoCheck (command, *it); } } if (command->failed () && command->error ().isEmpty ()) { ROutput dummy_output; dummy_output.type = ROutput::Error; if (command->errorIncomplete ()) { dummy_output.output = i18n ("Incomplete statement.\n"); } else if (command->errorSyntax ()) { dummy_output.output = i18n ("Syntax error.\n"); } else { dummy_output.output = i18n ("An unspecified error occurred while running the command.\n"); } addOutputNoCheck (command, &dummy_output); } } } if (RKSettingsModuleWatch::shouldShowOutput (command)) log_view->insertPlainText ("\n"); command_input_shown.removeAll (command); } void RKCommandLog::settingsChanged (RKSettings::SettingsPage page) { if (page != RKSettings::PageWatch) return; RK_TRACE (APP); log_view->document ()->setMaximumBlockCount (RKSettingsModuleWatch::maxLogLines ()); } void RKCommandLog::linesAdded () { RK_TRACE (APP); // scroll to bottom log_view->moveCursor (QTextCursor::End, QTextCursor::MoveAnchor); log_view->ensureCursorVisible (); } void RKCommandLog::configureLog () { RK_TRACE (APP); RKSettings::configureSettings (RKSettings::PageWatch, this); } void RKCommandLog::clearLog () { RK_TRACE (APP); log_view->setPlainText (QString ()); // set a fixed width font QFont font = KGlobalSettings::fixedFont (); log_view->setCurrentFont (font); } void RKCommandLog::runSelection () { RK_TRACE (APP); RKConsole::pipeUserCommand (getView ()->textCursor ().selectedText ()); } ////////////////////////// END RKCommandLog /////////////////////////// /////////////////////// BEGIN RKCommandLogView //////////////////////// RKCommandLogView::RKCommandLogView (RKCommandLog *parent) : QTextEdit (parent) { RK_TRACE (APP); } RKCommandLogView::~RKCommandLogView () { RK_TRACE (APP); } void RKCommandLogView::contextMenuEvent (QContextMenuEvent *event) { RK_TRACE (APP); emit (popupMenuRequest (event->globalPos ())); event->accept (); } void RKCommandLogView::selectAll () { RK_TRACE (APP); moveCursor (QTextCursor::Start, QTextCursor::MoveAnchor); moveCursor (QTextCursor::Start, QTextCursor::KeepAnchor); } //////////////////////// END RKCommandLogView ///////////////////////// /////////////////////// BEGIN RKCommandLogPart //////////////////////// #include #include RKCommandLogPart::RKCommandLogPart (RKCommandLog *for_log) : KParts::Part (0) { RK_TRACE (APP); setComponentData (KGlobal::mainComponent ()); setWidget (log = for_log); setXMLFile ("rkcommandlogpart.rc"); } RKCommandLogPart::~RKCommandLogPart () { RK_TRACE (APP); } void RKCommandLogPart::initActions () { RK_TRACE (APP); copy = actionCollection ()->addAction (KStandardAction::Copy, "log_copy", log->getView (), SLOT (copy())); actionCollection ()->addAction (KStandardAction::Clear, "log_clear", log, SLOT (clearLog())); actionCollection ()->addAction (KStandardAction::SelectAll, "log_select_all", log->getView (), SLOT (selectAll())); QAction *configure = actionCollection ()->addAction ("log_configure", log, SLOT(configureLog())); configure->setText (i18n ("Configure")); run_selection = RKStandardActions::runCurrent (log, log, SLOT(runSelection())); connect (log->getView (), SIGNAL (popupMenuRequest(QPoint)), this, SLOT (doPopupMenu(QPoint))); } void RKCommandLogPart::doPopupMenu (const QPoint &pos) { QMenu *menu = static_cast (factory ()->container ("rkcommandlog_context_menu", this)); copy->setEnabled (log->getView ()->textCursor ().hasSelection ()); run_selection->setEnabled (log->getView ()->textCursor ().hasSelection ()); if (!menu) { RK_ASSERT (false); return; } menu->exec (pos); copy->setEnabled (true); run_selection->setEnabled (true); } #include "rkcommandlog.moc" rkward-0.6.4/rkward/windows/detachedwindowcontainer.h0000664000175000017500000000470312633754364022437 0ustar thomasthomas/*************************************************************************** detachedwindowcontainer - description ------------------- begin : Wed Oct 21 2005 copyright : (C) 2005, 2009, 2010 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef DETACHEDWINDOWCONTAINER_H #define DETACHEDWINDOWCONTAINER_H #include #include class RKMDIWindow; class RKTopLevelWindowGUI; class QCloseEvent; /** This class is used to host a (KPart enabled) window detached from the main window. @see RKWorkplace::detachWindow (). @author Thomas Friedrichsmeier */ class DetachedWindowContainer : public KParts::MainWindow { Q_OBJECT public: /** constructor. @param widget_to_capture The window to reparent into the detached window */ explicit DetachedWindowContainer (RKMDIWindow *widget_to_capture); /** destructor. Usually you don't call this explicitly, but rather delete/close the child view. The DetachedWindowContainer will then self destruct via viewDestroyed () */ ~DetachedWindowContainer (); public slots: /** self-destruct, when child view is destroyed */ void viewDestroyed (QObject *view); /** re-attach to the main window */ void slotReattach (); /** update own caption, when the window's caption has changed */ void updateCaption (RKMDIWindow *); void slotSetStatusBarText (const QString &text); /** Hide any emtpy menus. @param ignore do nothing if true. For internal use, only. */ void hideEmptyMenus (bool ignore=false); protected: /** when receiving a close event, dispatch to the embedded window */ void closeEvent (QCloseEvent *e); private: RKMDIWindow *captured; RKTopLevelWindowGUI *toplevel_actions; }; #endif rkward-0.6.4/rkward/windows/rkhelpsearchwindow.cpp0000664000175000017500000002771712633754364022013 0ustar thomasthomas/*************************************************************************** rkhelpsearchwindow - description ------------------- begin : Fri Feb 25 2005 copyright : (C) 2005, 2006, 2007, 2009, 2010, 2011 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkhelpsearchwindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../rbackend/rinterface.h" #include "../rbackend/rcommandreceiver.h" #include "../rbackend/rksessionvars.h" #include "../debug.h" #include "../rkglobals.h" #include "../rkward.h" #include "../core/robject.h" #include "../misc/rkcommonfunctions.h" #include "../misc/rkdummypart.h" #include "../misc/rkstandardicons.h" #define GET_HELP 1 #define HELP_SEARCH 2 // result columns #define COL_TYPE 0 #define COL_TOPIC 1 #define COL_TITLE 2 #define COL_PACKAGE 3 #define COL_COUNT 4 RKHelpSearchWindow* RKHelpSearchWindow::main_help_search = 0; RKHelpSearchWindow::RKHelpSearchWindow (QWidget *parent, bool tool_window, const char *name) : RKMDIWindow (parent, SearchHelpWindow, tool_window, name) { RK_TRACE (APP); setPart (new RKDummyPart (0, this)); initializeActivationSignals (); setFocusPolicy (Qt::ClickFocus); QVBoxLayout* main_layout = new QVBoxLayout (this); QHBoxLayout* selection_layout = new QHBoxLayout (); main_layout->addLayout (selection_layout); selection_layout->setContentsMargins (0, 0, 0, 0); QVBoxLayout* labels_layout = new QVBoxLayout (); selection_layout->addLayout (labels_layout); labels_layout->setContentsMargins (0, 0, 0, 0); QLabel *label = new QLabel (i18n ("Find:"), this); label->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Minimum); labels_layout->addWidget (label); label = new QLabel (i18n ("Fields:"), this); label->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Minimum); labels_layout->addWidget (label); QVBoxLayout* main_settings_layout = new QVBoxLayout (); selection_layout->addLayout (main_settings_layout); main_settings_layout->setContentsMargins (0, 0, 0, 0); field = new QComboBox (this); field->setEditable (true); field->setSizePolicy (QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); connect (field->lineEdit () , SIGNAL (returnPressed()), this, SLOT (slotFindButtonClicked())); main_settings_layout->addWidget (field); QHBoxLayout* fields_packages_layout = new QHBoxLayout (); main_settings_layout->addLayout (fields_packages_layout); fields_packages_layout->setContentsMargins (0, 0, 0, 0); fieldsList = new QComboBox (this); fieldsList->setEditable (false); fieldsList->addItem (i18n ("All"), "c(\"alias\", \"concept\", \"title\",\"keyword\")"); fieldsList->addItem (i18n ("All but keywords"), "c(\"alias\", \"concept\", \"title\")"); fieldsList->addItem (i18n ("Keywords"), "c(\"keyword\")"); fieldsList->addItem (i18n ("Title"), "c(\"title\")"); fields_packages_layout->addWidget (fieldsList); label = new QLabel (i18n ("Package:"), this); label->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Minimum); fields_packages_layout->addWidget (label); packagesList = new QComboBox (this); packagesList->setEditable (false); fields_packages_layout->addWidget (packagesList); connect (RKSessionVars::instance (), SIGNAL (installedPackagesChanged()), this, SLOT (updateInstalledPackages())); updateInstalledPackages (); QVBoxLayout* checkboxes_layout = new QVBoxLayout (); selection_layout->addLayout (checkboxes_layout); checkboxes_layout->setContentsMargins (0, 0, 0, 0); caseSensitiveCheckBox = new QCheckBox (i18n ("Case sensitive"), this); checkboxes_layout->addWidget (caseSensitiveCheckBox); fuzzyCheckBox = new QCheckBox (i18n ("Fuzzy matching"), this); fuzzyCheckBox->setChecked (true); checkboxes_layout->addWidget (fuzzyCheckBox); findButton = new QPushButton (i18n ("Find"), this); findButton->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed); connect (findButton, SIGNAL (clicked()), this, SLOT (slotFindButtonClicked())); selection_layout->addWidget (findButton); results = new RKHelpSearchResultsModel (this); proxy_model = new QSortFilterProxyModel (this); proxy_model->setSourceModel (results); results_view = new QTreeView (this); results_view->setRootIsDecorated (false); results_view->setModel (proxy_model); results_view->setSortingEnabled (true); connect (results_view, SIGNAL (doubleClicked(QModelIndex)), this, SLOT (resultDoubleClicked(QModelIndex))); main_layout->addWidget (results_view); setCaption (i18n ("Help search")); } RKHelpSearchWindow::~RKHelpSearchWindow () { RK_TRACE (APP); } void RKHelpSearchWindow::focusInEvent (QFocusEvent *e) { RK_TRACE (APP); RKMDIWindow::focusInEvent (e); if (e->reason () != Qt::MouseFocusReason) { field->setFocus (); } } void RKHelpSearchWindow::getContextHelp (const QString &context_line, int cursor_pos) { RK_TRACE (APP); QString result = RKCommonFunctions::getCurrentSymbol (context_line, cursor_pos); if (result.isEmpty ()) return; getFunctionHelp (result); } void RKHelpSearchWindow::getFunctionHelp (const QString &function_name, const QString &package, const QString &type) { RK_TRACE (APP); // we use .rk.getHelp() instead of plain help() to receive an error, if no help could be found QString command = ".rk.getHelp("; if (type == "demo") command = "rk.demo("; else if (type == "vignette") command = "print (vignette("; command.append (RObject::rQuote (function_name)); if (!package.isEmpty ()) command.append (", package=" + RObject::rQuote (package)); command.append (")"); if (type == "vignette") command.append (")"); RKGlobals::rInterface ()->issueCommand (command, RCommand::App | RCommand::GetStringVector, i18n ("Find HTML help for %1").arg (function_name), this, GET_HELP); } void RKHelpSearchWindow::slotFindButtonClicked () { RK_TRACE (APP); if (field->currentText ().isEmpty ()) { return; } QString agrep = "FALSE"; if (fuzzyCheckBox->isChecked ()) { agrep="NULL"; } QString ignoreCase = "TRUE"; if(caseSensitiveCheckBox->isChecked ()) { ignoreCase="FALSE"; } QString package = ", package="; if (packagesList->currentIndex () == 0) { package.append ("NULL"); // all installed packages; actually we could also use package.clear(), here. } else if (packagesList->currentIndex () == 1) { package.append (".packages()"); // all loaded packages } else { package.append ("\"" + packagesList->currentText () + "\""); } QString fields = fieldsList->itemData (fieldsList->currentIndex ()).toString (); QString s = ".rk.get.search.results (" + RObject::rQuote (field->currentText ()) + ", agrep=" + agrep + ", ignore.case=" + ignoreCase + package + ", fields=" + fields + ')'; RKGlobals::rInterface ()->issueCommand (s, RCommand::App | RCommand::Sync | RCommand::GetStringVector, QString (), this, HELP_SEARCH, 0); setEnabled (false); field->addItem (field->currentText ()); } void RKHelpSearchWindow::resultDoubleClicked (const QModelIndex& index) { RK_TRACE (APP); if (!index.isValid ()) return; int row = proxy_model->mapToSource (index).row (); QString topic = results->data (results->index (row, COL_TOPIC)).toString (); if (topic.isEmpty ()) return; QString package = results->data (results->index (row, COL_PACKAGE)).toString (); QString type = results->resultsType (row); getFunctionHelp (topic, package, type); } void RKHelpSearchWindow::updateInstalledPackages () { RK_TRACE (APP); QString old_value = packagesList->currentText (); packagesList->clear (); packagesList->addItem (i18n("All installed packages")); packagesList->addItem (i18n("All loaded packages")); #if QT_VERSION >= 0x040400 packagesList->insertSeparator (2); #endif packagesList->addItems (RKSessionVars::instance ()->installedPackages ()); int index = 0; if (!old_value.isEmpty ()) index = packagesList->findText (old_value); if (index < 0) index = 0; packagesList->setCurrentIndex (index); } void RKHelpSearchWindow::rCommandDone (RCommand *command) { RK_TRACE (APP); if (command->getFlags () == HELP_SEARCH) { QStringList res; if (command->failed ()) { RK_ASSERT (false); } else { RK_ASSERT (command->getDataType () == RData::StringVector); res = command->stringVector (); } results->setResults (res); for (int i = 0; i < COL_COUNT; ++i) results_view->resizeColumnToContents (i); setEnabled(true); } else if (command->getFlags () == GET_HELP) { if (command->failed ()) { KMessageBox::sorry (this, i18n ("No help found on '%1'. Maybe the corresponding package is not installed/loaded, or maybe you mistyped the command. Try using Help->Search R Help for more options.", command->command ().section ('\"', 1, 1)), i18n ("No help found")); } } else { RK_ASSERT (false); } } //////////////// RKHelpResultsModel //////////////// RKHelpSearchResultsModel::RKHelpSearchResultsModel (QObject *parent) : QAbstractTableModel (parent) { RK_TRACE (APP); result_count = 0; } RKHelpSearchResultsModel::~RKHelpSearchResultsModel () { RK_TRACE (APP); } void RKHelpSearchResultsModel::setResults (const QStringList &results) { RK_TRACE (APP); RK_ASSERT ((results.size () % 4) == 0); result_count = results.size () / 4; topics = results.mid (0, result_count); titles = results.mid (result_count, result_count); packages = results.mid (result_count*2, result_count); types = results.mid (result_count*3, result_count); reset (); } int RKHelpSearchResultsModel::rowCount (const QModelIndex& parent) const { // don't trace if (parent.isValid ()) return 0; return result_count; } int RKHelpSearchResultsModel::columnCount (const QModelIndex& parent) const { // don't trace if (parent.isValid ()) return 0; return COL_COUNT; } QString RKHelpSearchResultsModel::resultsType (int row) { RK_TRACE (APP); if (row >= result_count) { RK_ASSERT (false); return QString (); } return types[row]; } QVariant RKHelpSearchResultsModel::data (const QModelIndex& index, int role) const { // don't trace // easier typing int row = index.row (); int col = index.column (); if (result_count && (row < result_count)) { if (role == Qt::DisplayRole || role == Qt::ToolTipRole) { if (col == COL_TOPIC) return topics[row]; if (col == COL_TITLE) return titles[row]; if (col == COL_PACKAGE) return packages[row]; if (col == COL_TYPE) return types[row]; } else if ((col == 0) && (role == Qt::DecorationRole)) { if (types[row] == "help") return RKStandardIcons::getIcon (RKStandardIcons::WindowHelp); if (types[row] == "demo") return RKStandardIcons::getIcon (RKStandardIcons::WindowCommandEditor); if (types[row] == "vignette") return RKStandardIcons::getIcon (RKStandardIcons::DocumentPDF); } } else { RK_ASSERT (false); } return QVariant (); } QVariant RKHelpSearchResultsModel::headerData (int section, Qt::Orientation orientation, int role) const { // don't trace if (orientation == Qt::Horizontal) { if (role == Qt::DisplayRole) { if (section == COL_TOPIC) return (i18n ("Topic")); if (section == COL_TITLE) return (i18n ("Title")); if (section == COL_PACKAGE) return (i18n ("Package")); if (section == COL_TYPE) return (i18n ("Type")); } } return QVariant (); } #include "rkhelpsearchwindow.moc" rkward-0.6.4/rkward/windows/rcontrolwindow.h0000664000175000017500000000507712633754364020642 0ustar thomasthomas/*************************************************************************** rcontrolwindow - description ------------------- begin : Wed Oct 12 2005 copyright : (C) 2005, 2007, 2009 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RCONTROLWINDOW_H #define RCONTROLWINDOW_H #include #include #include "rkmdiwindow.h" class QPushButton; class RCommand; class RCommandChain; class RChainOrCommand; class RControlWindowListViewItem; /** \brief Interface to control R command execution This class provides a GUI interface to inspect, and manipulate the current RCommandStack, and to Pause/Resume the R engine. @author Thomas Friedrichsmeier */ class RControlWindow : public RKMDIWindow { Q_OBJECT public: /** constructor. @param parent parent QWidget, usually RKGlobals::rkApp () or similar */ RControlWindow (QWidget *parent, bool tool_window, const char *name=0); /** destructor */ ~RControlWindow (); /** reimplemented to start listening to the RCommandStackModel when showing. */ void showEvent (QShowEvent *e); /** when hidden, disconnect from the RCommandStackModel to save resources */ void hideEvent (QHideEvent *e); /** Static reference to the control window */ static RControlWindow* getControl () { return control_window; }; public slots: /** cancel button was clicked. Cancel selected commands (unless they are RCommand::Sync). */ void cancelButtonClicked (); /** pause button was clicked. Pause/Resume processing of the stack */ void pauseButtonClicked (); /** configure button was clicked. Invoke settings dialog */ void configureButtonClicked (); private: QTreeView *commands_view; QPushButton *cancel_button; QPushButton *pause_button; bool paused; friend class RKWardMainWindow; static RControlWindow *control_window; }; #endif rkward-0.6.4/rkward/windows/rkhtmlwindow.cpp0000664000175000017500000011152312633754364020626 0ustar thomasthomas/*************************************************************************** rkhtmlwindow - description ------------------- begin : Wed Oct 12 2005 copyright : (C) 2005-2015 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkhtmlwindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../rkglobals.h" #include "../rbackend/rinterface.h" #include "rkhelpsearchwindow.h" #include "../rkward.h" #include "../rkconsole.h" #include "../settings/rksettingsmodulegeneral.h" #include "../settings/rksettingsmoduler.h" #include "../settings/rksettingsmoduleoutput.h" #include "../misc/rkcommonfunctions.h" #include "../misc/rkstandardactions.h" #include "../misc/rkstandardicons.h" #include "../misc/xmlhelper.h" #include "../misc/rkxmlguisyncer.h" #include "../misc/rkprogresscontrol.h" #include "../misc/rkmessagecatalog.h" #include "../misc/rkfindbar.h" #include "../plugin/rkcomponentmap.h" #include "../windows/rkworkplace.h" #include "../windows/rkworkplaceview.h" #include "../debug.h" RKWebPage::RKWebPage (RKHTMLWindow* window): KWebPage (window, KIOIntegration | KPartsIntegration) { RK_TRACE (APP); RKWebPage::window = window; new_window = false; direct_load = false; settings ()->setFontFamily (QWebSettings::StandardFont, KGlobalSettings::generalFont ().family ()); settings ()->setFontFamily (QWebSettings::FixedFont, KGlobalSettings::fixedFont ().family ()); } bool RKWebPage::acceptNavigationRequest (QWebFrame* frame, const QNetworkRequest& request, QWebPage::NavigationType type) { Q_UNUSED (type); RK_TRACE (APP); RK_DEBUG (APP, DL_DEBUG, "Navigation request to %s", qPrintable (request.url ().toString ())); if (direct_load && (frame == mainFrame ())) { direct_load = false; return true; } if (new_window) { frame = 0; new_window = false; } if (!frame) { RKWorkplace::mainWorkplace ()->openAnyUrl (request.url ()); return false; } if (KUrl (mainFrame ()->url ()).equals (request.url (), KUrl::CompareWithoutFragment | KUrl::CompareWithoutTrailingSlash)) { RK_DEBUG (APP, DL_DEBUG, "Page internal navigation request from %s to %s", qPrintable (mainFrame ()->url ().toString ()), qPrintable (request.url ().toString ())); emit (pageInternalNavigation (request.url ())); return true; } window->openURL (request.url ()); return false; } void RKWebPage::load (const QUrl& url) { RK_TRACE (APP); direct_load = true; mainFrame ()->load (url); } QWebPage* RKWebPage::createWindow (QWebPage::WebWindowType) { RK_TRACE (APP); new_window = true; // Don't actually create the window, until we know which URL we're talking about. return (this); } RKHTMLWindow::RKHTMLWindow (QWidget *parent, WindowMode mode) : RKMDIWindow (parent, RKMDIWindow::HelpWindow) { RK_TRACE (APP); current_cache_file = 0; QVBoxLayout* layout = new QVBoxLayout (this); layout->setContentsMargins (0, 0, 0, 0); view = new KWebView (this, false); page = new RKWebPage (this); view->setPage (page); view->setContextMenuPolicy (Qt::CustomContextMenu); layout->addWidget (view, 1); findbar = new RKFindBar (this); layout->addWidget (findbar); findbar->hide (); connect (findbar, SIGNAL(findRequest(QString,bool,const RKFindBar*,bool*)), this, SLOT(findRequest(QString,bool,const RKFindBar*,bool*))); have_highlight = false; part = new RKHTMLWindowPart (this); setPart (part); part->initActions (); initializeActivationSignals (); part->setSelectable (true); setFocusPolicy (Qt::StrongFocus); setFocusProxy (view); // We have to connect this in order to allow browsing. connect (page, SIGNAL (pageInternalNavigation(QUrl)), this, SLOT (internalNavigation(QUrl))); connect (page, SIGNAL (downloadRequested(QNetworkRequest)), this, SLOT (saveRequested(QNetworkRequest))); connect (page, SIGNAL (printRequested(QWebFrame*)), this, SLOT(slotPrint())); connect (view, SIGNAL (customContextMenuRequested(QPoint)), this, SLOT(makeContextMenu(QPoint))); current_history_position = -1; url_change_is_from_history = false; window_mode = Undefined; useMode (mode); // needed to enable / disable the run selection action connect (view, SIGNAL (selectionChanged()), this, SLOT (selectionChanged())); selectionChanged (); } RKHTMLWindow::~RKHTMLWindow () { RK_TRACE (APP); delete current_cache_file; } KUrl RKHTMLWindow::restorableUrl () { RK_TRACE (APP); return (current_url.url ().replace (RKSettingsModuleR::helpBaseUrl(), "rkward://RHELPBASE")); } bool RKHTMLWindow::isModified () { RK_TRACE (APP); return false; } void RKHTMLWindow::makeContextMenu (const QPoint& pos) { RK_TRACE (APP); QMenu *menu = page->createStandardContextMenu (); menu->addAction (part->run_selection); menu->exec (view->mapToGlobal (pos)); delete (menu); } void RKHTMLWindow::selectionChanged () { RK_TRACE (APP); if (!(part && part->run_selection)) { RK_ASSERT (false); return; } #if QT_VERSION >= 0x040800 part->run_selection->setEnabled (view->hasSelection ()); #else part->run_selection->setEnabled (!view->selectedText ().isEmpty ()); #endif } void RKHTMLWindow::runSelection () { RK_TRACE (APP); RKConsole::pipeUserCommand (view->selectedText ()); } void RKHTMLWindow::findRequest (const QString& text, bool backwards, const RKFindBar* findbar, bool* found) { RK_TRACE (APP); QWebPage::FindFlags flags = QWebPage::FindWrapsAroundDocument; if (backwards) flags |= QWebPage::FindBackward; bool highlight = findbar->isOptionSet (RKFindBar::HighlightAll); if (highlight) flags |= QWebPage::HighlightAllOccurrences; if (findbar->isOptionSet (RKFindBar::MatchCase)) flags |= QWebPage::FindCaseSensitively; // clear previous highlight, if any if (have_highlight) page->findText (QString (), QWebPage::HighlightAllOccurrences); *found = page->findText (text, flags); have_highlight = found && highlight; } void RKHTMLWindow::slotPrint () { RK_TRACE (APP); // NOTE: taken from kwebkitpart, with small mods // Make it non-modal, in case a redirection deletes the part QPointer dlg (new QPrintDialog (view)); if (dlg->exec () == QPrintDialog::Accepted) { view->print (dlg->printer ()); } delete dlg; } void RKHTMLWindow::slotSave () { RK_TRACE (APP); page->downloadUrl (page->mainFrame ()->url ()); } void RKHTMLWindow::saveRequested (const QNetworkRequest& request) { RK_TRACE (APP); page->downloadUrl (request.url ()); } void RKHTMLWindow::openLocationFromHistory (VisitedLocation &loc) { RK_TRACE (APP); RK_ASSERT (window_mode == HTMLHelpWindow); int history_last = url_history.count () - 1; RK_ASSERT (current_history_position >= 0); RK_ASSERT (current_history_position <= history_last); if (loc.url == current_url) { restoreBrowserState (&loc); } else { url_change_is_from_history = true; openURL (loc.url); // TODO: merge into restoreBrowserState()? restoreBrowserState (&loc); url_change_is_from_history = false; } part->back->setEnabled (current_history_position > 0); part->forward->setEnabled (current_history_position < history_last); } void RKHTMLWindow::slotForward () { RK_TRACE (APP); ++current_history_position; openLocationFromHistory (url_history[current_history_position]); } void RKHTMLWindow::slotBack () { RK_TRACE (APP); // if going back from the end of the history, save that position, first. if (current_history_position >= (url_history.count () - 1)) { changeURL (current_url); --current_history_position; } --current_history_position; openLocationFromHistory (url_history[current_history_position]); } void RKHTMLWindow::openRKHPage (const KUrl& url) { RK_TRACE (APP); RK_ASSERT (url.protocol () == "rkward"); changeURL (url); bool ok = false; if ((url.host () == "component") || (url.host () == "page")) { useMode (HTMLHelpWindow); startNewCacheFile (); RKHelpRenderer render (current_cache_file); ok = render.renderRKHelp (url); current_cache_file->close (); KUrl cache_url = KUrl::fromLocalFile (current_cache_file->fileName ()); cache_url.setFragment (url.fragment ()); page->load (cache_url); } else if (url.host ().toUpper () == "RHELPBASE") { // NOTE: QUrl () may lowercase the host part, internally KUrl fixed_url = KUrl (RKSettingsModuleR::helpBaseUrl ()); fixed_url.setPath (url.path ()); if (url.hasQuery ()) fixed_url.setQuery (url.query ()); if (url.hasFragment ()) fixed_url.setFragment (url.fragment ()); ok = openURL (fixed_url); } if (!ok) { fileDoesNotExistMessage (); } } // static bool RKHTMLWindow::handleRKWardURL (const KUrl &url, RKHTMLWindow *window) { RK_TRACE (APP); if (url.protocol () == "rkward") { if (url.host () == "runplugin") { QString path = url.path (); if (path.startsWith ('/')) path = path.mid (1); int sep = path.indexOf ('/'); // NOTE: These links may originate externally, even from untrusted sources. The submit mode *must* remain "ManualSubmit" for this reason! RKComponentMap::invokeComponent (path.left (sep), path.mid (sep+1).split ('\n', QString::SkipEmptyParts), RKComponentMap::ManualSubmit); return true; } else { if (url.host () == "rhelp") { // TODO: find a nice solution to render this in the current window QStringList spec = url.path ().mid (1).split ('/'); QString function, package, type; if (!spec.isEmpty ()) function = spec.takeLast (); if (!spec.isEmpty ()) package = spec.takeLast (); if (!spec.isEmpty ()) type = spec.takeLast (); RKHelpSearchWindow::mainHelpSearch ()->getFunctionHelp (function, package, type); return true; } if (window) window->openRKHPage (url); else RKWorkplace::mainWorkplace ()->openHelpWindow (url); // will recurse with window set, via openURL() return true; } } return false; } bool RKHTMLWindow::openURL (const KUrl &url) { RK_TRACE (APP); if (handleRKWardURL (url, this)) return true; if (window_mode == HTMLOutputWindow) { if (url != current_url) { // output window should not change url after initialization if (!current_url.isEmpty ()) { RK_ASSERT (false); return false; } current_url = url; // needs to be set before registering RKOutputWindowManager::self ()->registerWindow (this); } } if (url.isLocalFile () && (KMimeType::findByUrl (url)->is ("text/html") || window_mode == HTMLOutputWindow)) { changeURL (url); QFileInfo out_file (url.toLocalFile ()); bool ok = out_file.exists(); if (ok) { page->load (url); } else { fileDoesNotExistMessage (); } return ok; } if (url_change_is_from_history || url.protocol ().toLower ().startsWith ("help")) { // handle help pages, and any page that we have previously handled (from history) changeURL (url); page->load (url); return true; } // special casing for R's dynamic help pages. These should be considered local, even though they are served through http if (url.protocol ().toLower ().startsWith ("http")) { QString host = url.host (); if ((host == "127.0.0.1") || (host == "localhost") || host == QHostInfo::localHostName ()) { KIO::TransferJob *job = KIO::get (url, KIO::Reload); connect (job, SIGNAL (mimetype(KIO::Job*,QString)), this, SLOT (mimeTypeDetermined(KIO::Job*,QString))); return true; } } RKWorkplace::mainWorkplace ()->openAnyUrl (url, QString (), KMimeType::findByUrl (url)->is ("text/html")); // NOTE: text/html type urls, which we have not handled, above, are forced to be opened externally, to avoid recursion. E.g. help:// protocol urls. return true; } KUrl RKHTMLWindow::url () { return current_url; } void RKHTMLWindow::mimeTypeDetermined (KIO::Job* job, const QString& type) { RK_TRACE (APP); KIO::TransferJob* tj = static_cast (job); KUrl url = tj->url (); tj->putOnHold (); if (type == "text/html") { changeURL (url); page->load (url); } else { RKWorkplace::mainWorkplace ()->openAnyUrl (url, type); } } void RKHTMLWindow::internalNavigation (const QUrl& new_url) { RK_TRACE (APP); KUrl real_url = current_url; // Note: This could be something quite different from new_url: a temp file for rkward://-urls. We know the base part of the URL has not actually changed, when this gets called, though. real_url.setFragment (new_url.fragment ()); changeURL (real_url); } void RKHTMLWindow::changeURL (const KUrl &url) { KUrl prev_url = current_url; current_url = url; updateCaption (url); if (!url_change_is_from_history) { if (window_mode == HTMLHelpWindow) { if (current_history_position >= 0) { // skip initial blank page url_history = url_history.mid (0, current_history_position); VisitedLocation loc; loc.url = prev_url; saveBrowserState (&loc); url_history.append (loc); } ++current_history_position; part->back->setEnabled (current_history_position > 0); part->forward->setEnabled (false); } } } void RKHTMLWindow::updateCaption (const KUrl &url) { RK_TRACE (APP); if (window_mode == HTMLOutputWindow) setCaption (i18n ("Output %1").arg (url.fileName ())); else setCaption (url.fileName ()); } void RKHTMLWindow::refresh () { RK_TRACE (APP); view->reload (); } void RKHTMLWindow::scrollToBottom () { RK_TRACE (APP); RK_ASSERT (window_mode == HTMLOutputWindow); view->page ()->mainFrame ()->setScrollBarValue (Qt::Vertical, view->page ()->mainFrame ()->scrollBarMaximum (Qt::Vertical)); } void RKHTMLWindow::zoomIn () { RK_TRACE (APP); view->setZoomFactor (view->zoomFactor () * 1.1); } void RKHTMLWindow::zoomOut () { RK_TRACE (APP); view->setZoomFactor (view->zoomFactor () / 1.1); } void RKHTMLWindow::setTextEncoding (QTextCodec* encoding) { RK_TRACE (APP); page->settings ()->setDefaultTextEncoding (encoding->name ()); view->reload (); } void RKHTMLWindow::useMode (WindowMode new_mode) { RK_TRACE (APP); if (window_mode == new_mode) return; if (new_mode == HTMLOutputWindow) { type = RKMDIWindow::OutputWindow | RKMDIWindow::DocumentWindow; setWindowIcon (RKStandardIcons::getIcon (RKStandardIcons::WindowOutput)); part->setOutputWindowSkin (); setMetaInfo (i18n ("Output Window"), "rkward://page/rkward_output", RKSettings::PageOutput); connect (page, SIGNAL(loadFinished(bool)), this, SLOT(scrollToBottom())); // TODO: This would be an interesting extension, but how to deal with concurrent edits? // page->setContentEditable (true); } else { RK_ASSERT (new_mode == HTMLHelpWindow); type = RKMDIWindow::HelpWindow | RKMDIWindow::DocumentWindow; setWindowIcon (RKStandardIcons::getIcon (RKStandardIcons::WindowHelp)); part->setHelpWindowSkin (); disconnect (page, SIGNAL(loadFinished(bool)), this, SLOT(scrollToBottom())); } updateCaption (current_url); window_mode = new_mode; } void RKHTMLWindow::startNewCacheFile () { delete current_cache_file; current_cache_file = new KTemporaryFile (); current_cache_file->setSuffix (".html"); current_cache_file->open (); } void RKHTMLWindow::fileDoesNotExistMessage () { RK_TRACE (APP); startNewCacheFile (); if (window_mode == HTMLOutputWindow) { current_cache_file->write (i18n ("

RKWard output file could not be found

\n").toUtf8 ()); } else { current_cache_file->write (QString ("

" + i18n ("Page does not exist or is broken") + "

").toUtf8 ()); } current_cache_file->close (); KUrl cache_url = KUrl::fromLocalFile (current_cache_file->fileName ()); page->load (cache_url); } void RKHTMLWindow::flushOutput () { RK_TRACE (APP); int res = KMessageBox::questionYesNo (this, i18n ("Do you really want to clear the output? This will also remove all image files used in the output. It will not be possible to restore it."), i18n ("Flush output?")); if (res==KMessageBox::Yes) { QFile out_file (current_url.toLocalFile ()); RCommand *c = new RCommand ("rk.flush.output (" + RCommand::rQuote (out_file.fileName ()) + ", ask=FALSE)\n", RCommand::App); connect (c->notifier (), SIGNAL (commandFinished(RCommand*)), this, SLOT (refresh())); RKProgressControl *status = new RKProgressControl (this, i18n ("Flushing output"), i18n ("Flushing output"), RKProgressControl::CancellableNoProgress); status->addRCommand (c, true); status->doNonModal (true); RKGlobals::rInterface ()->issueCommand (c); } } void RKHTMLWindow::saveBrowserState (VisitedLocation* state) { RK_TRACE (APP); if (view && view->page () && view->page ()->mainFrame ()) { state->scroll_position = view->page ()->mainFrame ()->scrollPosition (); } else { state->scroll_position = QPoint (); } } void RKHTMLWindow::restoreBrowserState (VisitedLocation* state) { RK_TRACE (APP); if (state->scroll_position.isNull ()) return; RK_ASSERT (view && view->page () && view->page ()->mainFrame ()); view->page ()->mainFrame ()->setScrollPosition (state->scroll_position); } RKHTMLWindowPart::RKHTMLWindowPart (RKHTMLWindow* window) : KParts::Part (window) { RK_TRACE (APP); setComponentData (KGlobal::mainComponent ()); RKHTMLWindowPart::window = window; setWidget (window); } void RKHTMLWindowPart::initActions () { RK_TRACE (APP); // We keep our own history. window->page->action (QWebPage::Back)->setVisible (false); window->page->action (QWebPage::Forward)->setVisible (false); // For now we won't bother with this one: Does not behave well, in particular (but not only) WRT to rkward://-links window->page->action (QWebPage::DownloadLinkToDisk)->setVisible (false); // common actions actionCollection ()->addAction (KStandardAction::Copy, "copy", window->view->pageAction (QWebPage::Copy), SLOT (trigger())); QAction* zoom_in = actionCollection ()->addAction ("zoom_in", new KAction (KIcon ("zoom-in"), i18n ("Zoom In"), this)); connect (zoom_in, SIGNAL(triggered(bool)), window, SLOT (zoomIn())); QAction* zoom_out = actionCollection ()->addAction ("zoom_out", new KAction (KIcon ("zoom-out"), i18n ("Zoom Out"), this)); connect (zoom_out, SIGNAL(triggered(bool)), window, SLOT (zoomOut())); actionCollection ()->addAction (KStandardAction::SelectAll, "select_all", window->view->pageAction (QWebPage::SelectAll), SLOT (trigger())); // unfortunately, this will only affect the default encoding, not necessarily the "real" encoding KCodecAction *encoding = new KCodecAction (KIcon ("character-set"), i18n ("Default &Encoding"), this, true); encoding->setStatusTip (i18n ("Set the encoding to assume in case no explicit encoding has been set in the page or in the HTTP headers.")); actionCollection ()->addAction ("view_encoding", encoding); connect (encoding, SIGNAL (triggered(QTextCodec*)), window, SLOT (setTextEncoding(QTextCodec*))); print = actionCollection ()->addAction (KStandardAction::Print, "print_html", window, SLOT (slotPrint())); save_page = actionCollection ()->addAction (KStandardAction::Save, "save_html", window, SLOT (slotSave())); run_selection = RKStandardActions::runCurrent (window, window, SLOT (runSelection())); // help window actions back = actionCollection ()->addAction (KStandardAction::Back, "help_back", window, SLOT (slotBack())); back->setEnabled (false); forward = actionCollection ()->addAction (KStandardAction::Forward, "help_forward", window, SLOT (slotForward())); forward->setEnabled (false); // output window actions outputFlush = actionCollection ()->addAction ("output_flush", window, SLOT (flushOutput())); outputFlush->setText (i18n ("&Flush Output")); outputFlush->setIcon (KIcon ("edit-delete")); outputRefresh = actionCollection ()->addAction ("output_refresh", window->page->action (QWebPage::ReloadAndBypassCache), SLOT (trigger())); outputRefresh->setText (i18n ("&Refresh Output")); outputRefresh->setIcon (KIcon ("view-refresh")); actionCollection ()->addAction (KStandardAction::Find, "find", window->findbar, SLOT (activate())); KAction* findAhead = actionCollection ()->addAction ("find_ahead", new KAction (i18n ("Find as you type"), this)); findAhead->setShortcut ('/'); connect (findAhead, SIGNAL (triggered(bool)), window->findbar, SLOT (activate())); actionCollection ()->addAction (KStandardAction::FindNext, "find_next", window->findbar, SLOT (forward()));; actionCollection ()->addAction (KStandardAction::FindPrev, "find_previous", window->findbar, SLOT (backward()));;; } void RKHTMLWindowPart::setOutputWindowSkin () { RK_TRACE (APP); print->setText (i18n ("Print output")); save_page->setText (i18n ("Save Output as HTML")); setXMLFile ("rkoutputwindow.rc"); run_selection->setVisible (false); } void RKHTMLWindowPart::setHelpWindowSkin () { RK_TRACE (APP); print->setText (i18n ("Print page")); save_page->setText (i18n ("Export page as HTML")); setXMLFile ("rkhelpwindow.rc"); run_selection->setVisible (true); } ////////////////////////////////////////// ////////////////////////////////////////// bool RKHelpRenderer::renderRKHelp (const KUrl &url) { RK_TRACE (APP); if (url.protocol () != "rkward") { RK_ASSERT (false); return (false); } bool for_component = false; // is this a help page for a component, or a top-level help page? if (url.host () == "component") for_component = true; QStringList anchors, anchornames; RKComponentHandle *chandle = 0; if (for_component) { chandle = componentPathToHandle (url.path ()); if (!chandle) return false; } component_xml = new XMLHelper (for_component ? chandle->getFilename () : QString (), for_component ? chandle->messageCatalog () : 0); QString help_file_name; QDomElement element; QString help_base_dir = RKCommonFunctions::getRKWardDataDir () + "pages/"; QString css_filename = QUrl::fromLocalFile (help_base_dir + "rkward_help.css").toString (); // determine help file, and prepare if (for_component) { component_doc_element = component_xml->openXMLFile (DL_ERROR); if (component_doc_element.isNull ()) return false; element = component_xml->getChildElement (component_doc_element, "help", DL_ERROR); if (!element.isNull ()) { help_file_name = component_xml->getStringAttribute (element, "file", QString (), DL_ERROR); if (!help_file_name.isEmpty ()) help_file_name = QFileInfo (chandle->getFilename ()).absoluteDir ().filePath (help_file_name); } } else { help_file_name = help_base_dir + url.path () + ".rkh"; } RK_DEBUG (APP, DL_DEBUG, "rendering help page for local file %s", help_file_name.toLatin1().data()); // open help file const RKMessageCatalog *catalog = component_xml->messageCatalog (); if (!for_component) catalog = RKMessageCatalog::getCatalog ("rkward__pages", RKCommonFunctions::getRKWardDataDir () + "po/"); help_xml = new XMLHelper (help_file_name, catalog); help_doc_element = help_xml->openXMLFile (DL_ERROR); if (help_doc_element.isNull () && (!for_component)) return false; // initialize output, and set title QString page_title (i18n ("No Title")); if (for_component) { page_title = chandle->getLabel (); } else { element = help_xml->getChildElement (help_doc_element, "title", DL_WARNING); page_title = help_xml->i18nElementText (element, false, DL_WARNING); } writeHTML ("" + page_title + "" "\n
\n

" + page_title + "

\n"); if (help_doc_element.isNull ()) { RK_ASSERT (for_component); writeHTML (i18n ("

Help page missing

\n

The help page for this component has not yet been written (or is broken). Please consider contributing it.

\n")); } if (for_component) { QString component_id = componentPathToId (url.path()); RKComponentHandle *handle = componentPathToHandle (url.path()); if (handle && handle->isAccessible ()) writeHTML ("" + i18n ("Use %1 now", page_title) + ""); } // fix all elements containing an "src" attribute QDir base_path (QFileInfo (help_file_name).absolutePath()); XMLChildList src_elements = help_xml->findElementsWithAttribute (help_doc_element, "src", QString (), true, DL_DEBUG); for (XMLChildList::iterator it = src_elements.begin (); it != src_elements.end (); ++it) { QString src = (*it).attribute ("src"); if (KUrl::isRelativeUrl (src)) { src = "file://" + QDir::cleanPath (base_path.filePath (src)); (*it).setAttribute ("src", src); } } // render the sections element = help_xml->getChildElement (help_doc_element, "summary", DL_INFO); if (!element.isNull ()) { writeHTML (startSection ("summary", i18n ("Summary"), QString (), &anchors, &anchornames)); writeHTML (renderHelpFragment (element)); } element = help_xml->getChildElement (help_doc_element, "usage", DL_INFO); if (!element.isNull ()) { writeHTML (startSection ("usage", i18n ("Usage"), QString (), &anchors, &anchornames)); writeHTML (renderHelpFragment (element)); } XMLChildList section_elements = help_xml->getChildElements (help_doc_element, "section", DL_INFO); for (XMLChildList::iterator it = section_elements.begin (); it != section_elements.end (); ++it) { QString title = help_xml->i18nStringAttribute (*it, "title", QString (), DL_WARNING); QString shorttitle = help_xml->i18nStringAttribute (*it, "shorttitle", QString (), DL_DEBUG); QString id = help_xml->getStringAttribute (*it, "id", QString (), DL_WARNING); writeHTML (startSection (id, title, shorttitle, &anchors, &anchornames)); writeHTML (renderHelpFragment (*it)); } // the section "settings" is the most complicated, as the labels of the individual GUI items has to be fetched from the component description. Of course it is only meaningful for component help, and not rendered for top level help pages. if (for_component) { element = help_xml->getChildElement (help_doc_element, "settings", DL_INFO); if (!element.isNull ()) { writeHTML (startSection ("settings", i18n ("GUI settings"), QString (), &anchors, &anchornames)); XMLChildList setting_elements = help_xml->getChildElements (element, QString (), DL_WARNING); for (XMLChildList::iterator it = setting_elements.begin (); it != setting_elements.end (); ++it) { if ((*it).tagName () == "setting") { QString id = help_xml->getStringAttribute (*it, "id", QString (), DL_WARNING); QString title = help_xml->i18nStringAttribute (*it, "title", QString (), DL_INFO); if (title.isEmpty ()) title = resolveLabel (id); writeHTML ("

" + title + "

"); writeHTML (renderHelpFragment (*it)); } else if ((*it).tagName () == "caption") { QString id = help_xml->getStringAttribute (*it, "id", QString (), DL_WARNING); QString title = help_xml->i18nStringAttribute (*it, "title", QString (), DL_INFO); if (title.isEmpty ()) title = resolveLabel (id); writeHTML ("

" + title + "

"); } else { help_xml->displayError (&(*it), "Tag not allowed, here", DL_WARNING); } } } } // "related" section element = help_xml->getChildElement (help_doc_element, "related", DL_INFO); if (!element.isNull ()) { writeHTML (startSection ("related", i18n ("Related functions and pages"), QString (), &anchors, &anchornames)); writeHTML (renderHelpFragment (element)); } // "technical" section element = help_xml->getChildElement (help_doc_element, "technical", DL_INFO); if (!element.isNull ()) { writeHTML (startSection ("technical", i18n ("Technical details"), QString (), &anchors, &anchornames)); writeHTML (renderHelpFragment (element)); } if (for_component) { // "dependencies" section QList deps = chandle->getDependencies (); if (!deps.isEmpty ()) { writeHTML (startSection ("dependencies", i18n ("Dependencies"), QString (), &anchors, &anchornames)); writeHTML (RKComponentDependency::depsToHtml (deps)); } } // "about" section RKComponentAboutData about; if (for_component) { about = chandle->getAboutData (); } else { about = RKComponentAboutData (help_xml->getChildElement (help_doc_element, "about", DL_INFO), *help_xml); } if (about.valid) { writeHTML (startSection ("about", i18n ("About"), QString (), &anchors, &anchornames)); writeHTML (about.toHtml ()); } // create a navigation bar KUrl url_copy = url; QString navigation = i18n ("

On this page:

"); RK_ASSERT (anchornames.size () == anchors.size ()); for (int i = 0; i < anchors.size (); ++i) { QString anchor = anchors[i]; QString anchorname = anchornames[i]; if (!(anchor.isEmpty () || anchorname.isEmpty ())) { url_copy.setRef (anchor); navigation.append ("

" + anchorname + "

\n"); } } writeHTML ("
" + navigation + "
"); writeHTML ("\n"); return (true); } QString RKHelpRenderer::resolveLabel (const QString& id) const { RK_TRACE (APP); QDomElement source_element = component_xml->findElementWithAttribute (component_doc_element, "id", id, true, DL_WARNING); if (source_element.isNull ()) { RK_DEBUG (PLUGIN, DL_ERROR, "No such UI element: %s", qPrintable (id)); } return (component_xml->i18nStringAttribute (source_element, "label", i18n ("Unnamed GUI element"), DL_WARNING)); } QString RKHelpRenderer::renderHelpFragment (QDomElement &fragment) { RK_TRACE (APP); QString text = help_xml->i18nElementText (fragment, true, DL_WARNING); // Can't resolve links and references based on the already parsed dom-tree, because they can be inside string to be translated. // I.e. resolving links before doing i18n will cause i18n-lookup to fail int pos = 0; int npos; QString ret; while ((npos = text.indexOf ("= 0) { ret += text.mid (pos, npos - pos); QString href; int href_start = text.indexOf (" href=\"", npos + 5); if (href_start >= 0) { href_start += 7; int href_end = text.indexOf ("\"", href_start); href = text.mid (href_start, href_end - href_start); } QString linktext; int end = text.indexOf (">", npos) + 1; if (text[end-2] != QChar ('/')) { int nend = text.indexOf ("", end); linktext = text.mid (end, nend - end); end = nend + 7; } ret += prepareHelpLink (href, linktext); pos = end; } ret += text.mid (pos); if (component_xml) { text = ret; ret.clear (); pos = 0; while ((npos = text.indexOf ("