From b2b6a9eeb26d8db0adad9e2a2c0dcd16516c7be9 Mon Sep 17 00:00:00 2001 From: Thomas Leonard Date: Mon, 17 Jul 2000 17:16:43 +0000 Subject: [PATCH] r328: Changed the install script so that the CVS directories don't get installed. Pinboard selections work, as does clicking on the root window. And popup menus. Keys bound to menu entries are automatically saved when the filer quits. The 'rox' script now just calls AppRun directly. Panels can be created without starting a new copy of the filer. The pinboard can be changed or removed by using --pinboard a second time. Files can be given as arguments - they are opened as if they were clicked on in a filer window (suggested by Alex Holden). If no arguments are given then the default is now the current directory (not your home directory). If you start the leafname in the path minibuffer with '.' then the Show Hidden feature is temporarily turned on. Pinned icons are now updated when the pointer moves over them, if necessary. Drag and drop works properly. --- ROX-Filer/Help/Changes | 42 ++- ROX-Filer/Help/TODO | 18 +- ROX-Filer/src/Docs/Manual.lyx | 247 ++++++++++++++---- ROX-Filer/src/Docs/Structs.dia | Bin 3179 -> 3555 bytes ROX-Filer/src/config.h.in | 2 +- ROX-Filer/src/display.c | 1 + ROX-Filer/src/filer.c | 3 +- ROX-Filer/src/filer.h | 5 + ROX-Filer/src/gui_support.c | 100 +++++++- ROX-Filer/src/gui_support.h | 2 + ROX-Filer/src/main.c | 105 ++++---- ROX-Filer/src/menu.c | 256 +++++++++++-------- ROX-Filer/src/menu.h | 2 + ROX-Filer/src/minibuffer.c | 25 ++ ROX-Filer/src/options.c | 2 +- ROX-Filer/src/options.h | 4 +- ROX-Filer/src/pinboard.c | 561 +++++++++++++++++++++++++++++++++++++---- ROX-Filer/src/pinboard.h | 9 + ROX-Filer/src/remote.c | 64 +---- ROX-Filer/src/remote.h | 7 +- ROX-Filer/src/run.c | 148 ++++++++++- ROX-Filer/src/run.h | 8 + ROX-Filer/src/type.c | 6 +- install.sh | 51 +++- rox | 202 --------------- 25 files changed, 1331 insertions(+), 539 deletions(-) rewrite ROX-Filer/src/Docs/Structs.dia (100%) delete mode 100755 rox diff --git a/ROX-Filer/Help/Changes b/ROX-Filer/Help/Changes index 7a5bd4e8..1348d7cd 100644 --- a/ROX-Filer/Help/Changes +++ b/ROX-Filer/Help/Changes @@ -2,6 +2,43 @@ A RISC OS-like filer for X by Thomas Leonard +17-Jul-2000 +~~~~~~~~~~~ +Clicking on the root window works (button-1 clears the selection, +button-3 opens the pinboard menu). The pinboard can own the primary +selection, which means that you can select something and then paste +its name into another program. + +Keys bound to menu entries are automatically saved when the filer quits. + +16-Jul-2000 +~~~~~~~~~~~ +Changed the install script so that the CVS directories don't get installed. + +The 'rox' script now just calls AppRun directly. +Panels can be created without starting a new copy of the filer. +The pinboard can be changed or removed by using --pinboard a second time. + +Files can be given as arguments - they are opened as if they were clicked +on in a filer window (suggested by Alex Holden). If no arguments are +given then the default is now the current directory (not your home +directory). + +If you start the leafname in the path minibuffer with '.' then the Show +Hidden feature is temporarily turned on. + +15-Jul-2000 +~~~~~~~~~~~ +Ctrl-clicking selects and unselects pinboard icons. Menu clicking +selects the icon clicked on while the menu is open. Pinned icons are +now updated when the pointer moves over them, if necessary. + +14-Jul-2000 +~~~~~~~~~~~ +Menu-clicking on a pinboard icon now brings up a menu. +Clicks on the root window are still ignored though; need to support +the GNOME-compliant window manager system for passing root clicks on... + 12-Jul-2000 ~~~~~~~~~~~ Files can now be dragged to pinboard icons, and they highlight nicely too! @@ -17,7 +54,7 @@ like normal icons. ~~~~~~~~~~~ Changed the DnD code to make it easier for the pinboard code to use it. Up and Home buttons on the toolbar now use the 'New window on button 1' -option setting. +option setting (suggested by Vincent Lefèvre). 09-Jul-2000 ~~~~~~~~~~~ @@ -46,7 +83,8 @@ some versions of GTK+. 30-Jun-2000 ~~~~~~~~~~~ -First steps towards pinboard support. +First steps towards pinboard support (requested by P.S.S.Camp and +Darren Winsper). 16-Jun-2000 ~~~~~~~~~~~ diff --git a/ROX-Filer/Help/TODO b/ROX-Filer/Help/TODO index b490e866..4d8fc571 100644 --- a/ROX-Filer/Help/TODO +++ b/ROX-Filer/Help/TODO @@ -21,6 +21,8 @@ directory in its new location until you click refresh. MISSING FEATURES +Pinboard selection becomes the primary selection. + Allow applets inside the panel? Allow the filer to go inside GNOME-panel and similar? @@ -36,14 +38,11 @@ More permissions checking in Find? Fully configurable toolbar. -Can't create a panel if the filer is already running (can use --new option). - -Use an environment variable to override the platform-name guessing in AppRun. - -People sometimes forget to save the key bindings - auto save? +Use an environment variable to override the platform-name guessing in +AppRun. -Enable (temporarily) single-click navigation when a special key (e.g. Alt) is -pressed? +Enable (temporarily) single-click navigation when a special key (e.g. Alt) +is pressed? Improve MIME-type guessing: - Try to find longest extension match (eg .ps.gz, then .gz) @@ -61,8 +60,6 @@ Display extra info in the toolbar (file under cursor, size, target mode, etc). More display styles. -Better pinboard features (dnd, popup menu). - RISC OS COMPATIBILITY @@ -81,6 +78,3 @@ Recursive 'a-x' acts on the directory first and so can't then change the permissions on the contents. chmod(1) has the same problem. Set Run Action should be on the 'File' menu, not in the minibuffer. - -Remove PERL script - the filer can do all this itself now with only minor -changes. Should speed things up. diff --git a/ROX-Filer/src/Docs/Manual.lyx b/ROX-Filer/src/Docs/Manual.lyx index 2763a94b..70e124ed 100644 --- a/ROX-Filer/src/Docs/Manual.lyx +++ b/ROX-Filer/src/Docs/Manual.lyx @@ -28,9 +28,19 @@ \layout Title ROX-Filer User Manual +\newline + +\latex latex + +\backslash +medskip +\backslash +large{} \layout Author -Thomas Leonard, tal197@users.sourceforge.net +Thomas Leonard, +\newline +tal197@users.sourceforge.net \layout Date @@ -44,6 +54,8 @@ ROX-Filer is a graphical file manger for the X Window System. Its user interface is based on the RISC OS filer and it supports similar features such as application directories and drag-and-drop loading and saving of files. + The filer can also act as a pinboard, allowing you to pin frequently used + files onto the desktop background. \layout Standard @@ -249,27 +261,88 @@ You can now run the filer by running the AppRun script without any options, $ ROX-Filer/AppRun \layout Standard -A window should appear and display the contents of your home directory. - If you opted to install the ` +A window should appear and display the contents of the current directory. + If you opted to install the `rox' script then you can simply do this instead: +\layout LyX-Code + +$ rox +\layout Standard + +If you installed the script into your home directory then you may need to + set your PATH environment variable so that the shell can find it. + For example, if you installed it into a directory called ` \family typewriter -rox +bin \family default -' script then you can simply run that to open a viewer onto the current - directory. - You can also use it to open files in the same way that the filer would. - If you installed the script into your home directory then you may need - to set your PATH environment variable so that the shell can find it. +' in your home directory, use this: +\layout LyX-Code + +$ PATH=${HOME}/bin:${PATH}; export PATH +\layout Standard + +or (if you are using the +\noun on +csh(1) +\noun default + shell): +\layout LyX-Code + +$ setenv PATH ${HOME}/bin:${PATH} +\layout LyX-Code + +$ rehash \layout Subsection Invoking \layout Standard -By default, ROX-Filer will start by displaying your home directory. +By default, ROX-Filer will start by displaying the current directory. You can get it to display other directories instead by listing them after the command: \layout LyX-Code -$ ROX-Filer/AppRun /home /usr /usr/local +$ rox /home /usr /usr/local +\layout Standard + +You can also use it to open files, like this: +\layout LyX-Code + +$ rox ~/.xsession +\layout Standard + +The filer supports various options, use ` +\family typewriter +-h +\family default +' for a list. + All options have long and short forms (eg ` +\family typewriter +-h +\family default +' and ` +\family typewriter +--help +\family default +') --- although on some systems you can only use the short versions. +\layout Standard + +Note that if the same version of the filer is already running on this machine + then, by default, it will be used to open the directories and the new copy + will exit immediately. + You can override this (perhaps because the old copy has stopped responding + for some reason) using the ` +\family typewriter +--new +\family default +' option. +\layout Subsubsection + + +\begin_inset LatexCommand \label{run_pin} + +\end_inset + +Pinboard support \layout Standard If you want the filer to manage your desktop background then you use the @@ -277,25 +350,43 @@ If you want the filer to manage your desktop background then you use the \family typewriter --pinboard \family default - option and supply a name for the pinboard: + option and supply a name for the pinboard, eg: \layout LyX-Code -$ ROX-Filer/AppRun --pinboard MyPinboard +$ rox --pinboard=MyPinboard \layout Standard The pinboard configuration is saved in ` \emph on /ROX-Filer/pb_MyPinboard \emph default -'. - You may have to use the -\family typewriter ---new -\family default - option when creating a pinboard (this will be fixed in later versions). +' as soon as you change it in some way (for example, by dropping a file + onto the background). + You can have as many pinboards as you like and switch between them by running + rox again, eg: +\layout LyX-Code + +$ rox --pinboard=MyOtherPinboard \layout Standard -If you want one of the directories to be opened as a panel put +To turn off the pinboard again, set the name to an empty string: +\layout LyX-Code + +$ rox --pinboard= +\layout Standard + +See section +\begin_inset LatexCommand \ref{winman} + +\end_inset + + if you have trouble getting the icons to display correctly. +\layout Subsubsection + +Panels +\layout Standard + +If you want a directory to be opened as a panel, put \family typewriter -b \family default @@ -304,8 +395,7 @@ If you want one of the directories to be opened as a panel put -t \family default before the directory (for `bottom' or `top' panels). - If you don't know what a panel is, try it and see! To run the filer in - the background, put an & at the end of the line. + If you don't know what a panel is, try it and see! \layout LyX-Code $ mkdir ~/Panel @@ -314,37 +404,60 @@ $ mkdir ~/Panel $ ln -s $HOME ~/Panel/Home \layout LyX-Code -$ ROX-Filer/AppRun -b ~/Panel & +$ rox -b ~/Panel \layout Standard The panel should be displayed in a window without a title bar. - If this does not work then you may need to update your window manager. - The filer was designed and tested using the Enlightenment window manager -\begin_inset LatexCommand \cite{enlightenment} + If this does not work then see section +\begin_inset LatexCommand \ref{winman} \end_inset -. - You could also try using the + for some ideas. +\layout Subsubsection + + +\begin_inset LatexCommand \label{winman} + +\end_inset + +Window manager notes +\layout Standard + +You may have to play around with your window manager a bit to get the pinboard + icons and panels to display correctly (eg, without borders and underneath + all other windows). + In particular, try setting the stacking level / depth to low (or a negative + value). + Make sure any 'Keep transients above other windows' type options are turned + off! +\layout Standard + +If order for the filer to receive mouse clicks on the background (used for + the pinboard support) you need a GNOME-compliant window manager. + To see if your window manager supports this, try clicking the right mouse + button on an unused area of the background. + If you get the pinboard menu, all is well. +\layout Standard + +Note for sawfish users: sawfish trys to guess whether you are using GNOME + at start-up and only provides support if so. + You need to add the line ` \family typewriter --o +(require 'gnome) \family default - option which bypasses the window manager entirely (also use this if the - panel appears in the wrong place): -\layout LyX-Code - -$ ROX-Filer/AppRun -o -b ~/Panel & +' to you +\family typewriter +.sawfishrc +\family default + file (see the sawfish manual for more details). \layout Standard -Note that if the same version of the filer is already running on this machine - then, by default, it will be used to open the directories and the new copy - will exit immediately. - You can override this (perhaps because the old copy has stopped responding - for some reason) using the ` +If all else fails, try running rox with the ` \family typewriter ---new +-no \family default -' option. +' options; this overrides window manager control of the icons altogether. \layout Subsection Mouse button and key bindings @@ -548,8 +661,7 @@ noindent Other keys can easily be defined by opening the menu, moving the pointer over the item you want to use and pressing a key. The key will appear in the menu and can be used from then on. - To make the new key bindings permanent, open the Options box from the menu - and click on Save. + Key bindings are automatically saved when the filer quits. \layout Subsection The selection @@ -1062,11 +1174,11 @@ Action \newline Copy... \newline -Make a copy of this object in the same directory. +Make a copy of this object. \newline Rename... \newline -Change the name used for this object. +Change the name used for this object, or move it between directories. \newline Link... \newline @@ -1264,6 +1376,45 @@ This is just a cut-down version of the window menu. it is very easy to change the panel contents to suit your needs. \layout Subsection +The pinboard +\layout Standard + +Section +\begin_inset LatexCommand \ref{run_pin} + +\end_inset + + explains how to turn the pinboard on. + Once on, you may drop icons from filer windows onto the pinboard to pin + them up. + Clicking on a pinned file acts just like clicking on it in a filer window. + You can drag pinned icons just like normal icons and you can right-click + on one to see the pinboard's popup menu. +\layout Standard + +Drag pinned icons with the middle mouse button to move them around (they + snap to a grid). + Changes to the pinboard are automatically saved. +\layout Standard + +Clicking on pinned icons with Control held down selects and unselects them. + Click on the desktop background to unselect them all. +\layout Standard + + +\series bold +IMPORTANT: +\series default +Pinning a file to the pinboard does +\series bold +not +\series default + copy it, it merely creates a shortcut to the original file. + If you delete the file, then you've lost it! Removing a file from the pinboard + only removes the link. + This is different to most other filers... +\layout Subsection + \begin_inset LatexCommand \label{sec: vfs} @@ -1475,6 +1626,10 @@ Up, Down Select the previous/next matching entry. \layout Standard +If you start entering a name beginning with a `.' then the `Show Hidden' + feature is temporarily turned on so that the file can be shown. +\layout Standard + Tab completion tries to fill in as many characters for you as it can. For example, if there are two files in a directory called `save-mail-nov-1999' and `save-mail-dec-1999' then typing 'save' and pressing Tab will expand @@ -2873,7 +3028,7 @@ detach() \layout Standard \added_space_top 0.3cm \added_space_bottom 0.3cm \align center -\begin_inset Figure size 595 618 +\begin_inset Figure size 595 606 file Structs.eps width 3 100.00 flags 9 diff --git a/ROX-Filer/src/Docs/Structs.dia b/ROX-Filer/src/Docs/Structs.dia dissimilarity index 100% index ce0d6fabdf2ef4879c96066a1682beb8e8d43ee6..50d0725cafadb4c86e51dc8eb00970d05213a9a6 100644 GIT binary patch literal 3555 zcwPbH4IJ_xiwFP!000001MQt%bK*D_hVT1Vq>`JSEh6;kk4ZXHGu=B=soJic+TE$S zbO|uT8)LJ!=}h)wfBVWnJ|GZmV-doHH?I=s9$>4Ne!j$Q!ahjcc)A6U%v*GN| zX?OAV?&JG6*qi7{m_0W&o}_tXUM}t@FJ4~r{_^i<5e!)15nal4H5eV8p;@YBWzX-uG}C(+>3>EG$o1Gd^xW5s+g`SV9? zl153f_~a;Ym8&}E4q=Xb{gJP-gDELX|Ya5ark7O&dqaX%wKRtn8*AMlf?5> z&FaE990&b0OO{8B*19vZe-{dho8>M&VkU`jrEFmICX7Zm#d@op)iby9z4E~TQQ#+HPVH7 zwpum6c)Y7LetCSXW4uDs>l0g zzej^nSe*Q2r#j3hIo(i6w~@W_`-|eJxL-9ICe!i!i=(HFOs{zoD=1xc`~6amg1n!; z{L~v&@j_57(q^UCk%I5rb-GY7B++vmqB zfEh>LI18svSpTzn;$cy{f-T?PyUdumst_0K_33X+ND9!FI z61S)U3seAib*8698suT=w15W=v{|NY93`gca2RHYV}ZDR5odRe`qk8cB`Ro}P2a*o zM{#Z!xG(ccz}&lHuF{6FwR=?7p!!&_IjYmiKo?pV%v3%Zg@IW!n6`vT0d~MnW&dqhSoNj>nj?kAYWy7393eGXILyNQ2FL)Js>m1< ziLp5bGhn9rY&h)S2IF*^6yX5i0G!&(C_n>f>P&^paGlj?9mqM-xe6RM$BIoA&$5}= zPGY?j+SvKn=7Y`rDciyPOm_VP+N|jTZO(|0l6AO8m~>>z=d+DC7g4= zBNS1-BilZny;Enlp3YXp@>1>zCc8+I%z5e>w^j`+nUY5p~mbQXnmW~6fnFJo^sJp@n zxDMJ~GQ+9#nH>0$ZD4fx7!_#{^(`ixXj4PM)4SkF7wOpE@rl9hkGt9jNbu{<{knVy zzpj+j=`clH*KgmgEBu9V?q3K-6lGqAOG3GC!KvH0CtH2Glu*Wa%RXI?@OFHN&M|puv6L7MXXa0j5oeXTInYjK39Bg3i>a5)1YH>`{S*GtX zr~$OIB)kg%8(`ZDY|{x?18WBzqB3Q59|w})?yZ$e0>prrx+xt%3?c!C7YPteBLR{U z*Yn&C_lfKHZQsCES{DoJ%;y)1o7#NV5k8^JZ~X$UojS8M8o-FJXxsO1`E5l5o=2(? z5I{iS{RITfk9 zPFCCmvzFKH%SthaNNE~Tu~?%ML}gpkEDDpQ7}193^4afM8=&%nlLi$AcRCde&&%L^}ZQr7?Q)jkr(cnZ1 z)%Jl+CR#hMdA%+JIyPrubH>4M&M@s9maii#8kUfjQS0eEw43V@z6yY5Fc`*qlmjwA zW_9CFIH2FlEMQ$!Y*0Htwo2F{Qe8NBUzT$_zNHkX{O zB{v3eN0Gn9yoQ}WAZ-un&%#1SaSqr3+a59nMFVSy8QWMK0J5vIkwwTxfDDk$;+=V+ ziy+LhB!y@KwUfh3c~*u=XLlAHd{`b>y;8+7W+{4oH@4GwBOD`AF|WgX!dt(!Ku|)< zYYL3*3nd7n}wdjt8^*>1Z1C*DiGx+Ppx-Ak^P-zaz7Oa8i$y1NRg(KpYU zkd~XV%L?VfBdXkB&N&hG#B{YQE2Sji%E)B3Qd=bO*%tQ0l0ZlrB< z6zvLDe~?5-A|w%#SWOaDLrG*-CDGsY;Be{2Z2#bJnXQ6)YUZF^TIc!1m5yv(6gPF| z#Tw3cWc6Z-NWtXBEElG4%Ji{nF^{ljU56|}79oqsp7CZ%z62St;fbpUbMHD3MFrwf>s17sU_&gp@(bAZ3RmWvuB&7SV3dZ&Ol4 z#+!LyvSf`$IQ7)k*;~g{fd$ewC+p{9>)S0)rxRMJZ+sTXg zIgR5vPDqW2)QIgE-)CyXiB7`U-ifruCxMefIFvTX^j#3G%OTqqZ6AlzWHuCk2j0M& z89s~o>KniTxP9b(GGm*)jGQ0MLQvlWG=O$yKHFZH1?e!biyV%41GiF0bv6o%px=Xz zg17zgHqeE(w=kVe48sh(A%#UlO#7x+nI%Ah&)cP{um((&P1b7ZXcULQ2G}$iZFC%i8c?o~LQ2 z&Q!?LBpv0noTo_;r=2uSU!v@{e9dt`Uri0JX)Yoy`)BRg(r(w<0@wtsUa3JWe=)xxfmyugeWfoirj*oCtYWEi zMg%oYeyqdZ-(8qj#{7kJ2~(7p8c9{{`YL!(cIwYI7m7!y%MaCo@@tgzQk@FmL4gP5{drKL zzP%G%S7#sNC@+w@4xW><@-=&&lWG4(XFJZb02(|e%{?a@r-*Ljt1T(vRQerdW^7!t zwy(CNW>ppHl!Tj(& z&JQQLxPfj%xA$^KfXy)OwXqM1M@Z%~pVj<|3Ml zXfC3;hjVGM(=?pxwj0jfB94AjaV&jk%wZjuxHdd?9-%&!MheB;V<(7TN@ICG%9EmP zY24VEm15i|vuQ=eVoVWVNb!n(Drx%oqS#w29+V2ohO=EzvuA3yDoU)XoT<%k+#+X} d-B{m;+0}>3?jX{mOpmWV{2!TVU~JK00RT3U0J;DG literal 3179 zcwPZ(43zU9iwFP!000001MQvPliD~E$KUf;P%|&tD^BcdW)Lk2KJJ!U-L2~%{+K4cFETIUEPa1L ziFNVA&712e7L!~|d-}z+_+J0?{^C}t+4qCN{rx>j9txqdoFws_6msxCktAZE8wxIN z-t>BpZ;pf#<&Z*ZAMv=<-IQ=d4F*^Ui`Tj ztlmC&djAf2Gcl9-OHtEVR>b;c^)TCdx#Ioh-!ER*EehQro!ngh$S_6qcR@I-Uv({Mf5c-*cN@gVcn2!48g(0X6uA|57k z_3^BPWy62Vkyh6 zJ~@kcj_35>xrDm><=0*{-zwR|;Y-oE9kNKfKQ<9wW9ZF=SC4qph|j0VQ^R$gy4RY&0QkO|-JLqU%C4`|yC(h@b@zK5O{D7m z<)A+FCpq8I$)J_I^81UESUqeCmFaxC{Nk*OR-!AO#2S{azWh;Xj>2M;ZGY;my4W(P z25HkviZs>#GsfFw^$`Fzz%~==));7`TaiPj0W(cPmLz%suGav@VEgdY1&{-B+Kk?2 z_u*|EMKXmY1#iZA8z*8?7$a@5hyMBCF2LHEtlCnUgr!7d!2Mcw@vlRB0B`4EOS4>s zV{>-&?RcIpmTupHHqaKu#VipIVWA!pa|-U`vX_4z+zF^V6Y9z%qAY(fNZhIiY)}E* z&83*DEG%T{w15W=v>B#t8mGGHcr0_AV}ZD15odOf`dRmYEh=bREZ?LOaZ;EC?&Go( zF!xO{S1VwwGDf8W)u)E-QJv2sp=78x9ELyzL!Eaq6S@9Z$-Gz(y{xI=I-o``<Db2=696`}3#If2}??Lvl8fvrj%bdn%(?m_7DI6cwTjRvUr&5Tt>$ zBajwP#l&Fblql|G^lSXbRLsB|Vp&>aTFMH?c{U1-RuXtxqaF$?;5un{$pWX+XL8~x z+ra4bF;ZC=j|>)^=+cIQr*DEMp|YvD^oi*1$3x`prrrX?Lf3{C=0??|N#ZfZaoC^MRiHwv*%18_$L4ofxQc0}B;S_5at;0y~kfYvsCV{q!{Vm}r|Hj0-A zyxXT^ymg+8L#R!IJ|ltcP`~R$8JoDimQD%xwe&0I5kGKQpL)b@oRyQS+i)@R#&g+d z<`T~JAZj*il|fXFHOpg}uALF>crIW3o_29m)=0$i!npOYf$D+QItomq>T{G=)j_VG zrVbV3be^bqmc*k9)p&qdk>}aHDUB#5lUz=Ol85Q?Jr?&*@hz;M0iMQ}Q1$GLvNT;j zdd1pz_4wP~GN)sqEcssIQTWB7WTh7jk8w0qrsNbSrL*x%g zJ3{$0sYIL4Exz6CSb^VNwMPIs z0?5xhfc)n2FuVOHjwRm=3AgLUk~`~fBFrMHXM8nNxM9&4t7{7BF-;-0THkZ>cy1N6aoX*@ z=foj4wSzwOmp)I25ucX#oKRvnzg1TAZfn&AKtrVOSGrtEY~8EqTLVt0G!x2q^3Wo?a^ zM*?nn&Z1bZw74zi&0|ZGTn7gC_HJF0N!Y;oa0l{E#Kd z5+{^KL}|osgdejs;!LD6F_)3H`XrDkgru}67x!UYRYSHb+TM=m=|UBM2j0M&R-bCQ z`v!0T?ih8Sv~Dw3k@KzI2#N=Q2GB0`XFHU6n2kfT$RWiWxRr)f>DKdD70ZfBifa5g6x(3iRHcuwwz5&e{LNk%@ z4uhHIie@y=^`Zvo0G&qY+{fu1paC=ruZv7{|6x1^G=QdAxG9JMF|{w;&c4Xix(3+b zs}WdQuSK3ssw_HgDBI4GELvcw{W&)nyig7O}uvX zUDj>M!JT!QcYpvlyz z)5KlJ?YK^pP26tEG<}To--?QKKh$VKji!^Y(KHfCG8CgbW7)9!>G;q^A4rHjk4jC* zyas6PGp`5VxZA`z_=show_hidden == hidden) diff --git a/ROX-Filer/src/filer.c b/ROX-Filer/src/filer.c index 08d8ee43..22a418fa 100644 --- a/ROX-Filer/src/filer.c +++ b/ROX-Filer/src/filer.c @@ -900,6 +900,7 @@ FilerWindow *filer_opendir(char *path, PanelType panel_type) filer_window->minibuffer = NULL; filer_window->minibuffer_label = NULL; filer_window->minibuffer_area = NULL; + filer_window->temp_show_hidden = FALSE; filer_window->path = real_path; filer_window->scanning = FALSE; filer_window->had_cursor = FALSE; @@ -961,7 +962,7 @@ FilerWindow *filer_opendir(char *path, PanelType panel_type) drag_data_get, NULL); gtk_signal_connect(GTK_OBJECT(collection), "selection_clear_event", GTK_SIGNAL_FUNC(collection_lose_selection), NULL); - gtk_signal_connect (GTK_OBJECT(collection), "selection_get", + gtk_signal_connect(GTK_OBJECT(collection), "selection_get", GTK_SIGNAL_FUNC(selection_get), NULL); gtk_selection_add_targets(collection, GDK_SELECTION_PRIMARY, target_table, diff --git a/ROX-Filer/src/filer.h b/ROX-Filer/src/filer.h index 6fb2c74a..4374c0e4 100644 --- a/ROX-Filer/src/filer.h +++ b/ROX-Filer/src/filer.h @@ -62,6 +62,11 @@ struct _FilerWindow GtkWidget *minibuffer; /* The text entry */ int mini_cursor_base; MiniType mini_type; + + /* TRUE if hidden files are shown because the minibuffer leafname + * starts with a dot. + */ + gboolean temp_show_hidden; }; extern FilerWindow *window_with_focus; diff --git a/ROX-Filer/src/gui_support.c b/ROX-Filer/src/gui_support.c index ee813640..83fa1f57 100644 --- a/ROX-Filer/src/gui_support.c +++ b/ROX-Filer/src/gui_support.c @@ -180,6 +180,9 @@ void set_cardinal_property(GdkWindow *window, GdkAtom prop, guint32 value) GDK_PROP_MODE_REPLACE, (gchar *) &value, 1); } +/* NB: Also used for pinned icons. + * TODO: Set the level here too. + */ void make_panel_window(GdkWindow *window) { static gboolean need_init = TRUE; @@ -279,8 +282,12 @@ gboolean load_file(char *pathname, char **data_out, long *length_out) if (ferror(file)) { - delayed_error(_("Error reading file"), - g_strerror(errno)); + guchar *tmp; + + tmp = g_strdup_printf("%s: %s\n", + pathname, g_strerror(errno)); + delayed_error(_("Error reading file"), tmp); + g_free(tmp); g_free(buffer); } else @@ -465,3 +472,92 @@ gboolean setup_xdnd_proxy(guint32 xid, GdkWindow *proxy_window) return !proxy; } +/* xid is the window (usually the root) which points to the proxy */ +void release_xdnd_proxy(guint32 xid) +{ + GdkAtom xdnd_proxy_atom; + + xdnd_proxy_atom = gdk_atom_intern("XdndProxy", FALSE); + + XDeleteProperty(GDK_DISPLAY(), xid, xdnd_proxy_atom); +} + +/* Looks for the proxy window to get root window clicks from the window + * manager. Taken from gmc. NULL if there is no proxy window. + */ +GdkWindow *find_click_proxy_window(void) +{ + GdkAtom click_proxy_atom; + Atom type; + int format; + unsigned long nitems, after; + Window *proxy_data; + Window proxy; + guint32 old_warnings; + GdkWindow *proxy_gdk_window; + + XGrabServer(GDK_DISPLAY()); + + click_proxy_atom = gdk_atom_intern("_WIN_DESKTOP_BUTTON_PROXY", FALSE); + type = None; + proxy = None; + + old_warnings = gdk_error_warnings; + + gdk_error_code = 0; + gdk_error_warnings = 0; + + /* Check if the proxy window exists */ + + XGetWindowProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(), + click_proxy_atom, 0, + 1, False, AnyPropertyType, + &type, &format, &nitems, &after, + (guchar **) &proxy_data); + + if (type != None) + { + if (format == 32 && nitems == 1) + proxy = *proxy_data; + + XFree(proxy_data); + } + + /* If the property was set, check if the window it points to exists + * and has a _WIN_DESKTOP_BUTTON_PROXY property pointing to itself. + */ + + if (proxy) + { + XGetWindowProperty(GDK_DISPLAY(), proxy, + click_proxy_atom, 0, + 1, False, AnyPropertyType, + &type, &format, &nitems, &after, + (guchar **) &proxy_data); + + if (!gdk_error_code && type != None) + { + if (format == 32 && nitems == 1) + if (*proxy_data != proxy) + proxy = GDK_NONE; + + XFree(proxy_data); + } + else + proxy = GDK_NONE; + } + + gdk_error_code = 0; + gdk_error_warnings = old_warnings; + + XUngrabServer(GDK_DISPLAY()); + gdk_flush(); + + if (proxy) + proxy_gdk_window = gdk_window_foreign_new(proxy); + else + proxy_gdk_window = NULL; + + return proxy_gdk_window; +} + diff --git a/ROX-Filer/src/gui_support.h b/ROX-Filer/src/gui_support.h index 4d912d02..a5276336 100644 --- a/ROX-Filer/src/gui_support.h +++ b/ROX-Filer/src/gui_support.h @@ -39,5 +39,7 @@ gboolean load_file(char *pathname, char **data_out, long *length_out); GtkWidget *new_help_button(HelpFunc show_help, gpointer data); void parse_file(char *path, ParseFunc *parse_line); gboolean setup_xdnd_proxy(guint32 xid, GdkWindow *proxy_window); +void release_xdnd_proxy(guint32 xid); +GdkWindow *find_click_proxy_window(void); #endif /* _GUI_SUPPORT_H */ diff --git a/ROX-Filer/src/main.c b/ROX-Filer/src/main.c index 4d7b8ef1..a8c06ea9 100644 --- a/ROX-Filer/src/main.c +++ b/ROX-Filer/src/main.c @@ -55,6 +55,7 @@ #include "i18n.h" #include "remote.h" #include "pinboard.h" +#include "run.h" int number_of_windows = 0; /* Quit when this reaches 0 again... */ int to_error_log = -1; /* Write here to log errors */ @@ -88,9 +89,9 @@ static void show_features(void); "you must use the short versions instead.\n\n") #endif -#define HELP N_("Usage: ROX-Filer/AppRun [OPTION]... [DIR]...\n" \ - "Open filer windows showing each directory listed, or $HOME \n" \ - "if no directories are given.\n\n" \ +#define HELP N_("Usage: ROX-Filer/AppRun [OPTION]... [FILE]...\n" \ + "Open each directory or file listed, or the current working\n" \ + "directory if no arguments are given.\n\n" \ " -b, --bottom=DIR open DIR as a bottom-edge panel\n" \ " -h, --help display this help and exit\n" \ " -n, --new start a new filer, even if already running\n" \ @@ -98,7 +99,9 @@ static void show_features(void); " -p, --pinboard=PIN use pinboard PIN as the pinboard\n" \ " -t, --top=DIR open DIR as a top-edge panel\n" \ " -v, --version display the version information and exit\n" \ - "\nReport bugs to .\n") + "\nThe latest version can be found at:\n" \ + "\thttp://rox.sourceforge.net\n" \ + "\nReport bugs to .\n") #define SHORT_OPS "t:b:op:hvn" @@ -210,13 +213,28 @@ void stderr_cb(gpointer data, gint source, GdkInputCondition condition) } } +/* The value that goes with an option */ +#define VALUE (*optarg == '=' ? optarg + 1 : optarg) + int main(int argc, char **argv) { int stderr_pipe[2]; + int i; struct sigaction act; - GList *panel_dirs = NULL; - GList *panel_sides = NULL; - guchar *pinboard = NULL; + guchar *tmp; + + /* This is a list of \0 separated strings. Each string starts with a + * character indicating what kind of operation to perform: + * + * fFILE open this file (or directory) + * dDIR open this dir (even if it looks like an app) + * pPIN display this pinboard + * tDIR open DIR as a top-panel + * bDIR open DIR as a bottom-panel + */ + GString *to_open; + + to_open = g_string_new(NULL); choices_init(); i18n_init(); @@ -251,10 +269,6 @@ int main(int argc, char **argv) case 'o': override_redirect = TRUE; break; - case 'p': - pinboard = *optarg == '=' ? optarg + 1 - : optarg; - break; case 'v': fprintf(stderr, "ROX-Filer %s\n", VERSION); fprintf(stderr, _(COPYING)); @@ -266,20 +280,43 @@ int main(int argc, char **argv) return EXIT_SUCCESS; case 't': case 'b': - panel_sides = g_list_prepend(panel_sides, - (gpointer) c); - panel_dirs = g_list_prepend(panel_dirs, - *optarg == '=' ? optarg + 1 - : optarg); + g_string_append_c(to_open, '<'); + g_string_append_c(to_open, c); + g_string_append_c(to_open, '>'); + tmp = pathdup(VALUE); + g_string_append(to_open, tmp); + g_free(tmp); + break; + case 'p': + g_string_append(to_open, "

"); + g_string_append(to_open, VALUE); break; default: printf(_(USAGE)); return EXIT_FAILURE; } } + + i = optind; + while (i < argc) + { + tmp = pathdup(argv[i++]); + + g_string_append(to_open, ""); + g_string_append(to_open, tmp); + } + + if (to_open->len == 0) + { + guchar *dir; + + dir = g_get_current_dir(); + g_string_sprintf(to_open, "%s", dir); + g_free(dir); + } gui_support_init(); - if (remote_init(argc - optind, argv + optind, new_copy)) + if (remote_init(to_open, new_copy)) return EXIT_SUCCESS; /* Already running */ pixmaps_init(); @@ -328,41 +365,17 @@ int main(int argc, char **argv) exit(EXIT_SUCCESS); } - if (pinboard) - pinboard_activate(pinboard); - - if (optind == argc && (!panel_dirs) && !pinboard) - filer_opendir(home_dir, PANEL_NO); - else - { - int i = optind; - GList *dir = panel_dirs; - GList *side = panel_sides; - - while (dir) - { - int c = (int) side->data; - - filer_opendir((char *) dir->data, - c == 't' ? PANEL_TOP : PANEL_BOTTOM); - dir = dir->next; - side = side->next; - } - - g_list_free(dir); - g_list_free(side); - - while (i < argc) - filer_opendir(argv[i++], PANEL_NO); - } - + run_list(to_open->str); + g_string_free(to_open, TRUE); + pipe(stderr_pipe); close_on_exec(stderr_pipe[0], TRUE); close_on_exec(stderr_pipe[1], TRUE); gdk_input_add(stderr_pipe[0], GDK_INPUT_READ, stderr_cb, NULL); to_error_log = stderr_pipe[1]; - gtk_main(); + if (number_of_windows > 0) + gtk_main(); return EXIT_SUCCESS; } diff --git a/ROX-Filer/src/menu.c b/ROX-Filer/src/menu.c index b49db697..88fda8a3 100644 --- a/ROX-Filer/src/menu.c +++ b/ROX-Filer/src/menu.c @@ -35,6 +35,7 @@ #include +#include "menu.h" #include "run.h" #include "action.h" #include "filer.h" @@ -54,6 +55,7 @@ GtkAccelGroup *filer_keys; GtkAccelGroup *panel_keys; +GtkAccelGroup *pinboard_keys; static GtkWidget *popup_menu = NULL; /* Currently open menu */ @@ -63,9 +65,14 @@ static gint updating_menu = 0; /* Non-zero => ignore activations */ static GtkWidget *xterm_here_entry; static char *xterm_here_value; +/* TRUE if we selected an icon automatically when the menu was opened */ +static gboolean pin_temp_item_selected; + /* Static prototypes */ +static void save_menus(void); static void position_menu(GtkMenu *menu, gint *x, gint *y, gpointer data); +static void pin_menu_closed(GtkWidget *widget); static void menu_closed(GtkWidget *widget); static void items_sensitive(gboolean state); static char *load_xterm_here(char *data); @@ -127,6 +134,11 @@ static void rox_help(gpointer data, guint action, GtkWidget *widget); static void open_as_dir(gpointer data, guint action, GtkWidget *widget); static void close_panel(gpointer data, guint action, GtkWidget *widget); +static void pin_help(gpointer data, guint action, GtkWidget *widget); +static void pin_remove(gpointer data, guint action, GtkWidget *widget); + +static void set_items_shaded(GtkWidget *menu, gboolean shaded, int from, int n); + static GtkWidget *create_options(); static void update_options(); static void set_options(); @@ -150,6 +162,7 @@ static GtkWidget *filer_hidden_menu; /* The Show Hidden item */ static GtkWidget *filer_new_window; /* The New Window item */ static GtkWidget *panel_menu; /* The popup panel menu */ static GtkWidget *panel_hidden_menu; /* The Show Hidden item */ +static GtkWidget *pinboard_menu; /* The popup pinboard menu */ /* Used for Copy, etc */ static GtkWidget *savebox = NULL; @@ -234,6 +247,15 @@ static GtkItemFactoryEntry panel_menu_def[] = { {N_("Remove Item"), NULL, remove_link, 0, NULL}, }; +static GtkItemFactoryEntry pinboard_menu_def[] = { +{N_("Show Help"), NULL, pin_help, 0, NULL}, +{N_("Remove Item(s)"), NULL, pin_remove, 0, NULL}, +{"", NULL, NULL, 0, ""}, +{N_("ROX-Filer Help"), NULL, rox_help, 0, NULL}, +{N_("ROX-Filer Options..."), NULL, show_options, 0, NULL}, +}; + + typedef struct _FileStatus FileStatus; /* This is for the 'file(1) says...' thing */ @@ -262,26 +284,35 @@ struct _FileStatus g_free(tmp); \ } while (0) +/* Creates menu from the _menu_def array. + * The accel group _keys must also have been created. + * All menu items are translated. Sets 'item_factory'. + */ +#define MAKE_MENU(name) \ +do { \ + GtkItemFactoryEntry *translated; \ + int n_entries; \ + \ + item_factory = gtk_item_factory_new(GTK_TYPE_MENU, \ + "<" #name ">", name ## _keys); \ + \ + n_entries = sizeof(name ## _menu_def) / sizeof(*name ## _menu_def); \ + translated = translate_entries(name ## _menu_def, n_entries); \ + gtk_item_factory_create_items (item_factory, n_entries, \ + translated, NULL); \ + free_translated_entries(translated, n_entries); \ +} while (0) + void menu_init() { - GtkItemFactory *item_factory; char *menurc; GList *items; guchar *tmp; GtkWidget *item; - int n_entries; - GtkItemFactoryEntry *translated; + GtkItemFactory *item_factory; \ filer_keys = gtk_accel_group_new(); - item_factory = gtk_item_factory_new(GTK_TYPE_MENU, - "", - filer_keys); - - n_entries = sizeof(filer_menu_def) / sizeof(*filer_menu_def); - translated = translate_entries(filer_menu_def, n_entries); - gtk_item_factory_create_items (item_factory, n_entries, - translated, NULL); - free_translated_entries(translated, n_entries); + MAKE_MENU(filer); GET_MENU_ITEM(filer_menu, "filer"); GET_SMENU_ITEM(filer_file_menu, "filer", "File"); @@ -296,15 +327,7 @@ void menu_init() filer_new_window = GTK_BIN(item)->child; panel_keys = gtk_accel_group_new(); - item_factory = gtk_item_factory_new(GTK_TYPE_MENU, - "", - panel_keys); - - n_entries = sizeof(panel_menu_def) / sizeof(*panel_menu_def); - translated = translate_entries(panel_menu_def, n_entries); - gtk_item_factory_create_items (item_factory, n_entries, - translated, NULL); - free_translated_entries(translated, n_entries); + MAKE_MENU(panel); GET_MENU_ITEM(panel_menu, "panel"); GET_SSMENU_ITEM(panel_hidden_menu, "panel", "Display", "Show Hidden"); @@ -314,7 +337,14 @@ void menu_init() gtk_item_factory_parse_rc(menurc); gtk_accel_group_lock(panel_keys); + + pinboard_keys = gtk_accel_group_new(); + MAKE_MENU(pinboard); + gtk_accel_group_lock(pinboard_keys); + GET_MENU_ITEM(pinboard_menu, "pinboard"); + gtk_signal_connect(GTK_OBJECT(pinboard_menu), "unmap_event", + GTK_SIGNAL_FUNC(pin_menu_closed), NULL); gtk_signal_connect(GTK_OBJECT(filer_menu), "unmap_event", GTK_SIGNAL_FUNC(menu_closed), NULL); gtk_signal_connect(GTK_OBJECT(panel_menu), "unmap_event", @@ -332,6 +362,8 @@ void menu_init() gtk_signal_connect_object(GTK_OBJECT(savebox), "save_done", GTK_SIGNAL_FUNC(gtk_widget_hide), GTK_OBJECT(savebox)); + + atexit(save_menus); } /* Build up some option widgets to go in the options dialog, but don't @@ -345,13 +377,11 @@ static GtkWidget *create_options() gtk_container_set_border_width(GTK_CONTAINER(table), 4); label = gtk_label_new( - _("To set the keyboard short-cuts you simply open " + _("To set the keyboard short-cuts, simply open " "the menu over a filer window, move the pointer over " "the item you want to use and press a key. The key " "will appear next to the menu item and you can just " - "press that key without opening the menu in future. " - "To save the current menu short-cuts for next time, " - "click the Save button at the bottom of this window.")); + "press that key without opening the menu in future.")); gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 2, 0, 1); @@ -385,12 +415,7 @@ static void set_options() static void save_options() { - char *menurc; - - menurc = choices_find_path_save("menus", PROJECT, TRUE); - if (menurc) - gtk_item_factory_dump_rc(menurc, NULL, TRUE); - + save_menus(); option_write("xterm_here", xterm_here_value); } @@ -442,6 +467,44 @@ static void position_menu(GtkMenu *menu, gint *x, gint *y, gpointer data) *y = CLAMP(*y, 0, screen_height - requisition.height); } +/* Display the pinboard menu. Set icon to NULL if no particular icon + * was clicked. + */ +void show_pinboard_menu(GdkEventButton *event, PinIcon *icon) +{ + int pos[2]; + GList *icons; + + if (icon) + { + if (pinboard_is_selected(icon)) + pin_temp_item_selected = FALSE; + else + { + pinboard_select_only(icon); + pin_temp_item_selected = TRUE; + } + } + + icons = pinboard_get_selected(); + + pos[0] = event->x_root; + pos[1] = event->y_root; + + if (icons) + { + set_items_shaded(pinboard_menu, + icons->next ? TRUE : FALSE , 0, 1); + + set_items_shaded(pinboard_menu, FALSE, 1, 1); + } + else + set_items_shaded(pinboard_menu, TRUE, 0, 2); + + gtk_menu_popup(GTK_MENU(pinboard_menu), NULL, NULL, position_menu, + (gpointer) pos, event->button, event->time); +} + void show_filer_menu(FilerWindow *filer_window, GdkEventButton *event, int item) { @@ -536,6 +599,12 @@ void show_filer_menu(FilerWindow *filer_window, GdkEventButton *event, (gpointer) pos, event->button, event->time); } +static void pin_menu_closed(GtkWidget *widget) +{ + if (pin_temp_item_selected) + pinboard_clear_selection(); +} + static void menu_closed(GtkWidget *widget) { if (window_with_focus == NULL || widget != popup_menu) @@ -1173,23 +1242,6 @@ static void show_file_info(gpointer data, guint action, GtkWidget *widget) } } -static void app_show_help(char *path) -{ - char *help_dir; - struct stat info; - - help_dir = g_strconcat(path, "/Help", NULL); - - if (mc_stat(help_dir, &info)) - delayed_error(_("Application"), - _("This is an application directory - you can " - "run it as a program, or open it (hold down " - "Shift while you open it). Most applications provide " - "their own help here, but this one doesn't.")); - else - filer_opendir(help_dir, PANEL_NO); -} - static void help(gpointer data, guint action, GtkWidget *widget) { Collection *collection; @@ -1210,59 +1262,9 @@ static void help(gpointer data, guint action, GtkWidget *widget) return; } item = selected_item(collection); - switch (item->base_type) - { - case TYPE_FILE: - if (item->flags & ITEM_FLAG_EXEC_FILE) - delayed_error(_("Executable file"), - _("This is a file with an eXecute bit " - "set - it can be run as a program.")); - else - delayed_error(_("File"), _( - "This is a data file. Try using the " - "Info menu item to find out more...")); - break; - case TYPE_DIRECTORY: - if (item->flags & ITEM_FLAG_APPDIR) - app_show_help( - make_path(window_with_focus->path, - item->leafname)->str); - else if (item->flags & ITEM_FLAG_MOUNT_POINT) - delayed_error(_("Mount point"), _( - "A mount point is a directory which another " - "filing system can be mounted on. Everything " - "on the mounted filesystem then appears to be " - "inside the directory.")); - else - delayed_error(_("Directory"), _( - "This is a directory. It contains an index to " - "other items - open it to see the list.")); - break; - case TYPE_CHAR_DEVICE: - case TYPE_BLOCK_DEVICE: - delayed_error(_("Device file"), _( - "Device files allow you to read from or write " - "to a device driver as though it was an " - "ordinary file.")); - break; - case TYPE_PIPE: - delayed_error(_("Named pipe"), _( - "Pipes allow different programs to " - "communicate. One program writes data to the " - "pipe while another one reads it out again.")); - break; - case TYPE_SOCKET: - delayed_error(_("Socket"), _( - "Sockets allow processes to communicate.")); - break; - default: - delayed_error(_("Unknown type"), _( - "I couldn't find out what kind of file this " - "is. Maybe it doesn't exist anymore or you " - "don't have search permission on the directory " - "it's in?")); - break; - } + + show_item_help(make_path(window_with_focus->path, item->leafname)->str, + item); } #define OPEN_VFS(fs) \ @@ -1325,9 +1327,7 @@ static void clear_selection(gpointer data, guint action, GtkWidget *widget) static void show_options(gpointer data, guint action, GtkWidget *widget) { - g_return_if_fail(window_with_focus != NULL); - - options_show(window_with_focus); + options_show(); } static gboolean new_directory_cb(guchar *initial, guchar *path) @@ -1424,10 +1424,8 @@ static void select_if(gpointer data, guint action, GtkWidget *widget) minibuffer_show(window_with_focus, MINI_SELECT_IF); } -static void rox_help(gpointer data, guint action, GtkWidget *widget) +void rox_help(gpointer data, guint action, GtkWidget *widget) { - g_return_if_fail(window_with_focus != NULL); - filer_opendir(make_path(getenv("APP_DIR"), "Help")->str, PANEL_NO); } @@ -1481,3 +1479,47 @@ static void free_paths(GList *paths) g_list_free(paths); } + +static void pin_help(gpointer data, guint action, GtkWidget *widget) +{ + PinIcon *icon; + + icon = pinboard_selected_icon(); + + if (icon) + pinboard_show_help(icon); + else + delayed_error(PROJECT, + _("You must first select a single pinned icon to get " + "help on.")); +} + +static void pin_remove(gpointer data, guint action, GtkWidget *widget) +{ + pinboard_unpin_selection(); +} + +/* Set n items from position 'from' in 'menu' to the 'shaded' state */ +static void set_items_shaded(GtkWidget *menu, gboolean shaded, int from, int n) +{ + GList *items, *item; + + items = gtk_container_children(GTK_CONTAINER(menu)); + + item = g_list_nth(items, from); + while (item && n--) + { + gtk_widget_set_sensitive(GTK_BIN(item->data)->child, !shaded); + item = item->next; + } + g_list_free(items); +} + +static void save_menus(void) +{ + char *menurc; + + menurc = choices_find_path_save("menus", PROJECT, TRUE); + if (menurc) + gtk_item_factory_dump_rc(menurc, NULL, TRUE); +} diff --git a/ROX-Filer/src/menu.h b/ROX-Filer/src/menu.h index 74e66c7d..3bbcb1d0 100644 --- a/ROX-Filer/src/menu.h +++ b/ROX-Filer/src/menu.h @@ -9,6 +9,7 @@ #define _MENU_H #include "filer.h" +#include "pinboard.h" extern GtkAccelGroup *filer_keys; @@ -19,6 +20,7 @@ void menu_update_options(void); void menu_set_options(void); void menu_save_options(void); +void show_pinboard_menu(GdkEventButton *event, PinIcon *icon); void show_filer_menu(FilerWindow *filer_window, GdkEventButton *event, int item); #endif /* _MENU_H */ diff --git a/ROX-Filer/src/minibuffer.c b/ROX-Filer/src/minibuffer.c index ff7c9c35..4d4ad399 100644 --- a/ROX-Filer/src/minibuffer.c +++ b/ROX-Filer/src/minibuffer.c @@ -116,6 +116,11 @@ void minibuffer_show(FilerWindow *filer_window, MiniType mini_type) MAX(collection->cursor_item, 0); gtk_entry_set_text(mini, make_path(filer_window->path, "")->str); + if (filer_window->temp_show_hidden) + { + display_set_hidden(filer_window, FALSE); + filer_window->temp_show_hidden = FALSE; + } break; case MINI_SHELL: case MINI_RUN_ACTION: @@ -145,6 +150,12 @@ void minibuffer_hide(FilerWindow *filer_window) gtk_widget_hide(filer_window->minibuffer_area); gtk_window_set_focus(GTK_WINDOW(filer_window->window), GTK_WIDGET(filer_window->collection)); + + if (filer_window->temp_show_hidden) + { + display_set_hidden(filer_window, FALSE); + filer_window->temp_show_hidden = FALSE; + } } /* Insert this leafname at the cursor (replacing the selection, if any). @@ -369,6 +380,20 @@ static void path_changed(GtkEditable *mini, FilerWindow *filer_window) Collection *collection = filer_window->collection; int item; + if (slash[1] == '.') + { + if (!filer_window->show_hidden) + { + filer_window->temp_show_hidden = TRUE; + display_set_hidden(filer_window, TRUE); + } + } + else if (filer_window->temp_show_hidden) + { + display_set_hidden(filer_window, FALSE); + filer_window->temp_show_hidden = FALSE; + } + find_next_match(filer_window, slash + 1, 0); item = collection->cursor_item; if (item != -1 && !matches(collection, item, slash + 1)) diff --git a/ROX-Filer/src/options.c b/ROX-Filer/src/options.c index a37da8b8..284fb2a2 100644 --- a/ROX-Filer/src/options.c +++ b/ROX-Filer/src/options.c @@ -244,7 +244,7 @@ static void save_options(GtkWidget *widget, gpointer data) gtk_widget_hide(window); } -void options_show(FilerWindow *filer_window) +void options_show(void) { GSList *next = options_sections; diff --git a/ROX-Filer/src/options.h b/ROX-Filer/src/options.h index c9917b3e..1fe693f4 100644 --- a/ROX-Filer/src/options.h +++ b/ROX-Filer/src/options.h @@ -12,8 +12,6 @@ typedef char *OptionFunc(char *value); typedef struct _OptionsSection OptionsSection; -#include "filer.h" - struct _OptionsSection { char *name; @@ -30,7 +28,7 @@ extern GSList *options_sections; void options_init(void); void option_register(char *key, OptionFunc *func); void options_load(void); -void options_show(FilerWindow *filer_window); +void options_show(void); void option_write(char *name, char *value); #endif /* _OPTIONS_H */ diff --git a/ROX-Filer/src/pinboard.c b/ROX-Filer/src/pinboard.c index ea02f8a3..ee5e46bf 100644 --- a/ROX-Filer/src/pinboard.c +++ b/ROX-Filer/src/pinboard.c @@ -35,8 +35,10 @@ #include "pinboard.h" #include "type.h" #include "choices.h" +#include "support.h" #include "gui_support.h" #include "run.h" +#include "menu.h" /* The number of pixels between the bottom of the image and the top * of the text. @@ -46,12 +48,19 @@ /* The size of the border around the icon which is used when winking */ #define WINK_FRAME 2 +enum +{ + TARGET_STRING, + TARGET_URI_LIST, +}; + struct _PinIcon { GtkWidget *win, *paper; GdkBitmap *mask; guchar *path; DirItem item; int x, y; + gboolean selected; }; struct _Pinboard { @@ -67,6 +76,8 @@ static gint loading_pinboard = 0; /* Non-zero => loading */ static PinIcon *current_wink_icon = NULL; static gint wink_timeout; +static gint number_selected = 0; + static GdkColor mask_solid = {1, 1, 1, 1}; static GdkColor mask_transp = {0, 0, 0, 0}; static GdkGC *mask_gc = NULL; @@ -74,6 +85,12 @@ static GdkGC *mask_gc = NULL; /* Proxy window for DnD and clicks on the desktop */ static GtkWidget *proxy_invisible; +/* The window (owned by the wm) which root clicks are forwarded to. + * NULL if wm does not support forwarding clicks. + */ +static GdkWindow *click_proxy_gdk_window = NULL; +static GdkAtom win_button_proxy; /* _WIN_DESKTOP_BUTTON_PROXY */ + static gint drag_start_x, drag_start_y; /* If the drag type is not DRAG_NONE then there is a grab in progress */ typedef enum { @@ -93,6 +110,15 @@ static gint end_wink(gpointer data); static gboolean button_release_event(GtkWidget *widget, GdkEventButton *event, PinIcon *icon); +static gboolean root_property_event(GtkWidget *widget, + GdkEventProperty *event, + gpointer data); +static gboolean root_button_press(GtkWidget *widget, + GdkEventButton *event, + gpointer data); +static gboolean enter_notify(GtkWidget *widget, + GdkEventCrossing *event, + PinIcon *icon); static gboolean button_press_event(GtkWidget *widget, GdkEventButton *event, PinIcon *icon); @@ -121,6 +147,16 @@ static void drag_leave(GtkWidget *widget, GdkDragContext *context, guint32 time, PinIcon *icon); +static void icon_may_update(PinIcon *icon); +static void forward_root_clicks(void); +static void change_number_selected(int delta); +static gint lose_selection(GtkWidget *widget, GdkEventSelection *event); +static void selection_get(GtkWidget *widget, + GtkSelectionData *selection_data, + guint info, + guint time, + gpointer data); + /**************************************************************** @@ -138,6 +174,10 @@ void pinboard_activate(guchar *name) Pinboard *old_board = current_pinboard; guchar *path, *slash; + /* Treat an empty name the same as NULL */ + if (!*name) + name = NULL; + if (old_board) { pinboard_clear(); @@ -151,24 +191,14 @@ void pinboard_activate(guchar *name) return; } - number_of_windows++; - - if (!proxy_invisible) - { - proxy_invisible = gtk_invisible_new(); - gtk_widget_show(proxy_invisible); - gdk_window_add_filter(proxy_invisible->window, - proxy_filter, NULL); - gdk_window_add_filter(GDK_ROOT_PARENT(), - proxy_filter, NULL); - } - if (!add_root_handlers()) { delayed_error(PROJECT, _("Another application is already " "managing the pinboard!")); return; } + + number_of_windows++; slash = strchr(name, '/'); if (slash) @@ -214,6 +244,7 @@ void pinboard_pin(guchar *path, guchar *name, int x, int y) path_len--; icon = g_new(PinIcon, 1); + icon->selected = FALSE; icon->path = g_strndup(path, path_len); icon->mask = NULL; snap_to_grid(&x, &y); @@ -243,13 +274,13 @@ void pinboard_pin(guchar *path, guchar *name, int x, int y) drag_data_get, NULL); gtk_widget_realize(icon->win); - gdk_window_set_decorations(icon->win->window, 0); - gdk_window_set_functions(icon->win->window, 0); if (override_redirect) { gdk_window_lower(icon->win->window); gdk_window_set_override_redirect(icon->win->window, TRUE); } + else + make_panel_window(icon->win->window); set_size_and_shape(icon, &width, &height); offset_from_centre(icon, width, height, &x, &y); @@ -257,8 +288,10 @@ void pinboard_pin(guchar *path, guchar *name, int x, int y) gtk_widget_add_events(icon->paper, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | - GDK_BUTTON1_MOTION_MASK | + GDK_BUTTON1_MOTION_MASK | GDK_ENTER_NOTIFY_MASK | GDK_BUTTON2_MOTION_MASK | GDK_BUTTON3_MOTION_MASK); + gtk_signal_connect(GTK_OBJECT(icon->paper), "enter-notify-event", + GTK_SIGNAL_FUNC(enter_notify), icon); gtk_signal_connect(GTK_OBJECT(icon->paper), "button-press-event", GTK_SIGNAL_FUNC(button_press_event), icon); gtk_signal_connect(GTK_OBJECT(icon->paper), "button-release-event", @@ -287,6 +320,35 @@ void pinboard_unpin(PinIcon *icon) pinboard_save(); } +/* Unpin all selected items */ +void pinboard_unpin_selection(void) +{ + GList *next; + + g_return_if_fail(current_pinboard != NULL); + + if (number_selected == 0) + { + delayed_error(PROJECT, + _("You should first select some pinned icons to " + "unpin. Hold down the Ctrl key to select some icons.")); + return; + } + + next = current_pinboard->icons; + while (next) + { + PinIcon *icon = (PinIcon *) next->data; + + next = next->next; + + if (icon->selected) + gtk_widget_destroy(icon->win); + } + + pinboard_save(); +} + /* Put a border around the icon, briefly. * If icon is NULL then cancel any existing wink. * The icon will automatically unhighlight unless timeout is FALSE, @@ -322,13 +384,27 @@ void pinboard_wink_item(PinIcon *icon, gboolean timeout) */ void pinboard_clear(void) { + GList *next; + g_return_if_fail(current_pinboard != NULL); - g_return_if_fail(current_pinboard->icons = NULL); /* XXX */ + + next = current_pinboard->icons; + while (next) + { + PinIcon *icon = (PinIcon *) next->data; + + next = next->next; + + gtk_widget_destroy(icon->win); + } g_free(current_pinboard->name); g_free(current_pinboard); current_pinboard = NULL; - /* TODO: Remove handlers */ + + release_xdnd_proxy(GDK_ROOT_WINDOW()); + gdk_window_remove_filter(GDK_ROOT_PARENT(), proxy_filter, NULL); + gdk_window_set_user_data(GDK_ROOT_PARENT(), NULL); } /* If path is on the pinboard then it may have changed... check! */ @@ -344,17 +420,142 @@ void pinboard_may_update(guchar *path) PinIcon *icon = (PinIcon *) next->data; if (strcmp(icon->path, path) == 0) + icon_may_update(icon); + } +} + +/* Return the single selected icon, or NULL */ +PinIcon *pinboard_selected_icon(void) +{ + GList *next; + PinIcon *found = NULL; + + g_return_val_if_fail(current_pinboard != NULL, NULL); + + for (next = current_pinboard->icons; next; next = next->next) + { + PinIcon *icon = (PinIcon *) next->data; + + if (icon->selected) { - dir_restat(icon->path, &icon->item); - gtk_widget_queue_draw(icon->win); + if (found) + return NULL; /* >1 icon selected */ + else + found = icon; } } + + return found; } +/* Display the help for this application/item */ +void pinboard_show_help(PinIcon *icon) +{ + g_return_if_fail(icon != NULL); + + show_item_help(icon->path, &icon->item); +} + +void pinboard_clear_selection(void) +{ + pinboard_select_only(NULL); +} + +gboolean pinboard_is_selected(PinIcon *icon) +{ + g_return_val_if_fail(icon != NULL, FALSE); + + return icon->selected; +} + +/* Set whether an icon is selected or not */ +void pinboard_set_selected(PinIcon *icon, gboolean selected) +{ + g_return_if_fail(icon != NULL); + + if (icon->selected == selected) + return; + + if (selected) + change_number_selected(+1); + else + change_number_selected(-1); + + icon->selected = selected; + gtk_widget_queue_draw(icon->win); +} + +/* Return a list of all the selected icons. + * g_list_free() the result. + */ +GList *pinboard_get_selected(void) +{ + GList *next; + GList *selected = NULL; + + for (next = current_pinboard->icons; next; next = next->next) + { + PinIcon *i = (PinIcon *) next->data; + + if (i->selected) + selected = g_list_append(selected, i); + } + + return selected; +} + +/* Clear the selection and then select this icon. + * Doesn't release and claim the selection unnecessarily. + * If icon is NULL, then just clears the selection. + */ +void pinboard_select_only(PinIcon *icon) +{ + GList *next; + + g_return_if_fail(current_pinboard != NULL); + + if (icon) + pinboard_set_selected(icon, TRUE); + + for (next = current_pinboard->icons; next; next = next->next) + { + PinIcon *i = (PinIcon *) next->data; + + if (i->selected && i != icon) + pinboard_set_selected(i, FALSE); + } +} + + /**************************************************************** * INTERNAL FUNCTIONS * ****************************************************************/ +/* See if the file the icon points to has changed. Update the icon + * if so. + */ +static void icon_may_update(PinIcon *icon) +{ + MaskedPixmap *image = icon->item.image; + + pixmap_ref(image); + dir_restat(icon->path, &icon->item); + + if (icon->item.image != image) + { + int x = icon->x, y = icon->y; + int width, height; + + set_size_and_shape(icon, &width, &height); + gdk_window_resize(icon->win->window, width, height); + offset_from_centre(icon, width, height, &x, &y); + gtk_widget_set_uposition(icon->win, x, y); + gtk_widget_queue_draw(icon->win); + } + + pixmap_unref(image); +} + static gint end_wink(gpointer data) { pinboard_wink_item(NULL, FALSE); @@ -387,7 +588,6 @@ static void set_size_and_shape(PinIcon *icon, int *rwidth, int *rheight) int width, height; GdkFont *font = icon->win->style->font; int font_height; - GdkBitmap *mask; MaskedPixmap *image = icon->item.image; DirItem *item = &icon->item; @@ -399,27 +599,40 @@ static void set_size_and_shape(PinIcon *icon, int *rwidth, int *rheight) height = image->height + GAP + (font_height + 2) + 2 * WINK_FRAME; gtk_widget_set_usize(icon->win, width, height); - mask = gdk_pixmap_new(icon->win->window, width, height, 1); + if (icon->mask) + gdk_pixmap_unref(icon->mask); + icon->mask = gdk_pixmap_new(icon->win->window, width, height, 1); if (!mask_gc) - mask_gc = gdk_gc_new(mask); + mask_gc = gdk_gc_new(icon->mask); /* Clear the mask to transparent */ gdk_gc_set_foreground(mask_gc, &mask_transp); - gdk_draw_rectangle(mask, mask_gc, TRUE, 0, 0, width, height); + gdk_draw_rectangle(icon->mask, mask_gc, TRUE, 0, 0, width, height); gdk_gc_set_foreground(mask_gc, &mask_solid); /* Make the icon area solid */ - gdk_draw_pixmap(mask, mask_gc, image->mask, + if (image->mask) + { + gdk_draw_pixmap(icon->mask, mask_gc, image->mask, 0, 0, (width - image->width) >> 1, WINK_FRAME, image->width, image->height); + } + else + { + gdk_draw_rectangle(icon->mask, mask_gc, TRUE, + (width - image->width) >> 1, + WINK_FRAME, + image->width, + image->height); + } gdk_gc_set_function(mask_gc, GDK_OR); if (item->flags & ITEM_FLAG_SYMLINK) { - gdk_draw_pixmap(mask, mask_gc, im_symlink->mask, + gdk_draw_pixmap(icon->mask, mask_gc, im_symlink->mask, 0, 0, /* Source x,y */ (width - image->width) >> 1, /* Dest x */ WINK_FRAME, /* Dest y */ @@ -428,7 +641,7 @@ static void set_size_and_shape(PinIcon *icon, int *rwidth, int *rheight) else if (item->flags & ITEM_FLAG_MOUNT_POINT) { /* Note: Both mount state pixmaps must have the same mask */ - gdk_draw_pixmap(mask, mask_gc, im_mounted->mask, + gdk_draw_pixmap(icon->mask, mask_gc, im_mounted->mask, 0, 0, /* Source x,y */ (width - image->width) >> 1, /* Dest x */ WINK_FRAME, /* Dest y */ @@ -437,16 +650,12 @@ static void set_size_and_shape(PinIcon *icon, int *rwidth, int *rheight) gdk_gc_set_function(mask_gc, GDK_COPY); /* Mask off an area for the text */ - gdk_draw_rectangle(mask, mask_gc, TRUE, + gdk_draw_rectangle(icon->mask, mask_gc, TRUE, (width - (item->name_width + 2)) >> 1, WINK_FRAME + image->height + GAP, item->name_width + 2, font_height + 2); - gtk_widget_shape_combine_mask(icon->win, mask, 0, 0); - - if (icon->mask) - gdk_pixmap_unref(icon->mask); - icon->mask = mask; + gtk_widget_shape_combine_mask(icon->win, icon->mask, 0, 0); *rwidth = width; *rheight = height; @@ -462,6 +671,8 @@ static gint draw_icon(GtkWidget *widget, GdkEventExpose *event, PinIcon *icon) MaskedPixmap *image = item->image; int image_x; GdkGC *gc = widget->style->black_gc; + GtkStateType state = icon->selected ? GTK_STATE_SELECTED + : GTK_STATE_NORMAL; font_height = font->ascent + font->descent; @@ -512,7 +723,8 @@ static gint draw_icon(GtkWidget *widget, GdkEventExpose *event, PinIcon *icon) text_y = WINK_FRAME + image->height + GAP + 1; gtk_paint_flat_box(widget->style, widget->window, - GTK_STATE_NORMAL, GTK_SHADOW_NONE, + state, + GTK_SHADOW_NONE, NULL, widget, "text", text_x - 1, text_y - 1, @@ -520,7 +732,7 @@ static gint draw_icon(GtkWidget *widget, GdkEventExpose *event, PinIcon *icon) font_height + 2); gtk_paint_string(widget->style, widget->window, - GTK_STATE_NORMAL, + state, NULL, widget, "text", text_x, text_y + font->ascent, @@ -574,6 +786,41 @@ static gboolean button_release_event(GtkWidget *widget, return TRUE; } +static gboolean root_property_event(GtkWidget *widget, + GdkEventProperty *event, + gpointer data) +{ + if (event->atom == win_button_proxy && + event->state == GDK_PROPERTY_NEW_VALUE) + { + /* Setup forwarding on the new proxy window, if possible */ + forward_root_clicks(); + } + + return FALSE; +} + +static gboolean root_button_press(GtkWidget *widget, + GdkEventButton *event, + gpointer data) +{ + if (event->button == collection_menu_button) + show_pinboard_menu(event, NULL); + else + pinboard_clear_selection(); + + return TRUE; +} + +static gboolean enter_notify(GtkWidget *widget, + GdkEventCrossing *event, + PinIcon *icon) +{ + icon_may_update(icon); + + return FALSE; +} + static gboolean button_press_event(GtkWidget *widget, GdkEventButton *event, PinIcon *icon) @@ -584,22 +831,83 @@ static gboolean button_press_event(GtkWidget *widget, return TRUE; if (b == collection_menu_button) - pinboard_unpin(icon); + show_pinboard_menu(event, icon); else if (b < 4) { - drag_start_x = event->x_root; - drag_start_y = event->y_root; - gtk_grab_add(widget); - - if (b == 1) - pin_drag_type = DRAG_MAY_COPY; + if (event->state & GDK_CONTROL_MASK) + { + pin_drag_type = DRAG_NONE; + pinboard_set_selected(icon, !icon->selected); + } else - pin_drag_type = DRAG_MOVE_ICON; + { + drag_start_x = event->x_root; + drag_start_y = event->y_root; + gtk_grab_add(widget); + + if (b == 1) + pin_drag_type = DRAG_MAY_COPY; + else + pin_drag_type = DRAG_MOVE_ICON; + } } return TRUE; } +/* Return a text/uri-list of all the icons in the list */ +static guchar *create_uri_list(GList *list) +{ + GString *tmp; + guchar *retval; + guchar *leader; + + tmp = g_string_new(NULL); + leader = g_strdup_printf("file://%s", o_no_hostnames + ? "" + : our_host_name()); + + for (; list; list = list->next) + { + PinIcon *icon = (PinIcon *) list->data; + + g_string_append(tmp, leader); + g_string_append(tmp, icon->path); + g_string_append(tmp, "\r\n"); + } + + g_free(leader); + retval = tmp->str; + g_string_free(tmp, FALSE); + + return retval; +} + +static void start_drag(PinIcon *icon, GdkEventMotion *event) +{ + GtkWidget *widget = icon->paper; + GList *selected; + + if (!icon->selected) + pinboard_select_only(icon); + + selected = pinboard_get_selected(); + g_return_if_fail(selected != NULL); + + if (selected->next == NULL) + drag_one_item(widget, event, icon->path, &icon->item, FALSE); + else + { + guchar *uri_list; + + uri_list = create_uri_list(selected); + drag_selection(widget, event, uri_list); + g_free(uri_list); + } + + g_list_free(selected); +} + /* An icon is being dragged around... */ static gint icon_motion_notify(GtkWidget *widget, GdkEventMotion *event, @@ -629,8 +937,7 @@ static gint icon_motion_notify(GtkWidget *widget, { pin_drag_type = DRAG_NONE; gtk_grab_remove(widget); - drag_one_item(widget, event, icon->path, - &icon->item, FALSE); + start_drag(icon, event); } } @@ -657,6 +964,47 @@ static gboolean add_root_handlers(void) { GdkWindow *root; + if (!proxy_invisible) + { + GtkTargetEntry target_table[] = + { + {"text/uri-list", 0, TARGET_URI_LIST}, + {"STRING", 0, TARGET_STRING}, + }; + + win_button_proxy = gdk_atom_intern("_WIN_DESKTOP_BUTTON_PROXY", + FALSE); + proxy_invisible = gtk_invisible_new(); + gtk_widget_show(proxy_invisible); + /* + gdk_window_add_filter(proxy_invisible->window, + proxy_filter, NULL); + */ + + gtk_signal_connect(GTK_OBJECT(proxy_invisible), + "property_notify_event", + GTK_SIGNAL_FUNC(root_property_event), NULL); + gtk_signal_connect(GTK_OBJECT(proxy_invisible), + "button_press_event", + GTK_SIGNAL_FUNC(root_button_press), NULL); + drag_set_pinboard_dest(proxy_invisible); + + /* The proxy window is also used to hold the selection... */ + gtk_signal_connect(GTK_OBJECT(proxy_invisible), + "selection_clear_event", + GTK_SIGNAL_FUNC(lose_selection), + NULL); + + gtk_signal_connect(GTK_OBJECT(proxy_invisible), + "selection_get", + GTK_SIGNAL_FUNC(selection_get), NULL); + + gtk_selection_add_targets(proxy_invisible, + GDK_SELECTION_PRIMARY, + target_table, + sizeof(target_table) / sizeof(*target_table)); + } + root = gdk_window_lookup(GDK_ROOT_WINDOW()); if (!root) root = gdk_window_foreign_new(GDK_ROOT_WINDOW()); @@ -665,13 +1013,40 @@ static gboolean add_root_handlers(void) return FALSE; /* Forward events from the root window to our proxy window */ + gdk_window_add_filter(GDK_ROOT_PARENT(), proxy_filter, NULL); gdk_window_set_user_data(GDK_ROOT_PARENT(), proxy_invisible); - - drag_set_pinboard_dest(proxy_invisible); + gdk_window_set_events(GDK_ROOT_PARENT(), + gdk_window_get_events(GDK_ROOT_PARENT()) | + GDK_PROPERTY_CHANGE_MASK); + forward_root_clicks(); + return TRUE; } +/* See if the window manager is offering to forward root window clicks. + * If so, grab them. Otherwise, do nothing. + * Call this whenever the _WIN_DESKTOP_BUTTON_PROXY property changes. + */ +static void forward_root_clicks(void) +{ + click_proxy_gdk_window = find_click_proxy_window(); + if (!click_proxy_gdk_window) + return; + + /* Events on the wm's proxy are dealt with by our proxy widget */ + gdk_window_set_user_data(click_proxy_gdk_window, proxy_invisible); + gdk_window_add_filter(click_proxy_gdk_window, proxy_filter, NULL); + + /* The proxy window for clicks sends us button press events with + * SubstructureNotifyMask. We need StructureNotifyMask to receive + * DestroyNotify events, too. + */ + XSelectInput(GDK_DISPLAY(), + GDK_WINDOW_XWINDOW(click_proxy_gdk_window), + SubstructureNotifyMask | StructureNotifyMask); +} + /* Write the current state of the pinboard to the current pinboard file */ static void pinboard_save(void) { @@ -732,6 +1107,8 @@ out: /* * Filter that translates proxied events from virtual root windows into normal * Gdk events for the proxy_invisible widget. Stolen from gmc. + * + * Also gets events from the root window. */ static GdkFilterReturn proxy_filter(GdkXEvent *xevent, GdkEvent *event, @@ -759,6 +1136,8 @@ static GdkFilterReturn proxy_filter(GdkXEvent *xevent, event->button.window = proxy; event->button.send_event = xev->xbutton.send_event; event->button.time = xev->xbutton.time; + event->button.x_root = xev->xbutton.x_root; + event->button.y_root = xev->xbutton.y_root; event->button.x = xev->xbutton.x; event->button.y = xev->xbutton.y; event->button.state = xev->xbutton.state; @@ -786,7 +1165,10 @@ static GdkFilterReturn proxy_filter(GdkXEvent *xevent, /* Does not save the new state */ static void icon_destroyed(GtkWidget *widget, PinIcon *icon) { - g_print("[ icon '%s' destroyed! ]\n", icon->item.leafname); + g_return_if_fail(icon != NULL); + + if (icon->selected) + change_number_selected(-1); gdk_pixmap_unref(icon->mask); dir_item_clear(&icon->item); @@ -914,3 +1296,88 @@ static void drag_leave(GtkWidget *widget, { pinboard_wink_item(NULL, FALSE); } + +/* When changing the 'selected' attribute of an icon, call this + * to update the global counter and claim or release the primary + * selection as needed. + */ +static void change_number_selected(int delta) +{ + guint32 time; + + g_return_if_fail(delta != 0); + g_return_if_fail(number_selected + delta >= 0); + + if (number_selected == 0) + { + time = gdk_event_get_time(gtk_get_current_event()); + + gtk_selection_owner_set(proxy_invisible, + GDK_SELECTION_PRIMARY, + time); + } + + number_selected += delta; + + if (number_selected == 0) + { + time = gdk_event_get_time(gtk_get_current_event()); + + gtk_selection_owner_set(NULL, + GDK_SELECTION_PRIMARY, + time); + } +} + +/* Called when another application wants the contents of our selection */ +static void selection_get(GtkWidget *widget, + GtkSelectionData *selection_data, + guint info, + guint time, + gpointer data) +{ + GString *str; + GList *next; + guchar *leader = NULL; + + str = g_string_new(NULL); + + if (info == TARGET_URI_LIST) + leader = g_strdup_printf("file://%s", + o_no_hostnames ? "" : our_host_name()); + + for (next = current_pinboard->icons; next; next = next->next) + { + PinIcon *icon = (PinIcon *) next->data; + + if (!icon->selected) + continue; + + if (leader) + g_string_append(str, leader); + g_string_append(str, icon->path); + g_string_append_c(str, ' '); + } + + g_free(leader); + + gtk_selection_data_set(selection_data, + gdk_atom_intern("STRING", FALSE), + 8, + str->str, + str->len ? str->len - 1 : 0); + + g_string_free(str, TRUE); +} + +/* Called when another application takes the selection away from us */ +static gint lose_selection(GtkWidget *widget, GdkEventSelection *event) +{ + /* 'lock' number_selected so that we don't send any events */ + number_selected++; + pinboard_clear_selection(); + number_selected--; + + return TRUE; +} + diff --git a/ROX-Filer/src/pinboard.h b/ROX-Filer/src/pinboard.h index c17649ed..d3d7e84b 100644 --- a/ROX-Filer/src/pinboard.h +++ b/ROX-Filer/src/pinboard.h @@ -16,8 +16,17 @@ typedef struct _PinIcon PinIcon; void pinboard_activate(guchar *name); void pinboard_pin(guchar *path, guchar *name, int x, int y); void pinboard_unpin(PinIcon *icon); +void pinboard_unpin_selection(void); void pinboard_wink_item(PinIcon *icon, gboolean timeout); void pinboard_clear(void); void pinboard_may_update(guchar *path); +void pinboard_show_help(PinIcon *icon); +void pinboard_clear_selection(void); + +PinIcon *pinboard_selected_icon(void); +gboolean pinboard_is_selected(PinIcon *icon); +void pinboard_set_selected(PinIcon *icon, gboolean selected); +GList *pinboard_get_selected(void); +void pinboard_select_only(PinIcon *icon); #endif /* _PINBOARD_H */ diff --git a/ROX-Filer/src/remote.c b/ROX-Filer/src/remote.c index 8da3d248..ac13f1a3 100644 --- a/ROX-Filer/src/remote.c +++ b/ROX-Filer/src/remote.c @@ -37,6 +37,8 @@ #include "support.h" #include "filer.h" #include "gui_support.h" +#include "run.h" +#include "remote.h" static GdkAtom filer_atom; /* _ROX_FILER_VERSION_HOST */ static GdkAtom ipc_atom; /* _ROX_FILER_OPEN */ @@ -47,8 +49,6 @@ static gboolean get_ipc_property(GdkWindow *window, Window *r_xid); static gboolean ipc_prop_changed(GtkWidget *window, GdkEventProperty *event, gpointer data); -static void send_request(GdkWindow *ipc_window, int argc, char **argv); -static void open_dirs(guchar *data, int length); /**************************************************************** * EXTERNAL INTERFACE * @@ -58,8 +58,10 @@ static void open_dirs(guchar *data, int length); /* Try to get an already-running filer to handle things (only if * new_copy is FALSE); TRUE if we succeed. * Create and IPC widget so that future filers can contact us. + * + * See main() for a description of 'to_open'. */ -gboolean remote_init(int argc, char **argv, gboolean new_copy) +gboolean remote_init(GString *to_open, gboolean new_copy) { guchar *unique_id; GdkWindowPrivate *window; @@ -76,7 +78,8 @@ gboolean remote_init(int argc, char **argv, gboolean new_copy) existing_ipc_window = new_copy ? NULL : get_existing_ipc_window(); if (existing_ipc_window) { - send_request(existing_ipc_window, argc, argv); + gdk_property_change(existing_ipc_window, ipc_atom, XA_STRING, 8, + GDK_PROP_MODE_APPEND, to_open->str, to_open->len); return TRUE; } @@ -172,10 +175,10 @@ static gboolean ipc_prop_changed(GtkWidget *window, while (1) { - if (!gdk_property_get(window->window, ipc_atom, + if (!(gdk_property_get(window->window, ipc_atom, XA_STRING, 0, grab_len, TRUE, NULL, NULL, - &length, &data) && data) + &length, &data) && data)) return TRUE; /* Error? */ if (length >= grab_len) @@ -189,7 +192,7 @@ static gboolean ipc_prop_changed(GtkWidget *window, data = g_realloc(data, length + 1); data[length] = '\0'; - open_dirs(data, length); + run_list(data); g_free(data); break; @@ -201,50 +204,3 @@ static gboolean ipc_prop_changed(GtkWidget *window, return FALSE; } -static void send_request(GdkWindow *ipc_window, int argc, char **argv) -{ - char *data, *d; - int i, size = 0; - - if (argc == 0) - { - argc = 1; - argv = &home_dir; - } - - for (i = 0; i < argc; i++) - size += strlen(argv[i]) + 1; - - data = g_malloc(size); - - d = data; - for (i = 0; i < argc; i++) - { - int len; - - len = strlen(argv[i]); - memcpy(d, argv[i], len + 1); - d += len + 1; - } - - gdk_property_change(ipc_window, ipc_atom, XA_STRING, 8, - GDK_PROP_MODE_APPEND, data, size); - - g_free(data); -} - -/* data is a sequence of nul-term'd strings */ -static void open_dirs(guchar *data, int length) -{ - while (length > 0) - { - int len; - - if (*data) - filer_opendir(data, PANEL_NO); - - len = strlen(data) + 1; - data += len; - length -= len; - } -} diff --git a/ROX-Filer/src/remote.h b/ROX-Filer/src/remote.h index 00786e9b..756786eb 100644 --- a/ROX-Filer/src/remote.h +++ b/ROX-Filer/src/remote.h @@ -5,4 +5,9 @@ * By Thomas Leonard, . */ -gboolean remote_init(int argc, char **argv, gboolean new_copy); +#ifndef _REMOTE_H +#define _REMOTE_H + +gboolean remote_init(GString *to_open, gboolean new_copy); + +#endif /* _REMOTE_H */ diff --git a/ROX-Filer/src/run.c b/ROX-Filer/src/run.c index c476cb9d..d4dda50c 100644 --- a/ROX-Filer/src/run.c +++ b/ROX-Filer/src/run.c @@ -40,6 +40,7 @@ static void write_data(gpointer data, gint fd, GdkInputCondition cond); static gboolean follow_symlink(char *full_path, FilerWindow *filer_window); static gboolean open_file(guchar *path, MIME_type *type); +static void app_show_help(char *path); typedef struct _PipedData PipedData; @@ -247,12 +248,139 @@ gboolean run_diritem(guchar *full_path, return open_file(full_path, edit ? &text_plain : item->mime_type); default: - report_error("open_item", + delayed_error(full_path, "I don't know how to open that"); return FALSE; } } +/* Attempt to open this item */ +gboolean run_by_path(guchar *full_path) +{ + gboolean retval; + DirItem item; + + /* XXX: Loads an image - wasteful */ + dir_stat(full_path, &item); + retval = run_diritem(full_path, &item, NULL, FALSE); + dir_item_clear(&item); + + return retval; +} + +void show_item_help(guchar *path, DirItem *item) +{ + switch (item->base_type) + { + case TYPE_FILE: + if (item->flags & ITEM_FLAG_EXEC_FILE) + delayed_error(_("Executable file"), + _("This is a file with an eXecute bit " + "set - it can be run as a program.")); + else + delayed_error(_("File"), _( + "This is a data file. Try using the " + "Info menu item to find out more...")); + break; + case TYPE_DIRECTORY: + if (item->flags & ITEM_FLAG_APPDIR) + app_show_help(path); + else if (item->flags & ITEM_FLAG_MOUNT_POINT) + delayed_error(_("Mount point"), _( + "A mount point is a directory which another " + "filing system can be mounted on. Everything " + "on the mounted filesystem then appears to be " + "inside the directory.")); + else + delayed_error(_("Directory"), _( + "This is a directory. It contains an index to " + "other items - open it to see the list.")); + break; + case TYPE_CHAR_DEVICE: + case TYPE_BLOCK_DEVICE: + delayed_error(_("Device file"), _( + "Device files allow you to read from or write " + "to a device driver as though it was an " + "ordinary file.")); + break; + case TYPE_PIPE: + delayed_error(_("Named pipe"), _( + "Pipes allow different programs to " + "communicate. One program writes data to the " + "pipe while another one reads it out again.")); + break; + case TYPE_SOCKET: + delayed_error(_("Socket"), _( + "Sockets allow processes to communicate.")); + break; + default: + delayed_error(_("Unknown type"), _( + "I couldn't find out what kind of file this " + "is. Maybe it doesn't exist anymore or you " + "don't have search permission on the directory " + "it's in?")); + break; + } +} + +/* Runs each item in the list. Items may be files, directories, + * panels or pinboards. + * + * See main() for a description of 'to_open'. + */ +void run_list(guchar *to_open) +{ + guchar *next; + + /* TODO: Should escape < characters in case one really does + * appear in a filename! + */ + + while (*to_open) + { + guchar code; + guchar *value; + + g_return_if_fail(to_open[0] == '<'); + code = to_open[1]; + + next = strchr(to_open + 1, '<'); + if (!next) + next = to_open + strlen(to_open); + + g_return_if_fail(next - to_open > 2); + g_return_if_fail(to_open[2] == '>'); + + value = g_strndup(to_open + 3, next - to_open - 3); + to_open = next; + + switch (code) + { + case 'f': + run_by_path(value); + break; + case 'p': + pinboard_activate(value); + break; + case 'd': + filer_opendir(value, PANEL_NO); + break; + case 't': + filer_opendir(value, PANEL_TOP); + break; + case 'b': + filer_opendir(value, PANEL_BOTTOM); + break; + default: + g_warning("Don't know how to handle '%s'", + value); + return; + } + + g_free(value); + } + +} /**************************************************************** * INTERNAL FUNCTIONS * @@ -376,3 +504,21 @@ static gboolean open_file(guchar *path, MIME_type *type) return FALSE; } + +static void app_show_help(char *path) +{ + char *help_dir; + struct stat info; + + help_dir = g_strconcat(path, "/Help", NULL); + + if (mc_stat(help_dir, &info)) + delayed_error(_("Application"), + _("This is an application directory - you can " + "run it as a program, or open it (hold down " + "Shift while you open it). Most applications provide " + "their own help here, but this one doesn't.")); + else + filer_opendir(help_dir, PANEL_NO); +} + diff --git a/ROX-Filer/src/run.h b/ROX-Filer/src/run.h index 993ea303..e874b84b 100644 --- a/ROX-Filer/src/run.h +++ b/ROX-Filer/src/run.h @@ -5,6 +5,9 @@ * By Thomas Leonard, . */ +#ifndef _RUN_H +#define _RUN_H + #include #include @@ -12,7 +15,12 @@ void run_app(char *path); void run_with_files(char *path, GSList *uri_list); void run_with_data(char *path, gpointer data, gulong length); +gboolean run_by_path(guchar *full_path); gboolean run_diritem(guchar *full_path, DirItem *item, FilerWindow *filer_window, gboolean edit); +void show_item_help(guchar *path, DirItem *item); +void run_list(guchar *to_open); + +#endif /* _RUN_H */ diff --git a/ROX-Filer/src/type.c b/ROX-Filer/src/type.c index b5f06ce6..9e33fac0 100644 --- a/ROX-Filer/src/type.c +++ b/ROX-Filer/src/type.c @@ -282,7 +282,11 @@ MaskedPixmap *type_to_icon(MIME_type *type) char *type_name; time_t now; - g_return_val_if_fail(type != NULL, im_unknown); + if (type == NULL) + { + pixmap_ref(im_unknown); + return im_unknown; + } now = time(NULL); /* Already got an image? */ diff --git a/install.sh b/install.sh index 9ced201a..190c240b 100755 --- a/install.sh +++ b/install.sh @@ -17,6 +17,17 @@ function endir () { fi } +function confirm_or_die () { + while [ 1 -eq 1 ]; do + echo -n "[yes/no] >>> " + read CONFIRM + case $CONFIRM in + [yY]*) return;; + [nN]*) die "OK.";; + esac + done +} + cd `dirname $0` ./ROX-Filer/AppRun --version 2> /dev/null @@ -92,14 +103,18 @@ EOF esac fi +endir "$CHOICES/MIME-icons" +endir "$CHOICES/MIME-info" +endir "$CHOICES/MIME-types" + echo "Installing icons..." -cp -r Choices/MIME-icons "$CHOICES" +cp Choices/MIME-icons/* "$CHOICES/MIME-icons" echo "Installing other files. If you haven't modified these since the " echo "last installation then answer 'yes' to any questions about overwriting " echo "files. Otherwise, you'll have to decide what to do!" -cp -ri Choices/MIME-types "$CHOICES" -cp -ri Choices/MIME-info "$CHOICES" +cp -i Choices/MIME-types/* "$CHOICES/MIME-types" +cp -i Choices/MIME-info/* "$CHOICES/MIME-info" echo echo "OK, done that. Next step..." @@ -134,7 +149,14 @@ if [ -n "$APPDIR" ]; then endir "$APPDIR" (cd ROX-Filer/src; make clean) > /dev/null - cp -r ROX-Filer $APPDIR + if [ -e "$APPDIR/ROX-Filer" ]; then + echo "ROX-Filer is already installed - delete the existing" + echo "copy?" + confirm_or_die + echo Deleting... + rm -rf "$APPDIR/ROX-Filer.old" + fi + cp -r ROX-Filer "$APPDIR" else echo "OK, I'll leave it where it is." APPDIR=`pwd` @@ -147,8 +169,8 @@ ln -s "$APPDIR/ROX-Filer" "$CHOICES/MIME-types/special_directory" cat << EOF -Where would you like to install the 'rox' script, which is used to open -files (and, often, start the filer)? +Where would you like to install the 'rox' script, which is used to run +the filer? 1) /usr/local/bin 2) ${HOME}/bin @@ -174,7 +196,12 @@ esac if [ -n "$BINDIR" ]; then endir "$BINDIR" - cp rox "$BINDIR" || die "Failed to install 'rox' script" + cat > "$BINDIR/rox" << EOF +#!/bin/sh +exec $APPDIR/ROX-Filer/AppRun "\$@" +EOF + [ $? -eq 0 ] || die "Failed to install 'rox' script" + chmod a+x "$BINDIR/rox" cat << EOF Script installed. You can run the filer by simply typing 'rox' @@ -195,12 +222,12 @@ fi sleep 3 cat << EOF - ****************************" - *** Now read the manual! ***" - ****************************" + **************************** + *** Now read the manual! *** + **************************** -Type: +Run ROX and click on the information icon on the toolbar: - \$ $APPDIR/ROX-Filer/AppRun $APPDIR/ROX-Filer/Help & + \$ rox & EOF diff --git a/rox b/rox deleted file mode 100755 index 46456fc3..00000000 --- a/rox +++ /dev/null @@ -1,202 +0,0 @@ -#!/usr/bin/env perl - -sub type_from_path; -sub try_running; -sub set_type; -sub show_help; - -if (defined($choices = $ENV{CHOICESPATH})) { - @choices = split(":", $choices); -} else { - @choices = (glob("~/Choices"), - "/usr/local/share/Choices", - "/usr/share/Choices"); -} - -@choices or die("No directories listed in CHOICESPATH!\n"); - -while ($ARGV[0] =~ /^--?(.*?)(\=(.*))?$/) -{ - shift; - - ($opt, $value) = ($1, $3); - - &show_help, next if $opt eq "h" or $opt eq "help"; - &set_type, next if $opt eq "t" or $opt eq "type"; - $verbose = 1, next if $opt eq "v" or $opt eq "verbose"; - $display = 1, next if $opt eq "d" or $opt eq "display"; - - die "Unknown option: $opt\n"; -} - -$item = shift or '.'; - -unless ($item =~ /^\//) { - chomp($pwd = `pwd`); - $item = "$pwd/$item"; -} - -unshift @ARGV, $item; - --e $item or die "File $item not found\n"; - -$type or --f _ && ($type = type_from_path($item)) or --d _ && ($type = "special/directory") or --p _ && ($type = "special/pipe") or --S _ && ($type = "special/socket") or --b _ && ($type = "special/block-device") or --c _ && ($type = "special/char-device") or - die "Don't know what kind of thing '$item' is!\n"; - -if ($display) { - print "$type\n"; - exit; -} - -print "Looking for a handler for files of type '$type'\n" if $verbose; - -if ($type =~ /^(.*?)\/(.*)$/) { - ($media, $subtype) = ($1, $2); - foreach (@choices) { - try_running "$_/MIME-types/${media}_$subtype" if $_; - } -} else { - $media = $type; -} - -foreach (@choices) { - try_running "$_/MIME-types/$media" if $_; -} - -die "No run action specified for files of type '$type'.\n"; - -exit; - -sub try_running -{ - my $path = shift; - - print "Checking for '$path'\n" if $verbose; - - return unless -e $path; - - -f _ and -x _ and (exec("$path", @ARGV), die "exec($path): $!\n"); - - if (-d _) - { - die "$path/AppRun is not executable" unless -x "$path/AppRun"; - - exec("$path/AppRun", @ARGV), die "exec($path): $!\n"; - } - - warn "$path exists but cannot be executed!\n"; -} - -sub check_entry -{ - my ($key, $prio, $values) = @_; - - return undef if $prio <= $best_rating; - - if ($key eq "ext" and $ext) { - foreach $e (split ' ', $values) { - return 1 if $e eq $ext; - } - } elsif ($key eq "regex") { - return 1 if $leaf =~ /$values/; - } - - undef; -} - -sub find_type -{ - my $file = shift; - my $type = undef; - - open(FILE, "<$file") or warn("Can't open $file\n"), return; - - while () { - s/^\s+//; - s/\s+$//; - - next if /^(#.*)?$/; - - if (/^([^:]*)\/(.*):?/) { - $type = "$1/$2" - } elsif (/^([^ :,]+)\s*(,\s*(\d+)\s*)?:\s*(.*)$/) { - my ($key, $prio, $values) = ($1, $3, $4); - - $prio ||= 1; - - if (check_entry($key, $prio, $values)) { - print "New best guess is '$type', " . - "rating $prio\n" if $verbose; - - $best_type = $type; - $best_rating = $prio; - } - } - } - - close FILE; - - return $type; -} - -sub type_from_path -{ - my $type; - local ($best_type, $best_rating) = ("text/plain", 0); - local ($leaf, $ext); - - ($leaf = $item) =~ s/.*\///; - ($ext = $leaf) =~ s/.*\.// or $ext = undef; - - foreach $dir (@choices) { - print "Looking inside '$dir'\n" if $verbose; - - if (-d "$dir/MIME-info") { - foreach (glob("$dir/MIME-info/*")) { - print "Scanning '$_'\n" if $verbose; - find_type($_); - } - } - } - - return $best_type; -} - -sub show_help -{ - print <<"EOF"; -Usage: rox [FILE [ARGS]] - -Open FILE using an appropriate program, invoking it with additional arguments -ARGS. If no FILE is given then the current directory is used. - - -h, --help display this help and exit - -d, --display display the guessed type rather than opening the file - -t, --type=TYPE assume TYPE instead of guessing - -v, --verbose display lots of extra info - -Report bugs to . -EOF - exit 0; -} - -sub set_type -{ - $value = shift @ARGV unless $value; - - $type = $value or die "Missing argument to --type option\n"; -} - -sub set_by_drag -{ - $prog = `dropbox -l "Drop a program which can open $type files here"`; - die "'dropbox' program failed (is it installed?)\n" if $?; - - print "OK, will use $prog to edit $type files.\n"; -} -- 2.11.4.GIT