emelfm2-0.4.1/0000700000175000017500000000000011015120161011771 5ustar cairocairoemelfm2-0.4.1/po/0000700000175000017500000000000011015120161012407 5ustar cairocairoemelfm2-0.4.1/po/fr.po0000600000175000017500000050600411014260437013377 0ustar cairocairo# translation of fr.po to français # Denis Prost , 2005, 2006, 2007. # Grégory SCHMITT , 2007. # traduction de emelfm2.pot à francais msgid "" msgstr "" "Project-Id-Version: fr\n" "PO-Revision-Date: 2008-05-07 19:54-0400\n" "Last-Translator: Grégory SCHMITT \n" "Language-Team: français \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: e2_about_dialog.c:107 e2_action.c:851 e2_alias.c:323 e2_command.c:2781 msgid "help" msgstr "aide" #: e2_about_dialog.c:118 e2_action.c:826 msgid "about" msgstr "à propos" #: e2_about_dialog.c:126 #, c-format msgid "" "An \"orthodox\" file manager for GTK+2\n" "\n" "Copyright © %s\n" "\n" "This program is licensed under the terms of the General Public License and " "comes with ABSOLUTELY NO WARRANTY\n" "\n" "This binary was compiled on %s\n" "using %s and GTK+%d.%d.%d" msgstr "" "Un gestionnaire de fichiers \"à l'ancienne\" GTK+2\n" "implémentant une interface à deux panneaux\n" "\n" "Copyright © %s\n" "\n" "Ce programme est soumis aux termes de la licence \"General Public License\" " "et est fourni ABSOLUMENT SANS AUCUNE GARANTIE\n" "\n" "Ce binaire a été compilé sur %s\n" "en utilisant %s et GTK+%d.%d.%d" #: e2_about_dialog.c:142 #, c-format msgid "" "The file\n" "%s\n" "gives details." msgstr "" "Voir les détails dans le fichier\n" "%s" #: e2_about_dialog.c:145 msgid "contributors" msgstr "contributeurs" #: e2_about_dialog.c:150 msgid "" "This program is based on emelFM, developed by Michael Clark.\n" "\n" "Contributions have been made by many friends." msgstr "" "Ce programme est basé sur emelFM, développé par Michael Clark.\n" "\n" "Beaucoup d'amis y ont contribué." #: e2_about_dialog.c:160 msgid "usage" msgstr "utilisation" #: e2_about_dialog.c:169 e2_option.c:1073 e2_output.c:980 msgid "commands" msgstr "commandes" #: e2_about_dialog.c:179 e2_filetype_dialog.c:1193 msgid "Read the file" msgstr "Lire le fichier" #: e2_about_dialog.c:179 e2_context_menu.c:554 e2_filetype_dialog.c:1192 #: e2p_view.c:83 msgid "_View" msgstr "_Voir" #: e2_action.c:798 msgid "bookmark" msgstr "signet" #: e2_action.c:799 e2_command.c:1858 msgid "command" msgstr "commande" #: e2_action.c:800 msgid "configure" msgstr "configurer" #: e2_action.c:801 e2_option__default.c:160 e2_option__default.c:329 #: e2_toolbar.c:2794 msgid "dialog" msgstr "dialogue" #: e2_action.c:802 msgid "dirline" msgstr "ligne d'affichage du dossier" #: e2_action.c:803 e2p_du.c:219 msgid "file" msgstr "fichier" #: e2_action.c:804 e2_task.c:3419 msgid "find" msgstr "chercher" #: e2_action.c:805 msgid "list" msgstr "liste" #: e2_action.c:806 msgid "option" msgstr "option" #: e2_action.c:807 e2_option.c:1094 msgid "output" msgstr "sortie" #: e2_action.c:808 msgid "pane_active" msgstr "panneau_actif" #: e2_action.c:809 msgid "pane1" msgstr "panneau1" #: e2_action.c:810 msgid "pane2" msgstr "panneau2" #: e2_action.c:811 e2_option.c:1099 msgid "panes" msgstr "panneaux" #: e2_action.c:812 e2_config_dialog.c:935 msgid "plugin" msgstr "greffon" #: e2_action.c:813 msgid "toggle" msgstr "inverser" #: e2_action.c:815 msgid "separator" msgstr "séparateur" #: e2_action.c:816 msgid "" msgstr "" #: e2_action.c:817 msgid "" msgstr "" #: e2_action.c:818 msgid "" msgstr "" #: e2_action.c:821 msgid "" msgstr "" #: e2_action.c:822 msgid "" msgstr "" #: e2_action.c:823 msgid "" msgstr "" #: e2_action.c:824 msgid "" msgstr "" #: e2_action.c:825 msgid "" msgstr "" #: e2_action.c:827 msgid "add" msgstr "ajouter" #: e2_action.c:828 msgid "adjust_ratio" msgstr "ajuster_le_ratio" #: e2_action.c:829 e2_bookmark.c:302 e2_option_tree.c:667 msgid "children" msgstr "fils" #: e2_action.c:830 e2_alias.c:321 msgid "clear" msgstr "nettoyer" #: e2_action.c:831 msgid "clear_history" msgstr "vider_l_historique" #: e2_action.c:832 msgid "complete" msgstr "compléter" #: e2_action.c:833 msgid "application" msgstr "application" #: e2_action.c:834 e2_task.c:1426 msgid "copy" msgstr "copier" #: e2_action.c:835 msgid "copy_as" msgstr "copier_comme" #: e2_action.c:836 msgid "copy_merge" msgstr "copier_fusionner" #: e2_action.c:837 msgid "copy_with_time" msgstr "copier_avec_la_date" #: e2_action.c:838 msgid "default" msgstr "par_défaut" #: e2_action.c:839 msgid "delete" msgstr "supprimer" # To be examined later #: e2_action.c:840 msgid "edit" msgstr "éditer" #: e2_action.c:841 msgid "edit_again" msgstr "editer_de_nouveau" #: e2_action.c:842 msgid "filetype" msgstr "type_de_fichier" #: e2_action.c:843 msgid "focus" msgstr "activer" #: e2_action.c:844 msgid "focus_toggle" msgstr "échanger_le_focus" #: e2_action.c:845 msgid "fullscreen" msgstr "plein écran" #: e2_action.c:846 msgid "go_back" msgstr "précédent" #: e2_action.c:847 msgid "go_forward" msgstr "suivant" #: e2_action.c:848 msgid "go_up" msgstr "parent" #: e2_action.c:849 msgid "goto_bottom" msgstr "fin" #: e2_action.c:850 msgid "goto_top" msgstr "début" #: e2_action.c:852 e2_option.c:1085 msgid "history" msgstr "historique" #: e2_action.c:853 e2_mkdir_dialog.c:918 msgid "info" msgstr "info" #: e2_action.c:854 msgid "insert_selection" msgstr "insérer_la_sélection" #: e2_action.c:855 msgid "invert_selection" msgstr "inverser_la_sélection" #: e2_action.c:856 msgid "mirror" msgstr "mirroriser" #: e2_action.c:857 msgid "mkdir" msgstr "nouveau_dossier" #: e2_action.c:858 msgid "mountpoints" msgstr "points de montage" #: e2_action.c:859 e2_task.c:1748 msgid "move" msgstr "déplacer" #: e2_action.c:860 msgid "move_as" msgstr "déplacer_comme" #: e2_action.c:861 e2_select_dir_dialog.c:45 msgid "open" msgstr "ouvrir" #: e2_action.c:862 msgid "open_in_other" msgstr "ouvrir_dans_l_autre_panneau" #: e2_action.c:863 msgid "open_with" msgstr "ouvrir_avec" #: e2_action.c:864 e2p_find.c:3069 msgid "owners" msgstr "propriétaires" #: e2_action.c:865 msgid "page_down" msgstr "page_suivante" #: e2_action.c:866 msgid "page_up" msgstr "page_précedente" #: e2_action.c:867 msgid "pending" msgstr "en attente" #: e2_action.c:868 e2_permissions_dialog.c:318 msgid "permissions" msgstr "permissions" #: e2_action.c:869 msgid "print" msgstr "imprimer" #: e2_action.c:870 e2_alias.c:322 msgid "quit" msgstr "quitter" #: e2_action.c:871 msgid "refresh" msgstr "rafraîchir" #: e2_action.c:872 msgid "refreshresume" msgstr "reprend_le_rafraîchissement" #: e2_action.c:873 msgid "refreshsuspend" msgstr "suspend_le_rafraîchissement" #: e2_action.c:874 e2_task.c:2358 msgid "rename" msgstr "renommer" #: e2_action.c:875 msgid "scroll_down" msgstr "descendre" #: e2_action.c:876 msgid "scroll_up" msgstr "remonter" #: e2_action.c:877 e2_option.c:1102 msgid "search" msgstr "chercher" #: e2_action.c:878 msgid "send" msgstr "envoyer" #: e2_action.c:879 msgid "set" msgstr "fixer" #: e2_action.c:880 msgid "show" msgstr "montrer" #: e2_action.c:881 msgid "show_hidden" msgstr "montrer_les_cachés" #: e2_action.c:882 msgid "show_menu" msgstr "afficher_le_menu" #: e2_action.c:883 #, fuzzy msgid "sortaccesssed" msgstr "trieraccédés" #: e2_action.c:884 #, fuzzy msgid "sortchanged" msgstr "trierchangés" #: e2_action.c:885 #, fuzzy msgid "sortgroup" msgstr "triergroupe" #: e2_action.c:886 #, fuzzy msgid "sortmodified" msgstr "triermodifiés" #: e2_action.c:887 #, fuzzy msgid "sortname" msgstr "triernom" #: e2_action.c:888 #, fuzzy msgid "sortpermission" msgstr "trierpermission" #: e2_action.c:889 #, fuzzy msgid "sortsize" msgstr "triertaille" #: e2_action.c:890 #, fuzzy msgid "sortuser" msgstr "trierusager" #: e2_action.c:891 msgid "switch" msgstr "échanger" #: e2_action.c:892 e2_option__default.c:274 e2p_find.c:3020 msgid "symlink" msgstr "lien symbolique" #: e2_action.c:893 msgid "symlink_as" msgstr "lier_comme" #: e2_action.c:894 msgid "sync" msgstr "synchroniser" #: e2_action.c:895 msgid "toggle_direction" msgstr "changer_la_direction" #: e2_action.c:897 msgid "toggle_select_all" msgstr "tout_(dé)sélectionner" #: e2_action.c:898 msgid "toggle_selected" msgstr "sélectionner_désélectionner" #: e2_action.c:899 e2_bookmark.c:564 msgid "trash" msgstr "corbeille" #: e2_action.c:900 msgid "trashempty" msgstr "vider_la_corbeille" #: e2_action.c:902 msgid "tree" msgstr "arborescence" #: e2_action.c:904 msgid "unpack" msgstr "désarchiver" # To be examined later #: e2_action.c:905 e2_option.c:1107 msgid "view" msgstr "voir" #: e2_action.c:906 msgid "view_again" msgstr "voir_de_nouveau" #: e2_action.c:907 msgid "view_at" msgstr "voir_à" #: e2_action.c:909 e2_bookmark.c:302 e2_option_tree.c:667 msgid "child" msgstr "fils" #: e2_action.c:910 msgid "ctrl" msgstr "ctrl" #: e2_action.c:911 msgid "dirs" msgstr "dossiers" #: e2_action.c:912 msgid "escape" msgstr "échap" #: e2_action.c:913 msgid "expand" msgstr "élargir" #: e2_action.c:914 e2p_du.c:219 msgid "files" msgstr "fichiers" #: e2_action.c:915 msgid "off" msgstr "arrêt" #: e2_action.c:916 msgid "on" msgstr "marche" #: e2_action.c:917 msgid "quote" msgstr "guillemet_simple" #: e2_action.c:918 msgid "shift" msgstr "shift" #: e2_action.c:919 msgid "top" msgstr "haut" #: e2_action.c:923 #, fuzzy msgid "" msgstr "" #: e2_action.c:924 msgid "dummy" msgstr "dummy" #: e2_action.c:925 #, fuzzy msgid "namespace" msgstr "namespace" #: e2_action.c:926 msgid "unpack_in_other" msgstr "Désarchiver dans l'_autre panneau" #: e2_action.c:929 msgid "key" msgstr "touche" #: e2_action.c:930 e2_vfs_dialog.c:699 msgid "alias" msgstr "alias" #: e2_alias.c:316 msgid "x" msgstr "x" #: e2_alias.c:317 e2_alias.c:318 e2_toolbar.c:2893 msgid "Done. Press enter " msgstr "Terminé. Appuyez sur Entrée" #: e2_alias.c:317 msgid "xx" msgstr "xx" #: e2_alias.c:324 e2_output.c:985 msgid "keys" msgstr "raccourcis clavier" #: e2_alias.c:325 msgid "e2ps" msgstr "e2ps" # Do not translate yet per Tom #: e2_alias.c:327 #, fuzzy msgid "cns" msgstr "cns" #: e2_alias.c:350 msgid "Match" msgstr "Correspondance" #: e2_alias.c:353 msgid "Stop" msgstr "Arrêt" #: e2_alias.c:355 msgid "Replace" msgstr "Remplacement" #: e2_bookmark.c:298 msgid "Are you sure that you want to delete the bookmark" msgstr "Êtes-vous sûr que vous voulez détruire ce signet ?" #: e2_bookmark.c:302 e2p_du.c:222 msgid "and" msgstr "et" #: e2_bookmark.c:307 msgid "confirm bookmark delete" msgstr "confirmez la suppression du signet" #: e2_bookmark.c:375 msgid "_Add after" msgstr "_Ajouter après" #: e2_bookmark.c:376 msgid "Bookmark the current directory after the selected bookmark" msgstr "Ajouter le dossier courant après le signet sélectionné" #: e2_bookmark.c:382 msgid "Add as _child" msgstr "Ajouter en tant que _fils" #: e2_bookmark.c:383 msgid "Bookmark the current directory a a child of the selected bookmark" msgstr "Ajouter le dossier courant en tant que fils du signet sélectionné" #: e2_bookmark.c:389 e2_context_menu.c:563 e2p_unpack.c:335 msgid "_Delete" msgstr "_Supprimer" #: e2_bookmark.c:390 msgid "Delete the selected bookmark, and its children if any" msgstr "Supprimer le signet sélectionné et ses fils s'il en a" #: e2_bookmark.c:552 msgid "_home" msgstr "_Maison" #: e2_bookmark.c:555 msgid "cdrom" msgstr "cdrom" #: e2_bookmark.c:557 msgid "root" msgstr "root" #: e2_bookmark.c:558 msgid "Your home directory" msgstr "Votre dossier Maison" #: e2_bookmark.c:558 msgid "home" msgstr "Espace personnel" #: e2_bookmark.c:559 msgid "media" msgstr "média" #: e2_bookmark.c:560 msgid "mnt" msgstr "mnt" #: e2_bookmark.c:561 msgid "usr" msgstr "usr" #: e2_bookmark.c:562 msgid "usr/local" msgstr "usr/local" #: e2_bookmark.c:564 msgid "default trash directory" msgstr "dossier Corbeille par défaut" #: e2_bookmark.c:582 e2_context_menu.c:613 e2_menu.c:995 e2_plugins.c:1278 #: e2_toolbar.c:2851 e2_toolbar.c:2918 e2_toolbar.c:3085 msgid "Label" msgstr "Étiquette" #: e2_bookmark.c:584 e2_context_menu.c:615 e2_menu.c:993 e2_plugins.c:1280 #: e2_toolbar.c:2853 e2_toolbar.c:2920 e2_toolbar.c:3087 msgid "Icon" msgstr "Icône" #: e2_bookmark.c:586 e2_menu.c:997 e2_plugins.c:1282 e2_toolbar.c:2855 #: e2_toolbar.c:2922 e2_toolbar.c:3089 msgid "Tooltip" msgstr "Info-bulle" #: e2_bookmark.c:588 e2_plugins.c:1286 e2_vfs_dialog.c:1429 msgid "Path" msgstr "Chemin" #: e2_bookmark.c:596 msgid "open bookmark in other pane on middle-button click" msgstr "" "ouvrir le signet dans l'autre panneau en cliquant sur le bouton du milieu de " "la souris" #: e2_bookmark.c:597 msgid "" "Clicking the middle mouse button on a bookmark will open it in the other " "file pane" msgstr "" "Cliquer sur un signet avec le bouton du milieu de la souris l'ouvre dans " "l'autre panneau." #: e2_bookmark.c:601 msgid "focus file pane after opening a bookmark in it" msgstr "placer le focus sur le panneau après y avoir ouvert un signet" #: e2_bookmark.c:602 msgid "" "After opening a bookmark in the inactive file pane, that pane will become " "the active one" msgstr "" "Après avoir ouvert un signet dans le panneau inactif, ce panneau deviendra " "actif." #: e2_bookmark.c:608 msgid "confirm any delete of a selected bookmark" msgstr "confirmer la suppression d'un signet sélectionné" #: e2_bookmark.c:609 msgid "You will be asked to confirm, before deleting any bookmark" msgstr "Une confirmation vous sera demandée avant suppression d'un signet." #: e2_bookmark.c:613 msgid "confirm any delete of multiple bookmarks" msgstr "confirmer la suppression de plusieurs signets en même temps" #: e2_bookmark.c:614 msgid "" "You will be asked to confirm, before deleting any bookmark that has " "'children'" msgstr "" "Une confirmation vous sera demandée avant de supprimer tout signet qui a des " "fils." #: e2_button.c:25 e2p_upgrade.c:107 msgid "_OK" msgstr "_OK" #: e2_button.c:28 e2p_upgrade.c:116 msgid "_Cancel" msgstr "_Annuler" #: e2_button.c:30 msgid "_Yes" msgstr "_Oui" #: e2_button.c:33 msgid "_No" msgstr "_Non" #: e2_button.c:36 msgid "Yes to _all" msgstr "Oui pour _tous" #: e2_button.c:38 e2p_cpbar.c:731 e2p_cpbar.c:735 e2p_mvbar.c:805 #: e2p_mvbar.c:809 msgid "_Stop" msgstr "_Arrêter" #: e2_button.c:40 e2p_config.c:1161 e2p_config.c:1288 msgid "_Apply" msgstr "Appl_iquer" #: e2_button.c:43 msgid "_Apply to all" msgstr "Appliquer à _tous" #: e2_button.c:45 e2_tree_dialog.c:1115 e2p_thumbs.c:1117 msgid "_Refresh" msgstr "_Rafraîchir" #: e2_button.c:47 msgid "_Close" msgstr "_Fermer" #: e2_button.c:49 msgid "C_reate" msgstr "_Créer" #: e2_button.c:51 e2_option_tree.c:978 e2_permissions_dialog.c:458 #: e2p_acl.c:3728 msgid "_Remove" msgstr "_Supprimer" #: e2_cache.c:726 #, c-format msgid "" "%sThis file stores runtime configuration data for %s.\n" "%sThe file will be overwritten each time %s is shut down.\n" "\n" msgstr "" "%s Ce fichier stocke les informations de configuration courante de %s.\n" "%s Il sera écrasé à chaque fois que %s est fermé !\n" "\n" #: e2_cache.c:955 #, c-format msgid "Cannot write cache file %s - %s" msgstr "Impossible d'écrire dans le fichier cache : %s - %s" #: e2_cl_option.c:43 #, c-format msgid "usage: %s [option]\n" msgstr "usage : %s {option}\n" #: e2_cl_option.c:56 msgid "" "Program options:\n" "-1,--one=DIR set 1st pane's start directory to DIR\n" "-2,--two=DIR set 2nd pane's start directory to DIR\n" "-c,--config=DIR set config directory to DIR (default: ~/.config/" "emelfm2)\n" "-e,--encoding=TYPE set filesystem character encoding to TYPE\n" "-f,--fallback-encoding set fallback encoding (default: ISO-8859-1)\n" "-i,--ignore-problems ignore encoding/locale problems (at your own risk!)\n" "-l,--log-all maximise scope of error logging\n" "-m,--daemon run program as daemon\n" "-r,--run-at-start=CMD run command CMD at session start\n" "-s,--set-option=OPT set one-line gui option using config-file formatted " "OPT\n" "-t,--trash=DIR set trash directory to DIR (default: ~/.local/share/" "Trash/files)\n" "\n" "Help options:\n" "-h,--help show this help message\n" "-u,--usage display brief usage messsage\n" "-v,--version display version and build info\n" msgstr "" "Options du programme :\n" "-1,--one=DIR démarre avec DIR dans le panneau 1\n" "-2,--two=DIR démarre avec DIR dans le panneau 2\n" "-c,--config=DIR fixe le dossier de configuration à DIR (par défaut : " "~/.config/emelfm2)\n" "-e,--encoding=TYPE fixe l'encodage utilise par le système de fichiers à " "TYPE\n" "-f,--fallback-encoding fixe l'encodage par défaut (par défaut : ISO-8859-1)\n" "-i,--ignore-problems ignore les problèmes de locale et d'encodage (à vos " "risques et périls !)\n" "-l,--log-all fixe le niveau de traces d'erreur au maximum\n" "-m,--daemon lance le programme en tant que démon\n" "-r,--run-at-start=CMD commande à exécuter immédiatement après le démarrage\n" "-s,--set-option=OPT fixe une option en utilisant la même syntaxe que " "celle utilisée dans le fichier de configuration\n" "-t,--trash=DIR fixe le dossier de la corbeille (defaut : ~/.local/" "share/Trash/files)\n" "\n" "Options d'aide :\n" "-h,--help montre ce message d'aide\n" "-u,--usage affiche un bref mode d'emploi\n" "-v,--version affiche les informations de version et de " "construction\n" #: e2_cl_option.c:78 msgid "" "-d,--debug=[1-5] set debug level from 1 (low) to 5 (high)\n" "-x,--verbose display time/location info on debug messages\n" msgstr "" "-d,--debug=[1-5] fixe le niveau de déboguage de 1 (faible) à 5 " "(élevé)\n" "-x,--verbose affiche des information de date et de localisation " "sur la sortie de déboguage\n" #: e2_cl_option.c:91 #, c-format msgid "" "%s v. %s\n" "Licensed under the GPL\n" "Copyright (C) %s\n" "Build date: %s\n" "Build platform: GTK+ %d.%d.%d %s\n" msgstr "" "%s (v%s)\n" "Logiciel sous licence GPL.\n" "Copyright (C) %s\n" "Date de compilation : %s\n" "Plateforme de compilation : GTK+ %d.%d.%d %s \n" #: e2_cl_option.c:299 msgid "Startup options must begin with \"-\" or \"--\"\n" msgstr "Les options de démarrage doivent commencer par \"-\" ou \"--\"\n" #: e2_command.c:643 e2_command.c:1301 e2_command.c:1587 msgid "returned" msgstr "a renvoyé" #: e2_command.c:789 #, c-format msgid "Command '%s' - %s" msgstr "Commande '%s' - %s" #: e2_command.c:1066 #, c-format msgid "Error while launching '%s'" msgstr "Erreur au lancement de '%s'" #: e2_command.c:1092 msgid "Cannot find last child process" msgstr "Impossible de trouver le dernier processus fils" #: e2_command.c:1100 #, c-format msgid "Cannot find child process with pid %ld" msgstr "Impossible de trouver le processus fils avec le pid %ld" #: e2_command.c:1103 #, c-format msgid "Cannot communicate to process %ld" msgstr "Impossible de communiquer avec le processus %d" #: e2_command.c:1118 msgid "Failed writing to child" msgstr "Erreur d'écriture vers le processus fils" #: e2_command.c:1594 #, c-format msgid "Strange error: could not run '%s'" msgstr "Erreur étrange : impossible de lancer '%s'" #: e2_command.c:1617 #, c-format msgid "The process with pid %ld is not our child" msgstr "Le processus avec le pid %d n'est pas un processus fils" #: e2_command.c:1629 e2_command.c:1640 #, c-format msgid "Failed writing to child: %s" msgstr "Erreur d'écriture vers le processus fils : %s" #: e2_command.c:1854 msgid "nothing is waiting" msgstr "pas de liste d'attente" #: e2_command.c:1878 e2_command.c:1982 e2_command.c:2045 msgid "" msgstr "<éléments sélectionnés>" #: e2_command.c:1953 msgid "nothing is running" msgstr "aucun traitement en cours" #: e2_command.c:1957 msgid " pid || directory || command" msgstr " pid || dossier || commande" #: e2_command.c:2019 msgid "command || directory || result" msgstr "commande || dossier || résultat" #: e2_command.c:2051 e2_command.c:2063 msgid "OK" msgstr "OK" #: e2_command.c:2051 e2_command.c:2066 msgid "error" msgstr "erreur" #: e2_command.c:2321 #, c-format msgid "Cannot run '%s'" msgstr "Echec du lancement de %s" #: e2_command.c:2439 msgid "Failed to expand macros" msgstr "Impossible de développer les macros" #: e2_command.c:2587 #, c-format msgid "Failed parsing command '%s' - %s" msgstr "Erreur de syntaxe dans la commande '%s' - %s" #: e2_command.c:2675 #, c-format msgid "Cannot send \"%s\" to a child process" msgstr "Impossible d'envoyer \"%s\" à un processus fils" #: e2_command.c:2870 msgid "x terminal emulator:" msgstr "émulateur de terminal X :" #: e2_command.c:2871 msgid "" "This is the external command/application that will be be run when emelFM2 is " "asked to open a terminal" msgstr "" "C'est la commande externe qui sera exécutée quand il est demandé à emelFM2 " "d'ouvrir un terminal." #: e2_command.c:2874 msgid "use external file-viewer" msgstr "utiliser un afficheur de fichier externe" #: e2_command.c:2875 msgid "" "If activated, the command entered below will be run to view file content, " "instead of launching the internal viewer" msgstr "" "Si activé, la commande ci-dessous sera utilisée pour afficher le contenu du " "fichier, au lieu de lancer l'afficheur interne." #: e2_command.c:2878 msgid "viewer command:" msgstr "commande de l'afficheur :" #: e2_command.c:2879 msgid "" "This is a command to run an external application for viewing file content.\n" "The first selected item will be supplied as the first argument" msgstr "" "Commande à lancer pour afficher le contenu des fichiers dans un programme " "externe.\n" "Le premier fichier sélectionné sera fourni au programme comme premier " "argument" #: e2_command.c:2883 msgid "use external file-editor" msgstr "utiliser un éditeur de fichier externe" #: e2_command.c:2884 msgid "" "If activated, the command entered below will be run to edit file content, " "instead of launching the internal editor" msgstr "" "Si activé, la commande ci-dessous sera utilisée pour éditer le contenu du " "fichier, au lieu de lancer l'éditeur interne." #: e2_command.c:2887 msgid "editor command:" msgstr "commande de l'éditeur :" #: e2_command.c:2888 msgid "" "This is a command to run an external application for editing file content.\n" "The first selected item will be supplied as the first argument" msgstr "" "Commande à lancer pour éditer les fichiers dans un programme externe.\n" "Le premier fichier sélectionné sera fourni au programme comme premierargument" #: e2_command.c:2892 msgid "use external encoding converter" msgstr "utiliser un convertisseur d'encodage externe" #: e2_command.c:2893 msgid "" "If activated, the command entered below will be run, instead of using the " "internal conversion functions, to convert file character encoding when needed" msgstr "" "Si activé, la commande ci-dessous, et non les fonctions de conversion " "internes, sera utilisée pour convertir l'encodage du fichier si nécessaire." #: e2_command.c:2896 e2p_upgrade.c:744 e2p_upgrade.c:745 msgid "File encoding:" msgstr "Encodage de fichier :" #: e2_command.c:2897 msgid "converter command:" msgstr "commande du convertisseur :" #: e2_command.c:2898 msgid "" "A command which runs an external application to convert text encoding to UTF-" "8" msgstr "Commande à lancer pour convertir un encodage de texte en UTF-8" #: e2_command.c:2902 msgid "use aliases" msgstr "utiliser des alias" #: e2_command.c:2903 msgid "This is a general switch to turn on/off alias handling for commands" msgstr "" "Interrupteur général pour (dés)activer la prise en compte des alias dans les " "commandes" #: e2_command.c:2906 msgid "interpret 'relative' paths" msgstr "interpréter les chemins \"relatifs\"" #: e2_command.c:2907 msgid "" "This enables correct interpretation of paths containing '..' etc. These " "might be typed in, or attached to a button, say" msgstr "" "Ceci active l'interprétation correcte des chemins contenant '..' etc. Ils " "pourront être saisis ou rattachés à un bouton, par exemple." #: e2_command.c:2910 msgid "running commands survive shutdown" msgstr "faire survivre les commandes en cours à la fermeture" #: e2_command.c:2911 msgid "" "If activated, commands that are still running will not be terminated at end " "of emelFM2 session" msgstr "" "Si activé, les commandes en cours continueront à s'exécuter après la " "fermeture d'EmelFM2." #: e2_command.c:2915 msgid "watch priority" msgstr "surveillance des priorités" #: e2_command.c:2916 msgid "" "The watch priority of commands influences how fast program\n" "output is read from i/o channels. A too-high priority might\n" "decrease gui responsiveness and break automatic scrolling.\n" "(note: negative values mean high priority, positive values mean low priority)" msgstr "" "La surveillance des priorités des commandes influe sur la vitesse à " "laquelle\n" "la sortie des programmes est lue depuis les canaux d'entrée/sortie. Une " "priorité trop forte pourrait\n" "accroître le temps de réponse de l'interface utilisateur et empêcher le " "défilement automatique de fonctionner.\n" "(note: les valeurs négatives représentent les priorités fortes et les " "positives les priorités faibles)" #: e2_command.c:2925 msgid "stop after timeout" msgstr "arrêter après expiration d'un délai" #: e2_command.c:2926 msgid "" "If activated, each file operation will be terminated if not finished within " "the time-interval set below" msgstr "" "Si activé, chaque opération sur fichier sera interrompue si elle ne termine " "pas dans le temps spécifié ci-dessous" #: e2_command.c:2929 msgid "timeout interval" msgstr "délai maximum" #: e2_command.c:2930 msgid "The interval (seconds) allowed to complete any file operation" msgstr "" "Le nombre de secondes autorisé pour achever n'importe quelle opération sur " "des fichiers" #: e2_command.c:2933 msgid "confirm any delete" msgstr "confirmer toute suppression" #: e2_command.c:2934 msgid "" "If activated, you will be asked for confirmation before actually deleting " "anything" msgstr "" "Si activé, une confirmation vous sera demandée avant toute suppression de " "fichier ou dossier." #: e2_command.c:2936 msgid "confirm any overwrite" msgstr "confirmer tout écrasement" #: e2_command.c:2937 msgid "" "If activated, you will be asked for confirmation before actually overwriting " "anything" msgstr "" "Si activé, une confirmation vous sera demandée avant tout écrasement de " "fichier ou dossier." #: e2_command.c:2939 msgid "relative symlinks" msgstr "liens symboliques relatifs" #: e2_command.c:2940 msgid "" "This gives each created symlink a relative path to its source, like '../../" "/', instead of a full path referenced to /" msgstr "" "Crée pour chaque lien symbolique un chemin relatif vers le fichier pointé, " "comme '../..//', au lieu d'un chemin absolu à partir de /." #: e2_command_line.c:577 msgid "mounts" msgstr "monte" #: e2_command_line.c:579 msgid "all" msgstr "tous" #: e2_command_line.c:672 #, c-format msgid "Warning - process %s is not active" msgstr "Avertissement - le processus %s n'est pas actif" #: e2_command_line.c:988 msgid "show last" msgstr "montrer la dernière commande" #: e2_command_line.c:989 msgid "" "If activated, the last-entered command will be displayed, instead of an " "empty line" msgstr "" "Si activé, la dernière commande saisie sera affichée, au lieu d'une ligne " "vide." #: e2_command_line.c:994 e2_command_line.c:1041 msgid "maximum number of history entries" msgstr "nombre maximum d'entrées dans l'historique" #: e2_command_line.c:995 msgid "" "This is the largest number of command-line history entries that will be " "recorded" msgstr "" "Nombre maximum d'entrées de l'historique de la ligne de commande qui seront " "mémorisées" #: e2_command_line.c:998 e2_command_line.c:1046 msgid "double entries" msgstr "doubler les entrées" #: e2_command_line.c:999 e2_command_line.c:1047 msgid "" "This allows entries to be recorded more than once in the history list, so " "the last entry is always close to hand" msgstr "" "Autorise des entrées de l'historique à être enregistrées plusieurs fois, de " "façon à ce que la dernière entrée soit toujours à portée de main." #: e2_command_line.c:1002 e2_command_line.c:1051 msgid "cyclic list" msgstr "liste cyclique" #: e2_command_line.c:1003 e2_command_line.c:1052 msgid "" "When scanning the history list, cycle from either end around to the other " "end, instead of stopping" msgstr "" "Reboucle au début ou à la fin quand on arrive à une extrémité de " "l'historique, au lieu de s'arrêter." #: e2_command_line.c:1007 e2_command_line.c:1056 msgid "show as a menu" msgstr "afficher comme menu" #: e2_command_line.c:1008 msgid "" "If activated, the history entries will be presented as a menu. For most Gtk" "+2 themes, this will not be as attractive as the list view" msgstr "" "Si activé, les entrées de l'historique seront affichées comme un menu. Pour " "la plupart des thèmes GTK+2, ce sera moins agréable visuellement qu'une " "liste." #: e2_command_line.c:1015 msgid "append space after unique items" msgstr "ajouter un espace après les éléments uniques" #: e2_command_line.c:1016 msgid "" "This appends a 'space' character to the end of a unique successful match of " "a file" msgstr "" "Ceci ajoute un caractère espace après le nom de fichier quand un seul " "fichier correspond au modèle saisi." #: e2_command_line.c:1023 msgid "show last entry" msgstr "afficher la dernière entrée" #: e2_command_line.c:1024 msgid "" "If activated, the last-entered directory will be displayed, instead of an " "empty line" msgstr "" "Si activé, le dernier dossier saisi sera affiché, au lieu d'une ligne vide." #: e2_command_line.c:1028 msgid "show pathname as a tooltip" msgstr "afficher le chemin dans une info-bulle." #: e2_command_line.c:1029 msgid "" "If activated, the full directory pathname will display as a tooltip. This is " "useful when the path is too long for the normal display" msgstr "" "Si activé, le chemin complet du dossier sera affiché dans une info-bulle." "C'est utile quand le chemin est trop long pour l'affichage normal." #: e2_command_line.c:1034 msgid " only" msgstr " seulement" #: e2_command_line.c:1034 msgid "inserted" msgstr "insérée" #: e2_command_line.c:1034 msgid "selected" msgstr "sélectionnée" #: e2_command_line.c:1035 msgid "directory path completion" msgstr "complétion des chemins de dossier" #: e2_command_line.c:1036 msgid "This determines the mode of completion when keying a directory-path" msgstr "" "Ceci fixe le type de complétion lors de la saisie d'un chemin de dossier." #: e2_command_line.c:1042 msgid "" "This is the largest number of directory-line history entries that will be " "retained" msgstr "Nombre maximum d'éléments contenus dans l'historique des dossiers" #: e2_command_line.c:1057 msgid "" "If activated, the directory line history will be presented as a menu. For " "most for most Gtk+2 themes, this will not be as attractive as the list view" msgstr "" "Si activé, l'historique des dossiers sera affiché sous forme de menu. Pour " "la plupart des thèmes GTK+2, ce sera visuellement moins agréable qu'une " "liste." #: e2_config_dialog.c:175 e2_config_dialog.c:1484 msgid "configuration" msgstr "configuration" #: e2_config_dialog.c:658 msgid "Reverting to default configuration cannot be undone" msgstr "Le retour à la version sauvergardée ne peut être annulé" #: e2_config_dialog.c:781 e2_option_tree_context_menu.c:243 msgid "_Expand" msgstr "_Etendre" #: e2_config_dialog.c:782 msgid "Expand all rows" msgstr "étendre toutes les rangées" #: e2_config_dialog.c:783 e2_option_tree_context_menu.c:247 #: e2_tree_dialog.c:1113 msgid "C_ollapse" msgstr "_Masquer" #: e2_config_dialog.c:784 msgid "Collapse all rows" msgstr "Masquer toutes les rangées" #: e2_config_dialog.c:922 msgid "choose plugin" msgstr "choisir le greffon" #: e2_config_dialog.c:1001 #, c-format msgid "Choose font: %s" msgstr "Choisir la police : %s" #: e2_config_dialog.c:1041 msgid "abcd efgh ABCD EFGH" msgstr "abcd efgh ABCD EFGH" #: e2_config_dialog.c:1041 msgid "example:" msgstr "exemple :" #: e2_config_dialog.c:1136 msgid "Color data are not stored there" msgstr "les informations de couleur ne sont pas stockées ici" #: e2_config_dialog.c:1154 msgid "The current color descriptor is not valid" msgstr "Le descripteur de couleur courant n'est pas valide" #: e2_config_dialog.c:1161 msgid "Set filetype color" msgstr "fixer la couleur pour ce type de fichier" #: e2_config_dialog.c:1219 #, c-format msgid "Choose color: %s" msgstr "Choisir la couleur : %s" #: e2_config_dialog.c:1264 msgid "abCD" msgstr "abCD" #: e2_config_dialog.c:1264 msgid "currently:" msgstr "courante :" #: e2_config_dialog.c:1517 e2_filetype_dialog.c:872 msgid "Categories" msgstr "Catégories" #: e2_config_dialog.c:1631 e2_config_dialog.c:1658 e2p_find.c:2903 msgid "change" msgstr "changer" #: e2_config_dialog.c:1632 msgid "Click to open a font select dialog" msgstr "Cliquer pour ouvrir le dialogue de sélection de la police" #: e2_config_dialog.c:1659 msgid "Click to open a color selection dialog" msgstr "Cliquer pour ouvrir le dialogue de sélection de la couleur" #: e2_config_dialog.c:1771 msgid "_Default" msgstr "_Défaut" #: e2_config_dialog.c:1772 msgid "Revert all options to their default settings" msgstr "Ramène toutes les options à leurs valeurs par défaut." #: e2_config_dialog.c:1776 msgid "_Basic" msgstr "_Basique" #: e2_config_dialog.c:1777 msgid "Display only the basic configuration options" msgstr "N'afficher que les options de configuration les plus courantes." #: e2_config_dialog.c:1781 msgid "Ad_vanced" msgstr "A_vancé" #: e2_config_dialog.c:1782 msgid "Display all configuration options" msgstr "afficher toutes les options de configuration" #: e2_context_menu.c:209 e2_filetype.c:119 e2_filetype.c:299 e2_filetype.c:493 #: e2_output.c:718 e2_task.c:3192 e2_task_backend.c:1929 msgid "" msgstr "" #: e2_context_menu.c:273 e2_filetype.c:120 e2_filetype.c:301 e2_filetype.c:501 #: e2_output.c:757 e2_task.c:3198 msgid "" msgstr "" #: e2_context_menu.c:552 e2p_thumbs.c:1102 msgid "Open _with.." msgstr "Ouvrir _avec.." #: e2_context_menu.c:556 msgid "_Info" msgstr "_Info" #: e2_context_menu.c:558 msgid "_Actions" msgstr "_Actions" #: e2_context_menu.c:559 e2_dnd.c:726 e2_file_info_dialog.c:246 #: e2_filetype_dialog.c:691 e2_option_tree_context_menu.c:231 #: e2_toolbar.c:2816 e2_tree_dialog.c:1109 e2_view_dialog.c:946 #: e2p_cpbar.c:875 msgid "_Copy" msgstr "_Copier" #: e2_context_menu.c:560 e2_dnd.c:732 e2_toolbar.c:2818 e2p_mvbar.c:951 msgid "_Move" msgstr "_Déplacer" #: e2_context_menu.c:561 e2_dnd.c:738 e2_toolbar.c:2820 msgid "_Link" msgstr "_Lier" #: e2_context_menu.c:562 e2_toolbar.c:2824 msgid "_Trash" msgstr "_Corbeille" #: e2_context_menu.c:565 e2p_rename.c:1580 msgid "_Rename.." msgstr "_Renommer..." #: e2_context_menu.c:566 msgid "Change _owners.." msgstr "Changer le _propriétaire..." #: e2_context_menu.c:568 msgid "Change _permissions.." msgstr "Changer les _permissions..." #: e2_context_menu.c:570 msgid "Copy as.." msgstr "Copier comme..." #: e2_context_menu.c:571 msgid "Move as.." msgstr "Déplacer comme.." #: e2_context_menu.c:572 msgid "Link as.." msgstr "Lier comme.." #: e2_context_menu.c:573 msgid "_Plugins" msgstr "_Greffons" #: e2_context_menu.c:576 msgid "_Edit plugins.." msgstr "_Editer les greffons..." #: e2_context_menu.c:577 msgid "_User commands" msgstr "Commandes _utilisateur" #: e2_context_menu.c:578 msgid "Enter file name:" msgstr "Entrer un nom de fichier :" #: e2_context_menu.c:578 msgid "_Make new file.." msgstr "Nouveau _fichier" #: e2_context_menu.c:579 msgid "The files are different" msgstr "Les fichiers sont différents" #: e2_context_menu.c:579 msgid "The files are identical" msgstr "Les fichiers sont identiques" #: e2_context_menu.c:579 msgid "_Compare files" msgstr "_Comparer les fichiers" #: e2_context_menu.c:580 msgid "Compare _directories" msgstr "Comparer les _dossiers" #: e2_context_menu.c:582 msgid "_Remove spaces" msgstr "_Supprimer les espaces dans le nom" #: e2_context_menu.c:583 msgid "Enter the piece-size (in kB):" msgstr "Entrer la taille de l'élément (en ko)" # duplicated mnemonic #: e2_context_menu.c:583 msgid "_Split file.." msgstr "_Scinder le fichier..." #: e2_context_menu.c:584 msgid "Co_ncatenate files.." msgstr "Concaténer les fichiers..." #: e2_context_menu.c:584 msgid "Enter the name of the combined file:" msgstr "Entrez le nom du fichier résultant :" #: e2_context_menu.c:585 msgid "_Free space" msgstr "_Espace libre" #: e2_context_menu.c:585 msgid "percent free" msgstr "pourcent libre" #: e2_context_menu.c:587 msgid "_Edit user commands.." msgstr "_Éditer les commandes utilisateur..." #: e2_context_menu.c:588 e2_toolbar.c:2826 msgid "Ma_ke dir.." msgstr "Nouveau _dossier" #: e2_context_menu.c:590 msgid "_Bookmarks" msgstr "_Signets" #: e2_context_menu.c:591 e2_toolbar.c:2979 e2_toolbar.c:3044 msgid "Add _top" msgstr "Ajouter en _haut" #: e2_context_menu.c:593 e2_toolbar.c:2983 e2_toolbar.c:3048 msgid "Add _bottom" msgstr "Ajouter en _bas" #: e2_context_menu.c:595 msgid "_Edit bookmarks.." msgstr "_Éditer les signets..." #: e2_context_menu.c:597 msgid "_Edit filetype.." msgstr "_Éditer le type de fichier..." #: e2_context_menu.c:618 msgid "Shift" msgstr "Shift" #: e2_context_menu.c:620 msgid "Ctrl" msgstr "Ctrl" #: e2_context_menu.c:622 e2_keybinding.c:1148 e2_toolbar.c:2857 #: e2_toolbar.c:2924 e2_toolbar.c:3091 msgid "Action" msgstr "Action" #: e2_context_menu.c:625 e2_keybinding.c:1151 e2_toolbar.c:2860 #: e2_toolbar.c:2927 e2_toolbar.c:3094 msgid "Argument" msgstr "Argument" #: e2_date_filter_dialog.c:216 msgid "Display only the items:" msgstr "Afficher uniquement les éléments :" #: e2_date_filter_dialog.c:217 msgid "date filter" msgstr "filtre sur la date" #: e2_date_filter_dialog.c:225 e2p_glob.c:515 msgid "accessed since" msgstr "accédés depuis" #: e2_date_filter_dialog.c:225 e2p_glob.c:515 msgid "modified before" msgstr "modifiés avant" #: e2_date_filter_dialog.c:225 e2p_glob.c:515 msgid "modified since" msgstr "modifiés depuis" #: e2_date_filter_dialog.c:226 e2p_glob.c:516 msgid "accessed before" msgstr "accédés avant" #: e2_date_filter_dialog.c:226 e2p_glob.c:516 msgid "changed before" msgstr "changés avant" #: e2_date_filter_dialog.c:226 e2p_glob.c:516 msgid "changed since" msgstr "changés depuis" #: e2_dialog.c:684 e2_tree_dialog.c:1458 msgid "_Hidden" msgstr "_Caché" #: e2_dialog.c:686 msgid "Toggle display of hidden items" msgstr "Inverser l'affichage des éléments cachés" #: e2_dialog.c:831 msgid "user input" msgstr "saisie de l'utilisateur" #: e2_dialog.c:1001 msgid "older" msgstr "plus_ancien" #: e2_dialog.c:1003 msgid "newer" msgstr "plus_récent" #: e2_dialog.c:1012 msgid "existing" msgstr "existant" #: e2_dialog.c:1027 #, c-format msgid "" "Remove all contents of %s\n" "%s ?" msgstr "" "Supprimer tout le contenu de %s\n" "%s ?" #: e2_dialog.c:1032 #, c-format msgid "" "Overwrite %s %s\n" "in %s ?" msgstr "" "Écraser %s %s\n" "dans %s ?" #: e2_dialog.c:1039 e2_dialog.c:1079 e2_dialog.c:1108 e2_edit_dialog.c:696 #: e2_edit_dialog.c:928 e2_task.c:2117 e2p_crypt.c:430 e2p_rename.c:899 msgid "confirm" msgstr "confirmer" #: e2_dialog.c:1106 #, c-format msgid "%s is taking a long time. Continue waiting ?" msgstr "%s prend beaucoup de temps. Voulez-vous continuer à attendre ?" #: e2_dialog.c:1113 msgid "_Quiet" msgstr "_Silencieux" #: e2_dialog.c:1114 msgid "Don't ask any more" msgstr "Ne plus demander" #: e2_dialog.c:1118 #, c-format msgid "Cancel the %s" msgstr "Annuler le %s" #: e2_dialog.c:1122 msgid "Wait some more" msgstr "Attendre encore un peu" #: e2_dialog.c:1224 msgid "center" msgstr "centrer" #: e2_dialog.c:1224 msgid "mouse" msgstr "souris" #: e2_dialog.c:1224 e2_output.c:2442 e2_toolbar.c:2729 e2p_acl.c:3513 msgid "none" msgstr "aucun" #: e2_dialog.c:1225 msgid "always center" msgstr "toujours centrer" #: e2_dialog.c:1225 msgid "center on parent" msgstr "centrer sur le parent" #: e2_dialog.c:1227 msgid "dialog position" msgstr "position du dialogue" #: e2_dialog.c:1228 msgid "This determines the position where dialog windows will pop up" msgstr "Détermine la position des boîtes de dialogue lorsqu'elles s'affichent." #: e2_dnd.c:749 msgid "C_ancel" msgstr "A_nnuler" #: e2_edit_dialog.c:119 e2_edit_dialog.c:344 msgid "file save as" msgstr "enregistrer sous" #: e2_edit_dialog.c:233 #, c-format msgid "Cannot save %s in its original encoding %s. Reverting to UTF-8" msgstr "" "Impossible de sauvegarder %s avec son encodage original %s. Sauvegarde en " "UTF-8." #: e2_edit_dialog.c:240 #, c-format msgid "Encoding conversion failed for %s" msgstr "La conversion de l'encodage de %s a échoué" #: e2_edit_dialog.c:312 #, c-format msgid "Original encoding of '%s' is unknown, now saved as UTF-8" msgstr "L'encodage original de %s est inconnu, il sera sauvegardé en UTF-8" #: e2_edit_dialog.c:412 msgid "Reverting to saved version cannot be undone" msgstr "Le retour à la version sauvegardée ne peut être annulé" #: e2_edit_dialog.c:477 #, c-format msgid "Cannot initialize spell checking: %s" msgstr "Impossible d'initialiser la vérification d'orthographe : %s" #: e2_edit_dialog.c:524 msgid "choose language" msgstr "choisir la langue" #: e2_edit_dialog.c:525 msgid "Enter the spell-checker language (like en_CA):" msgstr "Entrer la langue du correcteur orthographique (par exemple fr_FR) :" #: e2_edit_dialog.c:535 #, c-format msgid "Cannot set speller language to %s (%s)" msgstr "Impossible de fixer la langue du correcteur orthographique à %s (%s)" #: e2_edit_dialog.c:696 msgid "Replace this one ?" msgstr "Remplace celui-là ?" #: e2_edit_dialog.c:810 msgid "Spelling suggestions" msgstr "Orthographes suggérées" #: e2_edit_dialog.c:817 e2_edit_dialog.c:1538 e2_vfs_dialog.c:1025 #: e2p_config.c:1100 msgid "_Save" msgstr "Enregi_strer" #: e2_edit_dialog.c:818 msgid "Save the file" msgstr "Sauvegarde le fichier" #: e2_edit_dialog.c:820 msgid "Save as.." msgstr "E_nregistrer sous..." #: e2_edit_dialog.c:821 msgid "Save the file with a new name" msgstr "Enregistrer le fichier sous un nouveau nom" #: e2_edit_dialog.c:823 msgid "Save se_lection.." msgstr "Enregistrer la sé_lection..." #: e2_edit_dialog.c:824 msgid "Save the selected text" msgstr "Sauvegarde le texte sélectionné" #: e2_edit_dialog.c:829 e2_view_dialog.c:952 msgid "Print file" msgstr "Imprimer le fichier" #: e2_edit_dialog.c:829 e2_view_dialog.c:952 msgid "Print selected text" msgstr "Imprimer le texte sélectionné" #: e2_edit_dialog.c:830 e2_view_dialog.c:953 msgid "_Print.." msgstr "_Imprimer" #: e2_edit_dialog.c:834 msgid "R_efresh" msgstr "Ra_fraîchir" #: e2_edit_dialog.c:835 msgid "Reload the file being edited" msgstr "Recharger le fichier en cours d'édition" #: e2_edit_dialog.c:838 e2_toolbar.c:2889 e2p_find.c:3667 msgid "_Find.." msgstr "Ch_ercher..." #: e2_edit_dialog.c:839 msgid "Find matching text" msgstr "Recherche le texte correspondant" #: e2_edit_dialog.c:840 msgid "_Replace.." msgstr "_Remplacer..." #: e2_edit_dialog.c:841 msgid "Find and replace matching text" msgstr "Cherche et remplace le texte correspondant" #: e2_edit_dialog.c:843 e2_edit_dialog.c:1549 e2_output.c:1346 #: e2_view_dialog.c:1579 msgid "_Hide" msgstr "Cac_her" #: e2_edit_dialog.c:844 e2_view_dialog.c:1587 msgid "Hide the search options bar" msgstr "Cache la barre d'options de recherche" #: e2_edit_dialog.c:847 e2_edit_dialog.c:1550 msgid "_Undo" msgstr "Ann_uler" #: e2_edit_dialog.c:848 msgid "Undo last change" msgstr "Annule la dernière modification" #: e2_edit_dialog.c:852 e2_edit_dialog.c:1552 msgid "Re_do" msgstr "Refa_ire" #: e2_edit_dialog.c:853 msgid "Reverse last undo" msgstr "Refait la dernière modification annulée" #: e2_edit_dialog.c:858 msgid "_Check spelling" msgstr "_Vérifier l'orthographe" #: e2_edit_dialog.c:859 msgid "Flag mis-spelt words" msgstr "Marquer les mots mal orthographiés" #: e2_edit_dialog.c:861 msgid "_Clear spellcheck" msgstr "_Effacer la vérification d'orthographe" #: e2_edit_dialog.c:862 msgid "Remove all spell-check flags" msgstr "Retire toutes les marques de vérification d'orthographes" #: e2_edit_dialog.c:867 msgid "_Language.." msgstr "_Langue..." #: e2_edit_dialog.c:868 msgid "Set spell-checker language" msgstr "Définir la langue du vérificateur d'orthographe" # this is a find-toolbar button label - keep it as short as possible ! #: e2_edit_dialog.c:872 msgid "_Wrap" msgstr "Re_tour à la ligne" #: e2_edit_dialog.c:880 e2_view_dialog.c:1593 msgid "If activated, text in the window will be word-wrapped" msgstr "Si activé, le retour à la ligne sera automatique." #: e2_edit_dialog.c:883 msgid "Se_ttings" msgstr "C_onfiguration" #: e2_edit_dialog.c:884 e2_view_dialog.c:959 msgid "Open the configuration dialog at the options page" msgstr "Ouvre le dialogue de configuration à la page des options" #: e2_edit_dialog.c:928 msgid "Save modified file ?" msgstr "Sauvegarde le fichier modifié ?" #: e2_edit_dialog.c:1416 msgid "editing file" msgstr "édition du fichier" #: e2_edit_dialog.c:1491 msgid "Replacements" msgstr "Remplacements" #: e2_edit_dialog.c:1495 msgid "If activated, the next match will be sought after each replacement" msgstr "" "Si activé, après chaque remplacement, la correspondance suivante sera " "recherchée." #: e2_edit_dialog.c:1495 msgid "r_epeat" msgstr "répét_er" #: e2_edit_dialog.c:1498 msgid "If activated, all matches will be replaced at once" msgstr "" "Si activé, toutes les correspondances seront remplacées d'un seul coup." #: e2_edit_dialog.c:1498 msgid "_all" msgstr "_tous" #: e2_edit_dialog.c:1501 msgid "If activated, confirmation will be sought when \"replacing all\"" msgstr "Si activé, une confirmation sera demandée avant de tout remplacer." #: e2_edit_dialog.c:1501 msgid "co_nfirm" msgstr "co_nfirmer" #: e2_edit_dialog.c:1509 e2_view_dialog.c:1571 msgid "not found" msgstr "non trouvé" #: e2_edit_dialog.c:1516 msgid "_Replace" msgstr "_Remplacer" #: e2_edit_dialog.c:1524 msgid "Replace this match" msgstr "Remplacer celui-ci" #: e2_edit_dialog.c:1527 e2_view_dialog.c:1596 e2p_find.c:3627 msgid "_Find" msgstr "_Chercher" #: e2_edit_dialog.c:1535 e2_view_dialog.c:1605 msgid "Find the next match" msgstr "Chercher la correspondance suivante" #: e2_edit_dialog.c:1705 msgid "backup when saving" msgstr "faire une copie de sauvegarde" #: e2_edit_dialog.c:1706 msgid "" "When saving an edited file, an existing file with the same name will be " "renamed" msgstr "" "Lors de l'enregistrement d'un fichier modifié, tout fichier existant du même " "nom sera préalablement renommé." #: e2_file_info_dialog.c:207 e2_file_info_dialog.c:210 #: e2_file_info_dialog.c:446 e2_file_info_dialog.c:478 #: e2_file_info_dialog.c:499 msgid "Type:" msgstr "Type :" #: e2_file_info_dialog.c:215 msgid "Item:" msgstr "Élément :" #: e2_file_info_dialog.c:217 e2_file_info_dialog.c:645 msgid "Size:" msgstr "Taille :" #: e2_file_info_dialog.c:218 e2_file_info_dialog.c:672 msgid "User:" msgstr "Utilisateur :" #: e2_file_info_dialog.c:219 e2_file_info_dialog.c:680 msgid "Group:" msgstr "Groupe : " #: e2_file_info_dialog.c:220 e2_file_info_dialog.c:692 msgid "Permissions:" msgstr "Permissions :" #: e2_file_info_dialog.c:221 e2_file_info_dialog.c:703 msgid "Accessed:" msgstr "Accédé :" #: e2_file_info_dialog.c:222 e2_file_info_dialog.c:717 msgid "Modified:" msgstr "Modifié :" #: e2_file_info_dialog.c:223 e2_file_info_dialog.c:731 msgid "Changed:" msgstr "Changé :" #: e2_file_info_dialog.c:247 msgid "Copy displayed data" msgstr "Informations affichées pendant la copie" #: e2_file_info_dialog.c:344 msgid "file info" msgstr "Info fichier" #: e2_file_info_dialog.c:365 msgid "Virtual file" msgstr "Fichier virtuel" #: e2_file_info_dialog.c:381 e2_file_info_dialog.c:497 e2_utf8.c:339 #: e2_utf8.c:369 e2_utf8.c:397 e2_utf8.c:427 msgid "Unknown" msgstr "Inconnu" #: e2_file_info_dialog.c:464 e2p_acl.c:3609 e2p_crypt.c:2798 msgid "Directory" msgstr "Dossier" #: e2_file_info_dialog.c:487 msgid "Symbolic Link" msgstr "Lien Symbolique" #: e2_file_info_dialog.c:489 msgid "Character Device" msgstr "Périphérique de caractère" #: e2_file_info_dialog.c:491 msgid "Block Device" msgstr "Périphérique de bloc" #: e2_file_info_dialog.c:493 msgid "FIFO Pipe" msgstr "Tube FIFO" #: e2_file_info_dialog.c:495 msgid "Socket" msgstr "Socket" #: e2_file_info_dialog.c:523 #, c-format msgid "to %s" msgstr "vers %s" #: e2_file_info_dialog.c:571 msgid "(which is missing)" msgstr "(qui est manquant(e))" #: e2_file_info_dialog.c:573 msgid "(which is itself)" msgstr "(qui est lui-même)" #: e2_file_info_dialog.c:588 #, c-format msgid "and ultimately to %s" msgstr "et enfin vers %s" #: e2_file_info_dialog.c:602 msgid "(Cannot resolve the link)" msgstr "(Impossible de résoudre le lien)" #: e2_file_info_dialog.c:623 msgid "Encoding:" msgstr "Encodage:" #: e2_file_info_dialog.c:623 e2_file_info_dialog.c:627 #: e2_file_info_dialog.c:633 msgid "Mime:" msgstr "Type mime :" #: e2_file_info_dialog.c:666 e2_size_filter_dialog.c:192 e2p_du.c:170 #: e2p_find.c:2781 e2p_glob.c:501 msgid "bytes" msgstr "octets" #: e2_file_info_dialog.c:711 msgid "Item was last opened, run, read etc" msgstr "L'élément fut dernièrement ouvert" #: e2_file_info_dialog.c:725 msgid "Content of the item was last altered" msgstr "Le contenu fut dernièrement modifié" #: e2_file_info_dialog.c:739 msgid "Property of the item (name, permission, owner etc) was last altered" msgstr "" "Les propriétés de l'élément (nom, permission, propriétaire, etc.) furent " "dernièrement modifiées" #: e2_filelist.c:261 msgid "Something is wrong with the auto-refresh mechanism" msgstr "Il y a un problème avec le mécanisme de rafraîchissement automatique" #: e2_filelist.c:773 e2_toolbar.c:1621 msgid "k" msgstr "k" #: e2_filelist.c:778 e2_toolbar.c:1626 msgid "M" msgstr "M" #: e2_filetype.c:121 e2_filetype.c:307 e2_filetype.c:502 msgid "" msgstr "" #: e2_filetype.c:491 e2p_du.c:220 msgid "directories" msgstr "dossiers" #: e2_filetype.c:495 e2_filetype.c:602 e2_filetype.c:616 e2_filetype.c:631 #: e2_filetype.c:645 e2_filetype.c:680 e2_filetype_dialog.c:1201 #: e2_output.c:1397 e2_vfs_dialog.c:1029 e2_vfs_dialog.c:1571 msgid "_Open" msgstr "_Ouvrir" #: e2_filetype.c:496 e2_filetype.c:603 e2_filetype.c:617 e2_filetype.c:632 #: e2_filetype.c:646 e2_filetype.c:681 msgid "O_pen in other" msgstr "O_uvrir dans l'autre panneau" #: e2_filetype.c:497 msgid "_Mount" msgstr "_Monter" #: e2_filetype.c:498 msgid "_Unmount" msgstr "_Démonter" #: e2_filetype.c:499 msgid "executables" msgstr "exécutables" #: e2_filetype.c:505 e2_filetype_dialog.c:1198 e2p_upgrade.c:742 #: e2p_upgrade.c:743 msgid "_Run" msgstr "_Lancer" #: e2_filetype.c:508 e2_filetype.c:691 msgid "Edit _with.." msgstr "Éditer _avec..." #: e2_filetype.c:508 e2_filetype.c:691 e2p_upgrade.c:746 e2p_upgrade.c:747 msgid "Editor command:" msgstr "commande d'édition :" #: e2_filetype.c:509 e2_filetype.c:543 e2_filetype.c:694 #: e2_filetype_dialog.c:1188 e2_output.c:1394 msgid "_Edit" msgstr "_Éditer" #: e2_filetype.c:510 e2p_find.c:318 msgid "office documents" msgstr "documents bureautique" #: e2_filetype.c:516 e2_filetype.c:553 msgid "_Openoffice" msgstr "_OpenOffice.org" #: e2_filetype.c:517 msgid "HTML documents" msgstr "documents HTML" #: e2_filetype.c:522 msgid "_Firefox" msgstr "_Firefox" #: e2_filetype.c:523 msgid "_Mozilla" msgstr "_Mozilla" #: e2_filetype.c:524 msgid "_Lynx" msgstr "_Lynx" #: e2_filetype.c:525 msgid "_Opera" msgstr "_Opera" #: e2_filetype.c:526 msgid "PDF documents" msgstr "documents PDF" #: e2_filetype.c:532 msgid "postscript documents" msgstr "documents postscript" #: e2_filetype.c:537 msgid "text documents" msgstr "documents texte" #: e2_filetype.c:542 msgid "View" msgstr "Voir" #: e2_filetype.c:547 msgid "spreadsheets" msgstr "feuilles de calcul" #: e2_filetype.c:554 msgid "audio files" msgstr "fichiers audio" #: e2_filetype.c:565 msgid "image files" msgstr "fichiers images" #: e2_filetype.c:585 msgid "video files" msgstr "fichiers vidéo" #: e2_filetype.c:595 msgid "plain tarballs" msgstr "archives simple" #: e2_filetype.c:599 e2_filetype.c:605 e2_filetype.c:619 e2_filetype.c:634 #: e2_filetype.c:648 msgid "Unpack" msgstr "_Désarchiver" #: e2_filetype.c:600 msgid "Unpack in other pane" msgstr "Désarchiver dans l'_autre panneau" #: e2_filetype.c:607 e2_filetype.c:621 e2_filetype.c:636 e2_filetype.c:650 msgid "_List contents" msgstr "_Contenu de la liste" #: e2_filetype.c:608 msgid "gzip tarballs" msgstr "archives gzip" #: e2_filetype.c:613 e2_filetype.c:628 e2p_unpack.c:526 msgid "_Unpack" msgstr "_Désarchiver" #: e2_filetype.c:614 e2_filetype.c:629 msgid "Unpack in _other pane" msgstr "Désarchiver dans l'_autre panneau" #: e2_filetype.c:622 msgid "bzip2 tarballs" msgstr "archives bzip2" #: e2_filetype.c:637 msgid "zip archives" msgstr "archives zip" #: e2_filetype.c:641 msgid "_Unzip" msgstr "_Dézipper" #: e2_filetype.c:643 msgid "Unzip in _other pane" msgstr "Dézipper dans l'_autre panneau" #: e2_filetype.c:673 msgid "RPM packages" msgstr "paquetages RPM" #: e2_filetype.c:677 msgid "In_formation" msgstr "In_formation" #: e2_filetype.c:678 msgid "_Install" msgstr "_Installer" #: e2_filetype.c:683 msgid "source code files" msgstr "fichiers source" #: e2_filetype.c:695 msgid "object files" msgstr "fichiers objet" #: e2_filetype.c:701 msgid "_View symbols" msgstr "_Voir les symboles" #: e2_filetype.c:727 e2_keybinding.c:1142 msgid "Category" msgstr "Categorie" #: e2_filetype.c:732 e2_menu.c:999 msgid "Command" msgstr "Commande" #: e2_filetype.c:739 msgid "case-insensitive filetypes" msgstr "type de fichier non sensible à la casse" #: e2_filetype.c:740 msgid "" "This causes text-case to always be ignored when matching a file-extension" msgstr "La casse de l'extension sera ignorée." #: e2_filetype_dialog.c:443 msgid "new" msgstr "nouveau" #: e2_filetype_dialog.c:689 e2_option_tree_context_menu.c:227 msgid "Cu_t" msgstr "Cou_per" #: e2_filetype_dialog.c:690 msgid "Cut selected row(s)" msgstr "Couper les lignes sélectionnées" #: e2_filetype_dialog.c:692 msgid "Copy selected row(s)" msgstr "Copier les lignes sélectionnées" #: e2_filetype_dialog.c:697 e2_option_tree_context_menu.c:235 msgid "_Paste" msgstr "_Coller" #: e2_filetype_dialog.c:698 e2_option_tree_context_menu.c:236 msgid "Paste previously copied or cut row(s) after current row" msgstr "" "Colle après la ligne courante la ou les lignes précédemment copiées ou " "coupées" #: e2_filetype_dialog.c:702 e2_option_tree_context_menu.c:256 #: e2_permissions_dialog.c:454 e2p_acl.c:3725 msgid "_Add" msgstr "_Ajouter" #: e2_filetype_dialog.c:703 e2_option_tree_context_menu.c:257 msgid "Add a row after the current one" msgstr "Ajoute une ligne après la ligne courante" #: e2_filetype_dialog.c:705 e2_filetype_dialog.c:1002 e2_option_tree.c:969 #: e2_option_tree_context_menu.c:264 e2_toolbar.c:386 e2_toolbar.c:2998 #: e2_toolbar.c:3059 e2_toolbar.c:3065 msgid "_Up" msgstr "_Haut" #: e2_filetype_dialog.c:706 e2_option_tree_context_menu.c:265 msgid "Move selected row up" msgstr "Déplace la ligne sélectionnée vers le haut" #: e2_filetype_dialog.c:707 e2_filetype_dialog.c:1008 e2_option_tree.c:972 #: e2_option_tree_context_menu.c:267 e2_toolbar.c:388 msgid "_Down" msgstr "_Bas" #: e2_filetype_dialog.c:708 e2_option_tree_context_menu.c:268 msgid "Move selected row down" msgstr "Déplace la ligne sélectionnée vers le bas" #: e2_filetype_dialog.c:834 msgid "edit filetypes" msgstr "editer le type de fichier" #: e2_filetype_dialog.c:903 msgid "Extensions" msgstr "Extensions" #: e2_filetype_dialog.c:928 msgid "Labels" msgstr "Etiquettes" #: e2_filetype_dialog.c:937 msgid "Commands" msgstr "Commandes" #: e2_filetype_dialog.c:1006 e2_option_tree.c:970 msgid "Move the selected row one place up" msgstr "Déplace la ligne sélectionnée vers le haut" #: e2_filetype_dialog.c:1012 e2_option_tree.c:973 msgid "Move the selected row one place down" msgstr "Deplace la ligne sélectionnée vers le bas" #: e2_filetype_dialog.c:1014 e2_option_tree.c:980 e2_vfs_dialog.c:1557 #: e2p_acl.c:3795 msgid "Add a_fter" msgstr "Ajouter _après" #: e2_filetype_dialog.c:1018 msgid "Add a row after the currently selected one" msgstr "Ajoute une ligne après la ligne sélectionnée" #: e2_filetype_dialog.c:1157 msgid "ambiguous filetype" msgstr "type de fichier ambigu" #: e2_filetype_dialog.c:1157 msgid "unrecognised filetype" msgstr "type de fichier non reconnu" #: e2_filetype_dialog.c:1162 #, c-format msgid "" "What would you like to do with\n" "%s\n" "in %s ?" msgstr "" "Que voulez-vous faire avec\n" "%s\n" "dans %s ?" #: e2_filetype_dialog.c:1173 msgid "_Add.." msgstr "_Ajouter..." #: e2_filetype_dialog.c:1174 msgid "" "Create a new filetype for this extension, or add it to an existing filetype" msgstr "" "Crée un nouveau type de fichier pour cette extension, ou l'ajoute à un type " "existant" #: e2_filetype_dialog.c:1189 msgid "Edit the file" msgstr "Lire le fichier" #: e2_filetype_dialog.c:1199 msgid "Execute the item" msgstr "Exécuter l'élément" #: e2_filetype_dialog.c:1202 msgid "Open with the default application" msgstr "Ouvrir avec l'application par défaut" #: e2_filetype_dialog.c:1206 msgid "_Open.." msgstr "_Ouvrir..." #: e2_filetype_dialog.c:1207 msgid "Enter a command with which to open the file" msgstr "Entrez la commande avec laquelle vous souhaitez ouvrir le fichier" #: e2_fileview.c:155 e2_ownership_dialog.c:217 e2_permissions_dialog.c:327 #: e2_plugins.c:1284 e2p_acl.c:3467 e2p_crypt.c:2805 e2p_times.c:697 msgid "Filename" msgstr "Nom" #: e2_fileview.c:156 msgid "Size" msgstr "Taille" #: e2_fileview.c:157 e2_permissions_dialog.c:386 e2p_acl.c:3573 msgid "Permissions" msgstr "Permissions" #: e2_fileview.c:158 msgid "Owner" msgstr "Propriétaire" #: e2_fileview.c:159 e2_permissions_dialog.c:355 e2_permissions_dialog.c:410 #: e2p_acl.c:229 e2p_acl.c:2093 e2p_acl.c:3495 msgid "Group" msgstr "Groupe" #: e2_fileview.c:160 msgid "Modified" msgstr "Modifié" #: e2_fileview.c:161 e2p_times.c:716 msgid "Accessed" msgstr "Accédé" #: e2_fileview.c:162 msgid "Changed" msgstr "Changé" #: e2_fs.c:711 #, c-format msgid "'%s' is not a directory" msgstr "'%s' n'est pas un dossier" #: e2_fs.c:720 #, c-format msgid "Cannot access directory '%s' - No permission" msgstr "Impossible d'accéder au dossier '%s' - non permis" #: e2_fs.c:725 #, c-format msgid "Directory '%s' does not exist" msgstr "Le dossier '%s' n'existe pas" #: e2_fs.c:1293 e2_fs.c:1378 msgid "Reading directory data" msgstr "Lecture du dossier en cours" #: e2_fs.c:1294 msgid "directory read" msgstr "lecture du dossier" #: e2_fs.c:2082 #, c-format msgid "Cannot open '%s' for writing - %s" msgstr "Impossible d'ouvrir %s en écriture - %s" #: e2_fs.c:2127 #, c-format msgid "Error writing file '%s' - %s" msgstr "Erreur à l'écriture du fichier '%s' - %s" #: e2_fs.c:2243 e2_fs.c:2289 e2_fs.c:2514 e2_view_dialog.c:491 #: e2p_config.c:652 e2p_crypt.c:1720 e2p_dircmp.c:440 #, c-format msgid "Error reading file %s" msgstr "Erreur à la lecture du fichier %s" #: e2_fs.c:2272 e2_fs.c:2465 e2_fs_walk.c:311 e2_fs_walk.c:471 #: e2_fs_walk.c:700 e2_output.c:825 e2_task_backend.c:1261 e2p_acl.c:1808 #: e2p_acl.c:1882 #, c-format msgid "Cannot get information about %s" msgstr "Impossible d'obtenir d'information à propos de %s" #: e2_fs.c:2279 #, c-format msgid "Cannot open file %s" msgstr "Impossible d'ouvrir le fichier %s" #: e2_fs.c:2350 e2_fs.c:2454 #, c-format msgid "Cannot create file %s" msgstr "Impossible de créer le fichier %s" #: e2_fs.c:2362 e2_fs.c:2529 e2p_crypt.c:1752 #, c-format msgid "Error writing file %s" msgstr "Erreur à l'écriture du fichier %s" #: e2_fs.c:2440 e2p_crypt.c:867 e2p_crypt.c:1088 e2p_dircmp.c:408 #, c-format msgid "Cannot open '%s' for reading" msgstr "Impossible d'ouvrir '%s' en lecture" #: e2_fs.c:2589 #, c-format msgid "Cannot open pipe for command '%s'" msgstr "Impossible d'ouvrir un tube pour la commande '%s'" #: e2_fs.c:2612 #, c-format msgid "Command %s failed: not enough memory" msgstr "Echec de la commande %s. Mémoire insuffisante." #: e2_fs.c:2805 msgid "-rwxrwxrwx" msgstr "-rwxrwxrwx" #: e2_fs.c:2810 msgid "ldbcfs" msgstr "ldbcfs" #: e2_fs.c:2815 msgid "TtSs" msgstr "TtSs" #: e2_fs_walk.c:176 #, c-format msgid "Cannot change anything in %s" msgstr "Impossible de changer quoique ce soit de %s" #: e2_fs_walk.c:176 e2_fs_walk.c:652 e2_task_backend.c:160 e2p_find.c:1766 #: e2p_find.c:2337 e2p_times.c:359 #, c-format msgid "Cannot change permissions of %s" msgstr "Impossible de changer les permissions de %s" #: e2_fs_walk.c:333 e2_fs_walk.c:341 #, c-format msgid "Directory %s not opened" msgstr "Dossier %s non ouvert" #: e2_fs_walk.c:386 #, c-format msgid "Cannot open directory %s" msgstr "Impossible d'ouvrir le dossier %s" #: e2_keybinding.c:840 #, c-format msgid "Cannot find a key binding named %s" msgstr "Impossible de trouver le raccourci clavier %s" #: e2_keybinding.c:850 e2_option.c:1089 msgid "key bindings" msgstr "raccourcis clavier" #: e2_keybinding.c:1062 msgid "Now press one of h,m,d\\n" msgstr "Appuyer maintenant sur l'une des touches h,m,d\\n" #: e2_keybinding.c:1144 msgid "Key" msgstr "Touche" #: e2_keybinding.c:1146 msgid "Continue" msgstr "Continuer" #: e2_keybinding.c:1160 msgid "chained keybindings timeout (ms)" msgstr "expiration du délai pour les raccourcis chaînés (ms)" #: e2_keybinding.c:1161 msgid "" "This sets the time limit (in milliseconds) for accepting 'chained' " "keybindings" msgstr "" "Ceci fixe le délai limite (en millisecondes) pour accepter les raccourcis " "claviers enchaînés." #: e2_main.c:231 #, c-format msgid "Your current locale is '%s'.\n" msgstr "Votre localisation courante est '%s'.\n" #: e2_main.c:233 #, c-format msgid "" "You have set the environment variable G_BROKEN_FILENAMES, which\n" "causes GTK+ to convert filename encoding, from the one specified\n" "by the system locale, to UTF-8.\n" "However, you have not set a system locale. Please do so, by setting\n" "the environment variable LANG or LC_CTYPE!\n" msgstr "" "Vous avez positionné la variable d'environnement G_BROKEN_FILENAMES, quiest\n" "censé convertir en UTF-8 les noms de fichiers qui codés avec la localisation " "système actuelle.\n" "toutefois, vous n'avez pas fixé la valeur de cette localisation. Merci d'y " "remédier en affectant une valeur aux variables d'environnement LANG ou " "LC_CTYPE !\n" #: e2_main.c:241 #, c-format msgid "" "(Note: There is a command line option -i/--ignore-problems, but use it\n" "at your own risk!)\n" msgstr "" "(Note: Il existe une option de la ligne de commande -i/--ignore-problems, " "mais si vous l'utilisez, c'est\n" "à vos risques et périls !)\n" #: e2_main.c:246 #, c-format msgid "" "%s will ignore locale problems when reading filenames because\n" "--ignore-problems/-i has been set. This might result in segfaults and all\n" "kind of problems. You really should set a system locale with the\n" "LANG or LC_CTYPE environment variable.\n" msgstr "" "%s ignorera les problèmes de localisation dans les noms de fichiers, parce " "que l'option\n" "--ignore-problems/-i a été positionnée. Cela peut provoquer des erreurs de " "segmentation et toutes sortes de problèmes.\n" "Vous devriez fixer une valeur de localisation système en affectant une " "valeur aux variables d'environnement\n" "LANG ou LC_CTYPE.\n" #: e2_main.c:342 msgid "emelFM2" msgstr "emelFM2" #: e2_main.c:558 #, c-format msgid "%u process(es) are running" msgstr "Le ou les processus %u sont en cours" #: e2_menu.c:745 msgid "no children" msgstr "pas de fils" #: e2_menu.c:766 msgid "_Name filter" msgstr "Filtre sur le _nom" #: e2_menu.c:768 msgid "_Size filter" msgstr "Filtre sur la _taille" #: e2_menu.c:770 msgid "_Date filter" msgstr "Filtre sur la _date" #: e2_menu.c:773 msgid "_Directories too" msgstr "_Dossiers aussi" #: e2_menu.c:780 msgid "_Remove all filters" msgstr "_Supprimer tous les filtres" #: e2_menu.c:990 e2_plugins.c:1276 msgid "Menu" msgstr "Menu" #: e2_mkdir_dialog.c:58 msgid "new directory" msgstr "nouveau dossier" #: e2_mkdir_dialog.c:332 msgid "yes" msgstr "oui" #: e2_mkdir_dialog.c:339 msgid "no" msgstr "non" #: e2_mkdir_dialog.c:411 #, c-format msgid "cannot write to '%s' - %s" msgstr "impossible d'écrire dans '%s' - %s" #: e2_mkdir_dialog.c:419 #, c-format msgid "cannot write to parent directory - %s" msgstr "impossible d'écrire dans le dossier parent - %s" #: e2_mkdir_dialog.c:427 #, c-format msgid "only '%s' exists - %s" msgstr "seul '%s' existe - %s" #: e2_mkdir_dialog.c:456 msgid "the directory already exists." msgstr "le dossier existe déjà." #: e2_mkdir_dialog.c:458 msgid "something is in the way." msgstr "Quelque chose bloque l'opération." #: e2_mkdir_dialog.c:468 msgid "directories will be created" msgstr "Les dossiers seront créés." #: e2_mkdir_dialog.c:656 e2_mkdir_dialog.c:701 e2_task_backend.c:925 #, c-format msgid "Cannot create directory %s" msgstr "Impossible de créer le dossier %s" #: e2_mkdir_dialog.c:902 msgid "What is the new directory's name?" msgstr "Quel est le nom du nouveau dossier ?" #: e2_mkdir_dialog.c:902 msgid "create directory" msgstr "Créer un dossier" #: e2_mkdir_dialog.c:927 msgid "parent directory:" msgstr "dossier parent :" #: e2_mkdir_dialog.c:933 msgid "creation possible:" msgstr "création possible:" #: e2_mkdir_dialog.c:1003 msgid "open info frame" msgstr "afficher le cadre d'information" #: e2_mkdir_dialog.c:1004 msgid "" "This causes make-directory dialogs to start with extra information displayed" msgstr "" "Ceci entraîne l'affichage d'information supplémentaires dans le dialogue de " "création de dossier." #: e2_mkdir_dialog.c:1007 msgid "follow active-pane directory" msgstr "suivre les changements de dossier du panneau actif" #: e2_mkdir_dialog.c:1008 msgid "" "This makes the parent directory for new directories the same as the one in " "the active pane, even if the latter changes" msgstr "" "Ceci entraîne que le dossier parent des nouveaux dossiers est le même que " "celui du panneau actif, même si celui-ci change." #: e2_mkdir_dialog.c:1012 msgid "suggest directory name" msgstr "suggérer un nom de dossier" #: e2_mkdir_dialog.c:1013 msgid "" "This presents a suggested name for each new directory, based on the last-" "created directory with an increasing number appended" msgstr "" "Ceci entraîne l'affichage d'une proposition de nom pour chaque nouveau " "dossier créé, basée sur le nom du dernier dossier créé auquel est ajouté un " "nombre incrémenté à chaque fois." #: e2_mkdir_dialog.c:1017 msgid "show last directory name" msgstr "afficher le nom du dernier dossier créé" #: e2_mkdir_dialog.c:1018 msgid "" "This causes the name of the last-created directory to be shown in the entry " "field, after opening the dialog or when creating another directory" msgstr "" "Ceci entraîne l'affichage, dans le champ de saisie du nom, du nom du dernier " "dossier créé." #: e2_mkdir_dialog.c:1021 msgid "replicate changes" msgstr "répliquer les changements" #: e2_mkdir_dialog.c:1022 msgid "" "This causes option-changes to be replicated in other mkdir dialogs. " "Otherwise such changes will be confined to the current dialog" msgstr "" "Ceci provoque l'application des changements d'option aux autres dialogues de " "création de dossier. Autrement, les changements ne s'appliqueront que " "temporairement au dialogue courant." #: e2_name_filter_dialog.c:57 e2p_glob.c:301 msgid "Invalid filename pattern" msgstr "Modèle de nom de fichier invalide" #: e2_name_filter_dialog.c:161 msgid "Display only the items named like:" msgstr "N'afficher que les éléments dont le nom est de la forme :" #: e2_name_filter_dialog.c:162 msgid "name filter" msgstr "filtre sur le nom" #: e2_name_filter_dialog.c:175 e2p_glob.c:438 msgid "example: *.c,*.h" msgstr "exemple : *.c,*.h" #: e2_name_filter_dialog.c:178 msgid "Invert" msgstr "Inverser" #: e2_name_filter_dialog.c:186 msgid "Show files that DO NOT match the given mask" msgstr "Afficher les fichiers qui NE correspondent PAS au masque saisi." #: e2_name_filter_dialog.c:187 msgid "Case sensitive" msgstr "Tenir compte de la casse" #: e2_option.c:181 #, c-format msgid "Cannot create trash directory %s" msgstr "Impossible de créer le dossier de la corbeille %s" #: e2_option.c:346 msgid "Configuration data re-loaded" msgstr "Données de configuration rechargées" #: e2_option.c:611 #, c-format msgid "" "# This is the %s configuration data file.\n" "# It will be overwritten each time the program is run!\n" "\n" "# If you're inclined to edit the file between program sessions, note this:\n" "# for tree options, you have to use \\| to escape | and you have to use \\< " "to escape <,\n" "# if that is the first non-space character on a line.\n" "\n" msgstr "" "# Fichier de configuration %s.\n" "# Il sera écrasé chaque fois que le programme est lancé !\n" "\n" "# Si vous voulez l'éditer entre deux sessions du programme, notez ceci :\n" "# Pour les options qui se présentent sous forme d'arbre, vous devez utiliser " "\\| pour désactiver l'interprétation de | et \\< pour désactiver celle de " "<,\n" "# si c'est le premier caractère de la ligne autre qu'un espace.\n" "\n" #: e2_option.c:704 #, c-format msgid "Cannot write config file %s - %s" msgstr "Impossible d'écrire dans le fichier de configuration : %s - %s" #: e2_option.c:1067 msgid "aliases" msgstr "alias" #: e2_option.c:1068 msgid "bookmarks" msgstr "signets" #: e2_option.c:1069 msgid "colors" msgstr "couleurs" #: e2_option.c:1070 msgid "columns" msgstr "colonnes" #: e2_option.c:1071 msgid "command bar" msgstr "barre de commande" #: e2_option.c:1072 msgid "command line" msgstr "ligne de commande" #: e2_option.c:1074 msgid "confirmation" msgstr "confirmation" #: e2_option.c:1075 msgid "context menu" msgstr "menu contextuel" #: e2_option.c:1076 msgid "custom menus" msgstr "menus personnalisés" #: e2_option.c:1077 msgid "delays" msgstr "délais" #: e2_option.c:1078 msgid "dialogs" msgstr "dialogues" #: e2_option.c:1079 msgid "directory lines" msgstr "lignes d'affichage du dossier courant" #: e2_option.c:1080 msgid "extensions" msgstr "extensions" #: e2_option.c:1081 msgid "file actions" msgstr "actions sur fichier" #: e2_option.c:1082 msgid "filetypes" msgstr "types de fichiers" #: e2_option.c:1083 msgid "fonts" msgstr "polices" #: e2_option.c:1084 msgid "general" msgstr "général" #: e2_option.c:1086 e2p_config.c:1266 e2p_config.c:1301 e2p_config.c:1323 msgid "icons" msgstr "icônes" #: e2_option.c:1087 msgid "interface" msgstr "interface" #: e2_option.c:1088 msgid "item types" msgstr "types d'élément" #: e2_option.c:1090 msgid "make directory" msgstr "création de dossier" #: e2_option.c:1091 msgid "menus" msgstr "menus" #: e2_option.c:1092 msgid "miscellaneous" msgstr "divers" #: e2_option.c:1093 msgid "options" msgstr "options" #: e2_option.c:1095 msgid "pane 1" msgstr "panneau 1" #: e2_option.c:1096 msgid "pane1 bar" msgstr "barre d'outils du panneau 1" #: e2_option.c:1097 msgid "pane 2" msgstr "panneau 2" #: e2_option.c:1098 msgid "pane2 bar" msgstr "barre d'outils du panneau 2" #: e2_option.c:1100 msgid "plugins" msgstr "greffons" #: e2_option.c:1101 msgid "position" msgstr "position" #: e2_option.c:1103 msgid "startup" msgstr "démarrage" #: e2_option.c:1104 msgid "style" msgstr "style" #: e2_option.c:1105 msgid "tab completion" msgstr "complétion par tabulation" #: e2_option.c:1106 msgid "task bar" msgstr "barre de tâches" #: e2_option__default.c:70 msgid "show all options in config dialogs" msgstr "montre toutes les options dans les dialogues de configuration" #: e2_option__default.c:74 msgid "reload config on external change" msgstr "recharger la configuration en cas de changement externe" #: e2_option__default.c:75 msgid "" "This enables automatic reloading of the configuration data for this program, " "if that data is changed by another program instance" msgstr "" "Permet le rechargement automatique des données de configuration du " "programme, si ces données ont été changées par une autre instance du " "programme." #: e2_option__default.c:85 msgid "document containing usage advice" msgstr "document contenant des conseils d'utilisation" #: e2_option__default.c:86 msgid "This document is opened from the help dialog usage page" msgstr "Document ouvert par le bouton d'aide de la page sur l'utilisation." #: e2_option__default.c:92 msgid "document containing configuration advice" msgstr "document contenant des conseils de configuration" #: e2_option__default.c:93 msgid "This document is opened from the help dialog configuration page" msgstr "Document ouvert par le bouton d'aide de la page sur la configuration." #: e2_option__default.c:98 msgid "warn about running processes when shutting down" msgstr "" "afficher un avertissement si des processus sont en cours au moment de quitter" #: e2_option__default.c:99 msgid "This enables a reminder about incomplete commands and actions" msgstr "" "Active un avertissement concernant les commandes et actions non terminées." #: e2_option__default.c:115 msgid "opacity" msgstr "opacité" #: e2_option__default.c:116 msgid "Window translucence, 30 (faint) to 100 (opaque)" msgstr "Translucidité de la fenêtre, de 30 (faible) à 100 (opaque)" #: e2_option__default.c:120 msgid "'go up' on middle-button click" msgstr "remonter au dossier parent sur clic du bouton milieu de la souris" #: e2_option__default.c:121 msgid "" "This is a faster alternative to double clicking '..' or clicking the 'go up' " "button" msgstr "" "C'est un moyen plus rapide que de double-cliquer sur \"..\" ou de cliquer " "sur le bouton \"Dossier parent\"" #: e2_option__default.c:123 msgid "match windows (TM) right-click behaviour" msgstr "comportement du clic droit identique à Windows (TM)" #: e2_option__default.c:124 msgid "" "If activated, clicking the right mouse button will also select the row where " "the mouse cursor is" msgstr "" "Si activé, un clic droit sélectionne aussi la ligne sur laquelle se trouve " "le pointeur de la souris." #: e2_option__default.c:127 msgid "advanced windows (TM) right-click behaviour" msgstr "" "comportement du clic droit identique au comportement avancé de Windows (TM)" #: e2_option__default.c:128 msgid "" "If activated, clicking on a free area will not clear the current selection" msgstr "" "Si activé, cliquer sur une zone libre ne désactivera pas la sélection " "courante." #: e2_option__default.c:131 msgid "show icons in dialog buttons" msgstr "afficher les icônes dans les boutons des dialogues" #: e2_option__default.c:132 msgid "If activated, dialog buttons will show an icon as well as a label" msgstr "" "Si activé, les boutons des dialogues comporteront une icône et un texte." #: e2_option__default.c:137 msgid "use icons directory" msgstr "utiliser un dossier d'icônes" #: e2_option__default.c:138 msgid "" "If activated, icon files in the directory shown below will be used for " "toolbars etc" msgstr "" "Si activé, les fichiers d'icônes présents dans le répertoire ci-dessous " "seront utilisés pour les barres d'outils, etc." #: e2_option__default.c:142 msgid "icons directory" msgstr "dossier d'icônes" #: e2_option__default.c:143 msgid "The directory from which icon files will be retrieved" msgstr "Dossier d'où seront extraits les fichiers d'icônes" #: e2_option__default.c:155 msgid "show icons in menus" msgstr "afficher les icônes dans les menus" #: e2_option__default.c:156 msgid "Some people think that icons help you recognize items more quickly" msgstr "" "Certaines personnes pensent que les icônes vous aident à reconnaître plus " "rapidement les éléments des menus." #: e2_option__default.c:160 e2_option__default.c:329 e2_toolbar.c:2794 msgid "button" msgstr "bouton" #: e2_option__default.c:160 e2_option__default.c:329 e2_toolbar.c:2794 msgid "dnd" msgstr "glisser & déposer" #: e2_option__default.c:160 e2_option__default.c:329 e2_toolbar.c:2794 msgid "menu" msgstr "menu" #: e2_option__default.c:160 e2_option__default.c:329 e2_toolbar.c:2794 msgid "toolbar large" msgstr "barre d'outils large" #: e2_option__default.c:160 e2_option__default.c:329 e2_toolbar.c:2794 msgid "toolbar small" msgstr "barre d'outils petite" #: e2_option__default.c:161 msgid "menu icon size" msgstr "taille des icônes de menu" #: e2_option__default.c:162 msgid "This sets the icon size for ALL menus" msgstr "Ceci fixe la taille des icônes pour TOUS les menus." #: e2_option__default.c:167 msgid "menu popup delay (ms)" msgstr "délai d'affichage des menus (ms)" #: e2_option__default.c:168 msgid "" "The delay (in milliseconds) after the mouse pointer arrives at a menu item, " "before any submenu will pop up" msgstr "" "Délai (en millisecondes) entre le moment où le pointeur de la souris arrive " "sur un élément du menu et celui où le sous-menu correspondant est affiché." #: e2_option__default.c:171 msgid "menu popdown delay (ms)" msgstr "délai de fermeture de menu (ms)" #: e2_option__default.c:172 msgid "" "The delay (in milliseconds) after the mouse pointer leaves a menu item, " "before popping down an activated submenu" msgstr "" "Délai (en millisecondes) entre le moment où le pointeur de la souris quitte " "un élément de menu, et celui où un éventuel sous-menu actif est refermé." #: e2_option__default.c:181 msgid "auto refresh" msgstr "rafraîchir automatiquement" #: e2_option__default.c:182 msgid "" "This enables automatic checking for changes to the content of displayed " "directories. Otherwise you need to refresh manually" msgstr "" "Ceci active la vérification automatique des changements survenant dans les " "dossiers affichés. Sinon, vous devrez rafraîchir l'affichage manuellement." #: e2_option__default.c:185 msgid "focus the pane after completing a directory entry" msgstr "donner le focus au panneau après avoir complété une entrée de dossier" #: e2_option__default.c:186 msgid "" "If activated, after return/enter is pressed in a directory line, the pane " "containing that dir line will become the active one" msgstr "" "Si activé, l'appui de la touche Entrée dans une ligne de saisie du dossier " "provoque l'activation du panneau correspondant." #: e2_option__default.c:190 msgid "select first item in newly-opened directories" msgstr "" "sélectionner automatiquement le premier élément des répertoires nouvellement " "ouverts" #: e2_option__default.c:191 msgid "" "If activated, when a directory is first displayed in a session, the first " "item there will be selected" msgstr "" "Si activé, lorsqu'un dossier est ouvert pour la première fois depuis le " "lancement d'emelfm2, le premier élément de la liste des fichiers est " "automatiquement sélectionné." #: e2_option__default.c:196 msgid "use horizontal panes" msgstr "utiliser des panneaux horizontaux" #: e2_option__default.c:197 msgid "Horizontal (vertical-stacked) panes present more columns at once" msgstr "" "Les panneaux horizontaux (c'est-à-dire empilés verticalement) permettent de " "visualiser plus de colonnes simultanément." #: e2_option__default.c:205 msgid "bottom-left" msgstr "en bas à gauche" #: e2_option__default.c:205 msgid "bottom-right" msgstr "en bas à droite" #: e2_option__default.c:205 msgid "top-left" msgstr "en haut à gauche" #: e2_option__default.c:205 msgid "top-right" msgstr "en haut à droite" #: e2_option__default.c:206 msgid "scrollbar position" msgstr "position de la barre de défilement" #: e2_option__default.c:207 msgid "The default (bottom-right) should be ok for most users" msgstr "" "La valeur par défaut (en bas à droite) devrait convenir à la plupart des " "utilisateurs." #: e2_option__default.c:210 msgid "bold name header" msgstr "en-têtes en caractères gras" #: e2_option__default.c:210 msgid "colored headers" msgstr "en-têtes colorés" #: e2_option__default.c:210 msgid "sensitivity" msgstr "prise en compte de la casse" #: e2_option__default.c:211 msgid "type of indicator for active pane" msgstr "type d'indicateur pour le panneau actif" #: e2_option__default.c:212 msgid "" "This determines how the active pane is indicated on-screen. Some GTK themes " "do not allow column-header re-coloring" msgstr "" "Ceci définit comment le panneau actif apparaît à l'écran. Certains thèmes " "GTK n'autorisent pas la recoloration des en-têtes de colonnes." #: e2_option__default.c:216 msgid "banded background" msgstr "couleur de fond alternée" #: e2_option__default.c:217 msgid "If activated, lines in file lists will alternate background color" msgstr "" "Si activé, la couleur de fond des lignes des listes de fichiers sera " "alternée." #: e2_option__default.c:221 e2_output.c:2457 e2_view_dialog.c:1862 msgid "use custom font" msgstr "utiliser une police personnalisée" #: e2_option__default.c:222 e2_output.c:2458 e2_view_dialog.c:1863 msgid "" "If activated, the font specified below will be used, instead of the theme " "default" msgstr "" "Si activé, la police de caractère spécifiée ci-dessous sera utilisée, au " "lieu de celle par défaut du thème utilisé." #: e2_option__default.c:225 msgid "custom font" msgstr "police personnalisée" #: e2_option__default.c:226 msgid "This is the font used for flle pane text" msgstr "" "Ceci est la police utilisée pour le texte des panneaux affichant les listes " "de fichiers." #: e2_option__default.c:229 msgid "Default: May 20 09:11" msgstr "par défaut: 20 Mai 09:11" #: e2_option__default.c:230 msgid "American: 05/20/04 09:11" msgstr "américain: 05/20/04 09:11" #: e2_option__default.c:230 msgid "Standard: 20/05/04 09:11" msgstr "standard : 20/05/04 09:11" #: e2_option__default.c:231 msgid "LC_TIME locale specified" msgstr "comme spécifiée par la locale LC_TIME" #: e2_option__default.c:232 msgid "date format" msgstr "format de la date" #: e2_option__default.c:233 msgid "This determines the format of all dates displayed in the panes" msgstr "Ceci fixe le format des dates affichées dans les panneaux." #: e2_option__default.c:237 msgid "condensed" msgstr "condensé" #: e2_option__default.c:237 msgid "exact" msgstr "exact" #: e2_option__default.c:238 msgid "size format" msgstr "format de la taille de fichier" #: e2_option__default.c:239 msgid "" "Displayed item-sizes can be 'condensed' to show kB, MB where appropriate" msgstr "" "Ceci condense les tailles affichées en affichant des Ko, ou Mo s'il y a lieu." #: e2_option__default.c:243 msgid "show parent directory entry '..' in file lists" msgstr "afficher l'entrée '..' du dossier parent dans les listes de fichiers" #: e2_option__default.c:244 msgid "This slows status-line updates" msgstr "" "Ceci a pour inconvénient de ralentir les mises à jour de la ligne d'état." #: e2_option__default.c:246 msgid "filename sort is case sensitive" msgstr "prendre en compte la casse sur le tri des noms de fichiers" #: e2_option__default.c:247 msgid "" "This places all the capitalised file/directory names ahead of the others, if " "your LANG_C suports that" msgstr "" "Ceci place en tête de liste tous les fichiers/dossiers dont la première " "lettre est une majuscule, à condition que votre LANG_C le prenne en charge." #: e2_option__default.c:250 msgid "both panes are like pane 1" msgstr "utiliser les paramètres du panneau 1 pour les deux panneaux" #: e2_option__default.c:252 e2_option__default.c:258 #, c-format msgid "" "This makes pane %d options (other than toolbar placement and content) apply " "to pane %d" msgstr "" "Ceci entraîne l'application des options du panneau %d (autres que le " "positionnement de la barre d'outils et le contenu) au panneau %d." #: e2_option__default.c:256 msgid "both panes are like pane 2" msgstr "utiliser les paramèters du panneau 2 pour les deux panneaux" #: e2_option__default.c:268 msgid "executable" msgstr "exécutable" #: e2_option__default.c:269 msgid "Executable file names are listed in this color" msgstr "" "Les noms de fichiers exécutables sont affichés dans la liste avec cette " "couleur." #: e2_option__default.c:271 e2p_du.c:220 e2p_find.c:3018 msgid "directory" msgstr "dossier" #: e2_option__default.c:272 msgid "Directory names are listed in this color" msgstr "Les noms de dossiers sont affichés dans la liste avec cette couleur." #: e2_option__default.c:275 msgid "Symbolic link names are listed in this color" msgstr "" "Les noms de liens symboliques sont affichés dans la liste avec cette couleur." #: e2_option__default.c:277 msgid "device" msgstr "périphérique" #: e2_option__default.c:278 msgid "Device names are listed in this color" msgstr "" "Les noms de périphériques sont affichés dans la liste avec cette couleur." #: e2_option__default.c:280 e2p_find.c:3051 msgid "socket" msgstr "socket" #: e2_option__default.c:281 msgid "Sockets are listed in this color" msgstr "Les \"sockets\" sont affichés dans la liste avec cette couleur." #: e2_option__default.c:286 msgid "custom background color" msgstr "couleur de fond personnalisée" #: e2_option__default.c:287 msgid "If enabled, the color specified below will be used for background" msgstr "Si activé, la couleur spécifiée ci-dessous sera utilisée pour le fond." #: e2_option__default.c:290 msgid "background color" msgstr "couleur de fond" #: e2_option__default.c:291 msgid "Background color used instead of theme color" msgstr "Couleur de fond utilisée en lieu de la couleur du thème" #: e2_option__default.c:295 msgid "active pane header color" msgstr "couleur de l'entête du panneau actif" #: e2_option__default.c:296 msgid "" "This color is used for the background of column headers in the active pane" msgstr "" "Cette couleur sera utilisée pour le fond des entêtes de colonnes pour le " "panneau actif." #: e2_option__default.c:298 msgid "highlight color" msgstr "couleur de mise en évidence" #: e2_option__default.c:299 msgid "" "This color is used for the background of highlighed item-names and other text" msgstr "" "Cette couleur sera utilisée pour le fond des éléments marqués et du texte " "mis en évidence." #: e2_option__default.c:320 e2_toolbar.c:2784 msgid "icon above label" msgstr "icône au dessus du texte" #: e2_option__default.c:320 e2_toolbar.c:2784 msgid "icon beside label" msgstr "icône à côté du texte" #: e2_option__default.c:320 e2_toolbar.c:2784 msgid "icon only" msgstr "icône seulement" #: e2_option__default.c:320 e2_toolbar.c:2784 msgid "label only" msgstr "texte seulement" #: e2_option__default.c:320 e2_option__default.c:329 e2_toolbar.c:2784 #: e2_toolbar.c:2794 msgid "theme" msgstr "thème" #: e2_option__default.c:321 e2_toolbar.c:2785 #, c-format msgid "'%s' uses the Gtk default, '%s' leaves most space for other things" msgstr "" "'%s' utilise la valeur GTK par défaut, '%s' laisse le maximum d'espace pour " "d'autres choses." #: e2_option__default.c:323 msgid "toolbars button style" msgstr "style des boutons des barres d'outils" #: e2_option__default.c:330 e2_toolbar.c:2795 #, c-format msgid "'%s' uses the Gtk default, '%s' is smallest, '%s' is largest" msgstr "" "'%s' utilise la valeur GTK par défaut, '%s' est plus petit, '%s' est plus " "grand." #: e2_option__default.c:332 msgid "toolbars icon size" msgstr "taille des icônes des barres d'outils" #: e2_option_tree.c:163 e2_option_tree.c:408 msgid "Press key" msgstr "Appuyer sur une touche" #: e2_option_tree.c:621 #, c-format msgid "select icon for %s" msgstr "choisir l'icône pour %s" #: e2_option_tree.c:666 #, c-format msgid "Are you sure that you want to delete this row and %d %s?" msgstr "Êtes vous sûr que vous voulez supprimer cette ligne et %d %s?" #: e2_option_tree.c:670 msgid "confirm row delete" msgstr "confirmer la suppression de la ligne" #: e2_option_tree.c:956 e2_toolbar.c:2899 e2p_acl.c:3791 e2p_find.c:3613 #: e2p_rename.c:1538 msgid "_Help" msgstr "_Aide" #: e2_option_tree.c:957 msgid "Get help on this option" msgstr "Obtenir de l'aide sur cette option" #: e2_option_tree.c:960 msgid "_Select" msgstr "_Sélectionner" #: e2_option_tree.c:961 msgid "Show plugin-selection dialog" msgstr "Afficher le dialogue de sélection des greffons" #: e2_option_tree.c:964 msgid "Co_lor" msgstr "Cou_leur" #: e2_option_tree.c:965 msgid "Show color-selection dialog" msgstr "Afficher le dialogue de sélection des couleurs" #: e2_option_tree.c:979 msgid "Remove the selected row" msgstr "Supprime la ligne sélectionnée" #: e2_option_tree.c:981 msgid "Add a row after the currently selected row in the tree" msgstr "Ajoute une ligne après la ligne sélectionnée" #: e2_option_tree.c:984 msgid "Add ch_ild" msgstr "Ajouter un _fils" #: e2_option_tree.c:985 msgid "Add a child row to the currently selected one" msgstr "Ajoute une ligne \"fille\" à la ligne sélectionnée" #: e2_option_tree_context_menu.c:228 msgid "Cut selected row and any descendant(s)" msgstr "Coupe la ligne sélectionnée et tous ses descendants" #: e2_option_tree_context_menu.c:232 msgid "Copy selected row and any descendant(s)" msgstr "Copie la ligne sélectionnées et tous ses descendants" #: e2_option_tree_context_menu.c:244 msgid "Expand all rows on this page" msgstr "Développe toutes les lignes de cette page" #: e2_option_tree_context_menu.c:248 msgid "Collapse all rows on this page" msgstr "replie toutes les lignes de cette page" #: e2_option_tree_context_menu.c:260 msgid "Add c_hild" msgstr "Ajouter un _fils" #: e2_option_tree_context_menu.c:261 msgid "Add a child to the selected row" msgstr "Ajoute un fils à la ligne sélectionnée" #: e2_output.c:548 msgid "output tabs" msgstr "onglets de sortie" #: e2_output.c:1070 #, c-format msgid "Cannot find any output from process %s" msgstr "Aucune sortie en provenance du processus %s" #: e2_output.c:1347 msgid "Do not show the output pane" msgstr "Ne pas afficher le panneau de sortie" #: e2_output.c:1351 msgid "_Toggle full" msgstr "(Dé)_maximiser" #: e2_output.c:1354 msgid "Toggle output pane size to/from the full window size" msgstr "Bascule la taille du panneau de sortie de \"normale\" à \"maximale\"" #: e2_output.c:1358 msgid "_New tab" msgstr "_Nouvel onglet" #: e2_output.c:1359 msgid "Add another tab for the output pane" msgstr "Ajouter un nouvel onglet pour le panneau de sortie" #: e2_output.c:1363 msgid "_Remove tab" msgstr "_Supprimer l'onglet" #: e2_output.c:1364 msgid "Close this this tab" msgstr "Ferme l'onglet courant" #: e2_output.c:1373 msgid "_Attach" msgstr "_Attacher" #: e2_output.c:1374 msgid "Move this tab back to output pane" msgstr "Replacer cet onglet sur le panneau de sortie" #: e2_output.c:1381 e2_output.c:1385 msgid "_Clear" msgstr "_Nettoyer" #: e2_output.c:1382 e2_output.c:1386 msgid "Clear this tab" msgstr "Effacer le contenu de cet onglet" #: e2_output.c:1395 msgid "Edit the tab contents" msgstr "Éditer le contenu de l'onglet" #: e2_output.c:1417 msgid "Co_mmand output" msgstr "Sortie de commande" #: e2_output.c:1418 msgid "Show output from a completed command" msgstr "Afficher la sortie d'une commande terminée" #: e2_output.c:1431 e2_view_dialog.c:958 msgid "_Settings" msgstr "_Configuration" #: e2_output.c:1442 msgid "_Other" msgstr "_Autres" #: e2_output.c:1443 msgid "Open the configuration dialog at the output options page" msgstr "Ouvre le dialogue de configuration à la page des options de sortie" #: e2_output.c:1772 msgid "-- end-of-output --" msgstr "-- fin-des-messages --" #: e2_output.c:2415 msgid "show output pane when a new message appears" msgstr "afficher le panneau de sortie quand un nouveau message survient" #: e2_output.c:2416 msgid "This will ensure you don't miss any messages" msgstr "Ceci vous assure de ne rater aucun message." #: e2_output.c:2419 msgid "show output pane if the command line is focused" msgstr "" "afficher le panneau de sortie quand le focus est sur la ligne de commande" #: e2_output.c:2420 msgid "" "This causes the output pane to be opened when you are about enter a command" msgstr "" "Ceci entraîne l'ouverture du panneau de sortie quand vous êtes sur le point " "de taper une commande." #: e2_output.c:2424 msgid "hide output pane if the command line is unfocused" msgstr "" "cacher le panneau de sortie quand le focus n'est pas sur la ligne de commande" #: e2_output.c:2425 msgid "" "This causes the output pane to be closed when you move focus away from the " "command line" msgstr "" "Le panneau de sortie sera automatiquement fermé dès que le focus n'est plus " "sur la ligne de commande." #: e2_output.c:2428 msgid "show commands" msgstr "montrer les commandes" #: e2_output.c:2429 msgid "This echoes the commands that are run and their exit value" msgstr "Ceci affiche les commandes lancées et leur valeur de retour." #: e2_output.c:2431 msgid "scroll to new output" msgstr "défiler vers les nouveaux messages" #: e2_output.c:2432 msgid "" "This will automatically scroll the output pane content, to show new message" "(s)" msgstr "" "Ceci fait défiler automatiquement le contenu du panneau de sortie pour " "rendre visible les nouveaux messages." #: e2_output.c:2434 msgid "only scroll when really following" msgstr "respecter le positionnement manuel" #: e2_output.c:2435 msgid "" "This stops automatic pane scrolling to new output if you've manually " "scrolled away" msgstr "" "Ceci désactive le défilement automatique du panneau de sortie pour rendre " "visible les nouveaux messages, quand vous l'avez manuellement fait défiler " "vers une autre zone." #: e2_output.c:2438 msgid "only scroll when new output is at the end" msgstr "ne défiler que lorsque les nouveaux messages sont à la fin" #: e2_output.c:2439 msgid "" "This stops pane scrolling if a different process has displayed text after " "the current insert position" msgstr "" "Suspend le défilement du panneau si un autre processus a affiché du texte " "après le point d'insertion courant." #: e2_output.c:2442 msgid "everywhere" msgstr "partout" #: e2_output.c:2442 msgid "words" msgstr "mots" #: e2_output.c:2444 msgid "line wrap mode" msgstr "mode retour à la ligne automatique" #: e2_output.c:2445 msgid "" "If mode is 'none', a horizontal scrollbar will be available. Mode 'words' " "will only break the line between words" msgstr "" "Si le mode est 'aucun', une barre de défilement horizontale sera affichée. " "Si c'est 'mots', le retour à la ligne automatique ne se fera qu'entre les " "mots" #: e2_output.c:2448 msgid "left margin (pixels)" msgstr "marge gauche (en pixels)" #: e2_output.c:2449 msgid "This is the left margin between the output-pane edge and the text in it" msgstr "" "Il s'agit de la marge gauche entre le bord du panneau de sortie et le texte " "qu'il contient." #: e2_output.c:2452 msgid "right margin (pixels)" msgstr "marge droite (en pixels)" #: e2_output.c:2453 msgid "" "This is the right margin between the output-pane edge and the text in it" msgstr "" "Il s'agit de la marge droite entre le bord du panneau de sortie et le texte " "qu'il contient." #: e2_output.c:2461 msgid "custom output font" msgstr "personnaliser la police de sortie" #: e2_output.c:2462 msgid "This is the font used for text in the output pane" msgstr "Police utilisée pour le texte du panneau de sortie" #: e2_output.c:2467 msgid "positive color" msgstr "couleur des événements positifs" #: e2_output.c:2468 msgid "" "This color is used for messages about successful operations or other " "'positive events'" msgstr "" "Couleur utilisée pour les messages rendant compte d'opérations qui se sont " "terminées avec succès ou d'autres événements positifs." #: e2_output.c:2471 msgid "negative color" msgstr "couleur des événements négatifs" #: e2_output.c:2472 msgid "" "This color is used for messages about unsuccessful operations or other " "'negative events'" msgstr "" "Couleur utilisée pour les messages rendant compte d'opérations qui ont " "échoué ou d'autres événements négatifs" #: e2_output.c:2475 msgid "unimportant color" msgstr "couleur ordinaire" #: e2_output.c:2476 msgid "" "This color is used for messages of minor importance or other miscellaneous " "events" msgstr "" "Couleur utilisée pour les messages de moindre importance et pour divers " "autres événements" #: e2_ownership_dialog.c:167 msgid "You are not authorised to make any change." msgstr "Vous n'êtes pas autorisez à faire des changements." #: e2_ownership_dialog.c:209 msgid "ownership" msgstr "propriétaire" #: e2_ownership_dialog.c:217 e2_permissions_dialog.c:327 e2p_acl.c:3467 #: e2p_times.c:697 msgid "Directory name" msgstr "Nom du dossier" #: e2_ownership_dialog.c:233 msgid "Permissions: " msgstr "Permissions : " #: e2_ownership_dialog.c:245 msgid "User: " msgstr "Utilisateur : " #: e2_ownership_dialog.c:251 msgid "Group: " msgstr "Groupe : " #: e2_ownership_dialog.c:375 msgid "Recurse" msgstr "Traiter récursivement" #: e2_pane.c:883 msgid "No trash directory is available" msgstr "Aucun répertoire corbeille n'est disponible" #: e2_pane.c:985 msgid "_Edit places" msgstr "_Éditer les emplacements" #: e2_pane.c:1584 #, c-format msgid "show %s column" msgstr "afficher la colonne %s" #: e2_pane.c:1585 #, c-format msgid "This causes the the named column to be displayed in pane %d (duh ...)" msgstr "Ceci entraîne l'affichage dans le panneau %d de la colonne désignée." #: e2_pane.c:1615 #, c-format msgid "At startup, show a specific directory in pane %d" msgstr "au démarrage, afficher un dossier particulier dans le panneau %d" #: e2_pane.c:1617 msgid "" "This causes the directory named below, instead of the last-opened directory, " "to be shown at session start" msgstr "" "Ceci entraîne l'affichage du dossier ci-dessous au démarrage, au lieu du " "dernier dossier visité." #: e2_pane.c:1622 #, c-format msgid "pane %d startup directory:" msgstr "dossier de démarrage du panneau %d :" #: e2_pane.c:1624 msgid "This is the directory to show in this pane, at session start" msgstr "Le dossier à afficher dans ce panneau au démarrage" #: e2_password_dialog.c:113 e2p_crypt.c:2922 msgid "Enter password:" msgstr "Saisissez le mot de passe :" #: e2_password_dialog.c:191 e2p_crypt.c:2934 msgid "Confirm password:" msgstr "Confirmez le mot de passe :" #: e2_password_dialog.c:249 msgid "Entered passwords are different" msgstr "Les mots de passe saisis sont différents" #: e2_permissions_dialog.c:337 e2_permissions_dialog.c:400 #: e2_vfs_dialog.c:1430 e2p_acl.c:229 e2p_acl.c:2090 e2p_acl.c:3477 #: e2p_gvfs.c:647 msgid "User" msgstr "Utilisateur" #: e2_permissions_dialog.c:379 msgid "currently" msgstr "actuellement" #: e2_permissions_dialog.c:395 e2p_acl.c:222 msgid "Read" msgstr "Lecture" #: e2_permissions_dialog.c:396 e2p_acl.c:222 msgid "Write" msgstr "Écriture" #: e2_permissions_dialog.c:397 e2p_acl.c:222 msgid "Exec" msgstr "Exécution" #: e2_permissions_dialog.c:398 msgid "Special" msgstr "Spécial" #: e2_permissions_dialog.c:407 msgid "Set UID" msgstr "UID appliqué" #: e2_permissions_dialog.c:417 msgid "Set GID" msgstr "GID appliqué" #: e2_permissions_dialog.c:420 e2p_acl.c:229 e2p_acl.c:2099 msgid "Other" msgstr "Autre" #: e2_permissions_dialog.c:427 msgid "Sticky" msgstr "Sticky" #: e2_permissions_dialog.c:440 e2p_acl.c:3704 msgid "Action:" msgstr "Action :" #: e2_permissions_dialog.c:450 e2p_acl.c:3722 e2p_times.c:751 e2p_times.c:753 #: e2p_times.c:755 msgid "_Set" msgstr "_Appliquer" #: e2_permissions_dialog.c:466 e2p_acl.c:3746 e2p_times.c:757 msgid "R_ecurse:" msgstr "Appliquer r_écursivement :" #: e2_permissions_dialog.c:475 e2p_acl.c:3754 msgid "" "If activated, changes will be applied to selected items and also their " "descendents, if such items match your choice of \"directories\" and/or " "\"others\" (anything not a directory)" msgstr "" "Si activé, les changements s'appliqueront aux éléments sélectionnés et à " "leurs descendants, à condition que de tels éléments correspondent à votre " "choix de \"dossiers\" et/ou \"autres\" (tout sauf un dossier)." #: e2_permissions_dialog.c:478 e2p_acl.c:3757 msgid "d_irectories" msgstr "d_ossiers" #: e2_permissions_dialog.c:481 e2p_acl.c:3760 msgid "o_thers" msgstr "_autres" #: e2_plugins.c:299 msgid "Cannot unload plugin" msgstr "Impossible de décharger le greffon" #: e2_plugins.c:738 msgid "Plugin not loaded" msgstr "Greffon non chargé" #: e2_plugins.c:1274 msgid "Loaded" msgstr "Chargé ?" #: e2_select_image_dialog.c:607 #, fuzzy msgid "Choose icons directory" msgstr "Choisir un dossier d'icônes" #: e2_select_image_dialog.c:627 #, fuzzy msgid "_other" msgstr "_autres" #: e2_select_image_dialog.c:631 #, fuzzy msgid "_stock" msgstr "_stock" #: e2_select_image_dialog.c:646 msgid "Remove the current icon" msgstr "Supprime l'icône courante" #: e2_size_filter_dialog.c:145 msgid "Display only the items which are:" msgstr "Afficher seulement les éléments qui sont :" #: e2_size_filter_dialog.c:146 msgid "size filter" msgstr "filtre sur la taille" #: e2_size_filter_dialog.c:153 e2p_glob.c:460 msgid "bigger than" msgstr "plus grand que" #: e2_size_filter_dialog.c:153 e2p_glob.c:460 msgid "equal to" msgstr "égale à" #: e2_size_filter_dialog.c:153 e2p_glob.c:460 msgid "smaller than" msgstr "plus petit que" #: e2_size_filter_dialog.c:192 e2p_find.c:2785 e2p_glob.c:501 msgid "Mbytes" msgstr "Moctets" #: e2_size_filter_dialog.c:192 e2p_find.c:2783 e2p_glob.c:501 msgid "kbytes" msgstr "Koctets" #: e2_task.c:516 #, c-format msgid "%s added to tasks queue" msgstr "%s ajouté à la liste des tâches" #: e2_task.c:784 #, c-format msgid "%s operation incomplete - time limit exceeded" msgstr "%s tâche inachevée - délai maximum dépassé" #: e2_task.c:868 #, c-format msgid "The current operation (%s)" msgstr "La tâche courante (%s)" #: e2_task.c:870 msgid "operation" msgstr "tâche" #: e2_task.c:1039 msgid "File operation in progress" msgstr "Opération sur fichier en cours" #: e2_task.c:1184 msgid "Operation not permitted - Circular directories" msgstr "Opération non permise - répertoires circulaires" #: e2_task.c:1416 e2_task.c:1742 e2_task.c:1985 e2_task.c:2352 e2p_clone.c:78 msgid "Enter new name for" msgstr "Saisir le nouveau nom de" #: e2_task.c:1991 msgid "link" msgstr "lien symbolique" #: e2_task.c:2113 #, c-format msgid "Are you sure you want to delete %s?" msgstr "Êtes-vous sûr que vous voulez supprimer %s?" #: e2_task.c:2444 e2p_rename.c:1017 e2p_rename.c:1019 #, c-format msgid "Cannot rename %s" msgstr "Impossible de renommer %s" #: e2_task.c:2582 e2p_acl.c:4165 #, c-format msgid "You do not have authority to change permission(s) of %s" msgstr "Vous n'êtes pas autorisé à changer les permissions de %s" #: e2_task.c:2740 #, c-format msgid "You do not have authority to change owner(s) of %s" msgstr "Vous n'êtes pas autorisé à changer le propriétaire de %s" #: e2_task.c:3248 msgid "open with" msgstr "ouvrir avec" #: e2_task.c:3249 e2_toolbar.c:2893 msgid "Enter command:" msgstr "Entrez une commande :" #: e2_task.c:3420 msgid "Enter a name or partial name to find:" msgstr "Entrez un nom de fichier ou un nom partiel de fichier à rechercher :" #: e2_task_backend.c:330 #, c-format msgid "Cannot change ownership of %s" msgstr "Impossible de changer le propriétaire de %s" #: e2_task_backend.c:490 e2_task_backend.c:506 e2_task_backend.c:1613 #, c-format msgid "Cannot delete %s" msgstr "Impossible de supprimer %s" #: e2_task_backend.c:663 e2_task_backend.c:1578 #, c-format msgid "Cannot delete existing %s" msgstr "Impossible de supprimer le fichier ou dossier existant %s" #: e2_task_backend.c:722 #, c-format msgid "Cannot create special file %s" msgstr "Impossible de créer le fichier spécial %s" #: e2_task_backend.c:737 #, c-format msgid "Cannot create FIFO %s" msgstr "Impossible de créer la FIFO %s" #: e2_task_backend.c:874 #, c-format msgid "Cannot create new link to %s" msgstr "Impossible de créer un lien vers %s" #: e2_task_backend.c:885 #, c-format msgid "Cannot get target info for link %s" msgstr "" "Impossible d'obtenir les informations sur le fichier pointé par le lien %s" #: e2_task_backend.c:1357 #, c-format msgid "Cannot copy all of %s" msgstr "Impossible de copier tout de %s" #: e2_task_backend.c:1378 msgid "incomplete" msgstr "incomplet" #: e2_task_backend.c:1463 #, c-format msgid "Cannot move %s to %s" msgstr "Impossible de déplacer %s vers %s" #: e2_task_backend.c:1518 #, c-format msgid "Cannot create link to %s" msgstr "Impossible de créer un lien vers %s" #: e2_task_backend.c:1566 #, c-format msgid "Cannot rename %s to %s" msgstr "Impossible de renommer %s en %s" #: e2_task_backend.c:1648 e2_task_backend.c:1764 e2p_acl.c:2826 #: e2p_crypt.c:1773 e2p_times.c:319 #, c-format msgid "Cannot get current data for %s" msgstr "Impossible d'obtenir les données courantes sur %s" #: e2_task_backend.c:1699 e2p_acl.c:1870 #, c-format msgid "Cannot change permission(s) of all of %s" msgstr "Impossible de changer les permissions de l'ensemble de %s" #: e2_task_backend.c:1718 #, c-format msgid "Cannot get current permissions of %s" msgstr "Impossible d'obtenir les permissions courantes de %s" #: e2_task_backend.c:1834 #, c-format msgid "Cannot change ownership of all of %s" msgstr "Impossible de changer le propriétaire de l'ensemble de %s" #: e2_toolbar.c:357 msgid "Show _bar" msgstr "Afficher la _barre" #: e2_toolbar.c:359 msgid "Show _tooltips" msgstr "Afficher les _info-bulles" #: e2_toolbar.c:361 msgid "Space _handling" msgstr "Gestion de l'_espace" #: e2_toolbar.c:364 msgid "_none" msgstr "_aucun" #: e2_toolbar.c:366 msgid "_scrollbars" msgstr "_barres de défilement" #: e2_toolbar.c:368 msgid "_rest button" msgstr "_bouton reste" #: e2_toolbar.c:370 msgid "_Placement" msgstr "_Placement" #: e2_toolbar.c:371 msgid "_horizontal" msgstr "_horizontal" #: e2_toolbar.c:373 msgid "_Container" msgstr "_Conteneur" #: e2_toolbar.c:376 msgid "_main window" msgstr "_fenêtre principale" #: e2_toolbar.c:378 msgid "_both panes" msgstr "les _deux panneaux" #: e2_toolbar.c:380 msgid "file-pane _1" msgstr "panneau _1" #: e2_toolbar.c:382 msgid "flle-pane _2" msgstr "panneau _2" #: e2_toolbar.c:392 msgid "_Left" msgstr "_Gauche" #: e2_toolbar.c:394 msgid "_Right" msgstr "_Droite" #: e2_toolbar.c:397 msgid "_Reset order" msgstr "_Réinitialiser l'ordre" #: e2_toolbar.c:399 msgid "Buttons _relief" msgstr "_Relief des boutons" #: e2_toolbar.c:401 msgid "Buttons _same size" msgstr "_Taille identique des boutons" #: e2_toolbar.c:403 msgid "_Button style" msgstr "_Style des boutons" #: e2_toolbar.c:407 e2_toolbar.c:421 msgid "_theme" msgstr "_theme" #: e2_toolbar.c:409 msgid "icon _only" msgstr "icône _seulement" #: e2_toolbar.c:411 msgid "_label only" msgstr "_texte seulement" #: e2_toolbar.c:413 msgid "icon _above label" msgstr "icône _au-dessus du texte" #: e2_toolbar.c:415 msgid "icon _beside label" msgstr "icône à _côté du texte" #: e2_toolbar.c:417 msgid "_Icon size" msgstr "_Taille de l'icône" #: e2_toolbar.c:423 msgid "_menu" msgstr "_menu" #: e2_toolbar.c:425 msgid "toolbar _small" msgstr "barre d'outils _petite" #: e2_toolbar.c:427 msgid "toolbar _large" msgstr "barre d'outils _grande" #: e2_toolbar.c:429 msgid "_button" msgstr "_bouton" #: e2_toolbar.c:431 msgid "drag '_n' drop" msgstr "glisser-_déposer" #: e2_toolbar.c:433 msgid "_dialog" msgstr "_dialogue" #: e2_toolbar.c:436 msgid "Bar i_tems" msgstr "_Elements des barres" #: e2_toolbar.c:719 msgid "_Rest" msgstr "_Reste" #: e2_toolbar.c:720 msgid "Show a menu of hidden items" msgstr "Afficher un menu des éléments cachés" #: e2_toolbar.c:1603 msgid "not" msgstr "non" #: e2_toolbar.c:1634 msgid "accessed" msgstr "accédés" #: e2_toolbar.c:1634 msgid "changed" msgstr "changés" #: e2_toolbar.c:1634 msgid "modified" msgstr "modifiés" #: e2_toolbar.c:2712 msgid "show the " msgstr "afficher la " #: e2_toolbar.c:2713 #, c-format msgid "This determines whether the %s is displayed or hidden" msgstr "Ceci détermine si la %s est affichée ou masquée." #: e2_toolbar.c:2720 #, c-format msgid "show tooltips for %s buttons" msgstr "afficher les info-bulles pour les boutons de la %s" #: e2_toolbar.c:2721 #, c-format msgid "If deactivated, tooltips will not be displayed for %s buttons" msgstr "" "Si désactivé, les info-bulles ne seront pas affichées pour les boutons de la " "%s." #: e2_toolbar.c:2727 #, c-format msgid "" "This determines the method for accessing %s elements that are hidden due to " "lack of screen-space" msgstr "" "Détermine la méthode d'accès aux éléments de la %s qui sont masqués par " "manque d'espace à l'écran." #: e2_toolbar.c:2729 msgid "use rest button" msgstr "utiliser le bouton \"Reste\"" #: e2_toolbar.c:2729 msgid "use scrollbars" msgstr "utiliser des barres de défilement" #: e2_toolbar.c:2730 msgid "space handling" msgstr "gestion de l'espace" #: e2_toolbar.c:2737 msgid " container" msgstr " (conteneur)" #: e2_toolbar.c:2738 #, c-format msgid "This determines the 'box' into which the %s is placed" msgstr "Ceci détermine la \"boîte\" dans laquelle la %s est placée." #: e2_toolbar.c:2740 msgid "both panes" msgstr "les deux panneaux" #: e2_toolbar.c:2740 msgid "file-pane 1" msgstr "panneau 1" #: e2_toolbar.c:2740 msgid "file-pane 2" msgstr "panneau 2" #: e2_toolbar.c:2740 msgid "main window" msgstr "fenêtre principale" #: e2_toolbar.c:2748 msgid " horizontal" msgstr " horizontale" #: e2_toolbar.c:2749 #, c-format msgid "This determines whether the %s is displayed horizontally or vertically" msgstr "Ceci détermine si la %s est affichée horizontalement ou verticalement." #: e2_toolbar.c:2756 msgid " priority" msgstr " (priorité)" #: e2_toolbar.c:2757 msgid "This determines the order of toolbars (0 = left or top)" msgstr "Ceci détermine l'ordre des barres d'outils (0 = gauche ou haut)" #: e2_toolbar.c:2766 msgid " buttons have relief" msgstr " : afficher les boutons avec du relief" #: e2_toolbar.c:2768 msgid "" "Buttons with relief show their outline continually, not just when 'moused'" msgstr "" "Les boutons en relief affichent leur bordure en permanence, et non seulement " "quand le pointeur de la souris les survole" #: e2_toolbar.c:2775 msgid " buttons are the same size" msgstr " : afficher les boutons avec la même taille" #: e2_toolbar.c:2777 msgid "" "Equal-width buttons look good on a vertical bar, bad on a horizontal bar" msgstr "" "Des boutons de même largeur produisent un bon effet visuel sur une barre " "verticale, mais pas sur une barre horizontale" #: e2_toolbar.c:2787 msgid "button style" msgstr "style de bouton" #: e2_toolbar.c:2797 msgid "icon size" msgstr "taille d'icône" #: e2_toolbar.c:2817 msgid "Copy item(s) selected in the active pane to the other one" msgstr "" "Copier dans le panneau inactif les éléments sélectionnés du panneau actif" #: e2_toolbar.c:2819 msgid "Move item(s) selected in the active pane to the other one" msgstr "" "Déplacer vers le panneau inactif les éléments sélectionnés du panneau actif" #: e2_toolbar.c:2821 msgid "Symlink item(s) selected in the active pane to the other one" msgstr "" "Créer un lien symbolique dans le panneau inactif pour chaque élément " "sélectionné du panneau actif" #: e2_toolbar.c:2822 msgid "Re_name.." msgstr "Re_nommer..." #: e2_toolbar.c:2823 msgid "Rename item(s) selected in the active pane" msgstr "Renommer les éléments sélectionnés du panneau actif" #: e2_toolbar.c:2825 msgid "Move item(s) selected in the active pane to a trashbin" msgstr "Déplacer dans la corbeille les éléments sélectionnés du panneau actif" #: e2_toolbar.c:2826 msgid "Create new directory(ies)" msgstr "Créer un nouveau dossier" #: e2_toolbar.c:2832 msgid "Re_fresh" msgstr "Ra_fraîchir" #: e2_toolbar.c:2832 msgid "Update pane contents" msgstr "Mettre à jour le contenu du panneau" #: e2_toolbar.c:2833 msgid "_Switch" msgstr "_Basculer" #: e2_toolbar.c:2834 msgid "Toggle the active pane" msgstr "Change de panneau actif" #: e2_toolbar.c:2880 msgid "Full" msgstr "Maximiser" #: e2_toolbar.c:2880 msgid "Maximize output pane" msgstr "Agrandir au maximum le panneau de sortie" #: e2_toolbar.c:2881 msgid "Shrink" msgstr "Restaurer" #: e2_toolbar.c:2881 msgid "Un-maximize output pane" msgstr "Rétablir la taille ordinaire du panneau de sortie" #: e2_toolbar.c:2882 msgid "Hide" msgstr "Masquer" #: e2_toolbar.c:2882 msgid "Hide output pane" msgstr "Masquer le panneau de sortie" #: e2_toolbar.c:2883 msgid "Show" msgstr "Afficher" #: e2_toolbar.c:2883 msgid "Show output pane" msgstr "Rendre visible le panneau de sortie" #: e2_toolbar.c:2884 msgid "Clear" msgstr "Nettoyer" #: e2_toolbar.c:2884 msgid "Clear output pane" msgstr "Réinitialiser à vide le panneau de sortie" #: e2_toolbar.c:2886 msgid "Clear command line" msgstr "Nettoyer la ligne de commande" #: e2_toolbar.c:2886 msgid "cl" msgstr "cl" #: e2_toolbar.c:2887 msgid "Child processes" msgstr "Processus fils" #: e2_toolbar.c:2887 msgid "ps" msgstr "ps" #: e2_toolbar.c:2888 msgid "Calculate disk usage of selected item(s)" msgstr "Calculer l'espace disque occupé par les éléments sélectionnés" #: e2_toolbar.c:2888 e2p_du.c:267 msgid "du" msgstr "occupation_disque" #: e2_toolbar.c:2889 msgid "Find item in active pane, by name" msgstr "Chercher selon le nom sur les éléments du panneau actif" #: e2_toolbar.c:2890 msgid "Open terminal at the active directory" msgstr "Ouvrir une fenêtre de terminal dans le dossier courant" #: e2_toolbar.c:2890 msgid "_X" msgstr "_X" #: e2_toolbar.c:2893 msgid "Run something as root" msgstr "Lancer une commande en tant que root" #: e2_toolbar.c:2893 msgid "su.." msgstr "su..." #: e2_toolbar.c:2895 msgid "Mount or unmount a device" msgstr "Monter ou démonter un périphérique" #: e2_toolbar.c:2895 msgid "mts.." msgstr "mts..." #: e2_toolbar.c:2898 msgid "View/change configuration settings for this program" msgstr "Afficher/modifier les éléments de configuration du programme" #: e2_toolbar.c:2898 msgid "_Settings.." msgstr "_Configuration..." #: e2_toolbar.c:2899 msgid "Get information about this program" msgstr "Afficher les informations sur ce programme" #: e2_toolbar.c:2901 msgid "Close this program" msgstr "Quitter le programme" #: e2_toolbar.c:2901 msgid "_Quit" msgstr "_Quitter" #: e2_toolbar.c:2950 e2_toolbar.c:3015 msgid "Hide other pane" msgstr "Cacher l'autre panneau" #: e2_toolbar.c:2950 e2_toolbar.c:2952 e2_toolbar.c:2955 e2_toolbar.c:3015 #: e2_toolbar.c:3017 e2_toolbar.c:3020 msgid "_Panes" msgstr "_Panneaux" #: e2_toolbar.c:2953 e2_toolbar.c:2956 e2_toolbar.c:3018 e2_toolbar.c:3021 msgid "Show other pane" msgstr "Montrer l'autre panneau" #: e2_toolbar.c:2958 e2_toolbar.c:3023 msgid "Show _hidden" msgstr "Montrer les éléments _cachés" #: e2_toolbar.c:2959 e2_toolbar.c:3024 msgid "Display hidden items in this directory" msgstr "Afficher les éléments cachés de ce dossier" #: e2_toolbar.c:2960 e2_toolbar.c:3025 msgid "Hide _hidden" msgstr "Masquer les éléments _cachés" #: e2_toolbar.c:2961 e2_toolbar.c:3026 msgid "Do not display hidden items in this directory" msgstr "Masquer les éléments cachés de ce dossier" #: e2_toolbar.c:2962 e2_toolbar.c:2964 e2_toolbar.c:3027 e2_toolbar.c:3029 msgid "Fil_ters" msgstr "Fil_tres" #: e2_toolbar.c:2963 e2_toolbar.c:3028 msgid "Set rules for the items to be displayed" msgstr "Définir les règles pour l'affichage des éléments" #: e2_toolbar.c:2965 e2_toolbar.c:3030 msgid "Set/remove rules for the items to be displayed" msgstr "" "Définir/supprimer les règles pour déterminer quels éléments doivent être " "affichés" #: e2_toolbar.c:2967 e2_toolbar.c:3032 msgid "_VFS" msgstr "_VFS" #: e2_toolbar.c:2968 e2_toolbar.c:3033 msgid "Show a virtual directory in this pane" msgstr "Afficher un dossier virtuel dans ce panneau" #: e2_toolbar.c:2969 e2_toolbar.c:3034 msgid "_LocalFS" msgstr "_LocalFS" #: e2_toolbar.c:2977 e2_toolbar.c:3042 msgid "_Marks" msgstr "_Marques" #: e2_toolbar.c:2978 e2_toolbar.c:3043 msgid "Bookmarks" msgstr "Signets" #: e2_toolbar.c:2980 e2_toolbar.c:3045 msgid "Add the current directory to the top of the bookmarks list" msgstr "Ajouter le dossier courant en tête de la liste des signets" #: e2_toolbar.c:2984 e2_toolbar.c:3049 msgid "Add the current directory to the bottom of the bookmarks list" msgstr "Ajouter le dossier courant à la fin de la liste des signets" #: e2_toolbar.c:2987 e2_toolbar.c:3052 msgid "_Edit bookmarks" msgstr "_Editer les signets" #: e2_toolbar.c:2988 e2_toolbar.c:3053 msgid "Open the bookmarks configuration dialog" msgstr "Ouvrir le dialogue de configuration des signets" #: e2_toolbar.c:2991 e2_toolbar.c:2994 e2_toolbar.c:3056 e2_toolbar.c:3062 msgid "Mi_rror" msgstr "Mi_rroriser" #: e2_toolbar.c:2992 e2_toolbar.c:2995 e2_toolbar.c:3057 e2_toolbar.c:3063 msgid "Go to directory shown in other pane" msgstr "Aller au dossier affiché dans l'autre panneau" #: e2_toolbar.c:2997 e2_toolbar.c:3058 e2_toolbar.c:3066 msgid "Go to previous directory in history" msgstr "Aller au dossier précédent dans l'historique" #: e2_toolbar.c:2997 e2_toolbar.c:3058 e2_toolbar.c:3066 msgid "_Back" msgstr "_Précédent" #: e2_toolbar.c:2998 e2_toolbar.c:3059 e2_toolbar.c:3065 msgid "Go up to parent directory" msgstr "Remonter au dossier parent" #: e2_toolbar.c:2999 e2_toolbar.c:3060 e2_toolbar.c:3064 msgid "Go to next directory in history" msgstr "Aller au dossier suivant dans l'historique" #: e2_toolbar.c:2999 e2_toolbar.c:3060 e2_toolbar.c:3064 msgid "_Forward" msgstr "_Suivant" #: e2_toolbar.c:3079 msgid "bar" msgstr "barre" #: e2_tree_dialog.c:1109 msgid "Copy selected path" msgstr "Copier le chemin sélectionné" #: e2_tree_dialog.c:1113 msgid "Collapse all paths" msgstr "Replier tous les chemins" #: e2_tree_dialog.c:1118 msgid "_Strict hiding" msgstr "_Masquage strict" #: e2_tree_dialog.c:1127 #, fuzzy msgid "" "If active, hidden ancestors of visible directories will not be displayed" msgstr "" "Si activé, les ancêtres masqués des dossiers visibles ne seront pas affichées" #: e2_tree_dialog.c:1388 msgid "pane 1 navigator" msgstr "navigateur du panneau 1" #: e2_tree_dialog.c:1388 msgid "pane 2 navigator" msgstr "navigateur du panneau 2" #: e2_tree_dialog.c:1460 msgid "Toggle display of hidden directories" msgstr "Montrer / masquer les éléments cachés de ce dossier" #: e2_utils.c:109 msgid "Not enough memory! Things may not work as expected" msgstr "Pas assez de mémoire ! Des comportements inattendus sont possibles." #: e2_utils.c:159 msgid "Cannot read USAGE help document" msgstr "Impossible de lire le document d'aide UTILISATION" #: e2_utils.c:845 msgid "No item selected" msgstr "Aucun élément sélectionné" #: e2_utils.c:888 msgid "No item selected in other pane" msgstr "Aucun élément sélectionné dans l'autre panneau" #: e2_utils.c:978 msgid "No matching '}' found in action text" msgstr "Aucune '}' correspondante trouvée dans le texte de l'action" #: e2_utils.c:1070 msgid "No matching '$' found in action text" msgstr "Aucune '$' correspondant trouvé dans le texte de l'action" #: e2_utils.c:1312 #, c-format msgid "Cannot access %s, going to %s instead" msgstr "Impossible d'accéder au dossier %s, ouverture par défaut de %s " #: e2_vfs_dialog.c:563 msgid "You must specify the site to open" msgstr "" #: e2_vfs_dialog.c:598 #, fuzzy, c-format msgid "Cannot open '%s'" msgstr "Impossible d'ouvrir %s" #: e2_vfs_dialog.c:685 msgid "vfs data" msgstr "" #: e2_vfs_dialog.c:708 msgid "You may provide a short-name for use in labels and matching" msgstr "" #: e2_vfs_dialog.c:721 msgid "protocol" msgstr "" #: e2_vfs_dialog.c:726 e2_vfs_dialog.c:732 e2p_find.c:2999 msgid "type" msgstr "type" #: e2_vfs_dialog.c:761 #, fuzzy msgid "host name" msgstr "maison" #: e2_vfs_dialog.c:761 msgid "parent identifier" msgstr "" #: e2_vfs_dialog.c:767 msgid "Domain name of the site e.g. your.computer.net" msgstr "" #: e2_vfs_dialog.c:768 msgid "Full URI for the parent space, if any" msgstr "" #: e2_vfs_dialog.c:788 msgid "path to archive" msgstr "" #: e2_vfs_dialog.c:794 #, fuzzy msgid "initial directory" msgstr "Dans le dossier _suivant :" #: e2_vfs_dialog.c:861 #, fuzzy msgid "Absolute path of directory at the site to be initially displayed" msgstr "Définit les règles pour l'affichage des éléments" #: e2_vfs_dialog.c:865 msgid "Absolute path of archive in this space, including archoive's full name" msgstr "" #: e2_vfs_dialog.c:870 msgid "Path to be initially displayed, if any" msgstr "" #: e2_vfs_dialog.c:894 #, fuzzy msgid "user" msgstr "user" #: e2_vfs_dialog.c:905 msgid "" "Username needed to log on to the specified site, or leave blank for anonymous" msgstr "" #: e2_vfs_dialog.c:920 msgid "password" msgstr "mot de passe" #: e2_vfs_dialog.c:930 msgid "" "Password needed to log on to the specified site, or leave blank for email " "address" msgstr "" #: e2_vfs_dialog.c:934 msgid "Password, if any, needed to open the archive" msgstr "" #: e2_vfs_dialog.c:963 #, fuzzy msgid "port" msgstr "exporter" #: e2_vfs_dialog.c:973 msgid "Port number to access the specified site, or leave blank for default" msgstr "" #: e2_vfs_dialog.c:1005 #, fuzzy msgid "_Previous" msgstr "_groupes" #: e2_vfs_dialog.c:1006 msgid "Show data for previously-used place with same protocol/type" msgstr "" #: e2_vfs_dialog.c:1009 #, fuzzy msgid "_Next" msgstr "_Appliquer" #: e2_vfs_dialog.c:1010 msgid "Show data for next-used place with same protocol/type" msgstr "" #: e2_vfs_dialog.c:1019 e2_vfs_dialog.c:1563 #, fuzzy msgid "_Local" msgstr "_Local" #: e2_vfs_dialog.c:1020 e2_vfs_dialog.c:1567 msgid "Revert to native filesystem" msgstr "" #: e2_vfs_dialog.c:1026 #, fuzzy msgid "Store this data but do not open the place" msgstr "Replacer cet onglet sur le panneau de sortie" #: e2_vfs_dialog.c:1030 #, fuzzy msgid "Open this place" msgstr "Remplacer celui-ci" #: e2_vfs_dialog.c:1413 #, fuzzy msgid "vfs history" msgstr "historique" #: e2_vfs_dialog.c:1427 msgid "Protocol" msgstr "" #: e2_vfs_dialog.c:1428 msgid "Host" msgstr "" #: e2_vfs_dialog.c:1431 e2p_gvfs.c:652 #, fuzzy msgid "Password" msgstr "mots" #: e2_vfs_dialog.c:1432 #, fuzzy msgid "Port" msgstr "_Configurer" #: e2_vfs_dialog.c:1433 #, fuzzy msgid "Alias" msgstr "alias" #: e2_vfs_dialog.c:1553 #, fuzzy msgid "Delete the selected row" msgstr "Supprime la ligne sélectionnée" #: e2_vfs_dialog.c:1561 #, fuzzy msgid "Add a row after the selected one" msgstr "Ajoute une ligne après la ligne sélectionnée" #: e2_vfs_dialog.c:1574 #, fuzzy msgid "Open the place described in the selected row" msgstr "Ajoute un fils à la ligne sélectionnée" #: e2_vfs_dialog.c:1580 #, fuzzy msgid "Save and close" msgstr "E_nregistrer sous..." #: e2_view_dialog.c:401 #, c-format msgid "Cannot read '%s'" msgstr "Impossible de lire %s" #: e2_view_dialog.c:417 #, c-format msgid "'%s' is a directory" msgstr "'%s' est un dossier" #: e2_view_dialog.c:468 #, c-format msgid "Encoding conversion command '%s' failed" msgstr "Échec de la commande de conversion d'encodage %s" #: e2_view_dialog.c:508 #, c-format msgid "Conversion from %s encoding failed: \"%s\"" msgstr "La conversion depuis l'encodage %s a échoué : \"%s\"" #: e2_view_dialog.c:947 msgid "Copy selected text" msgstr "Copier les lignes sélectionnées" #: e2_view_dialog.c:1004 msgid "Finds" msgstr "Cherchés" #: e2_view_dialog.c:1018 msgid "If activated, text case does matter when searching" msgstr "Si activé, la casse du texte est prise en compte lors de la recherche." #: e2_view_dialog.c:1018 msgid "_match case" msgstr "_respecter la casse" #: e2_view_dialog.c:1023 msgid "If activated, matches must be surrounded by word-separator characters" msgstr "" "Si activé, les correspondances doivent être entourées par des caractères de " "séparation de mots." #: e2_view_dialog.c:1023 msgid "wh_ole words" msgstr "m_ots entiers" #: e2_view_dialog.c:1027 msgid "If activated, searching proceeds toward document start" msgstr "Si activé, la recherche se fait en remontant dans le document." # button label, keep as short as practicable #: e2_view_dialog.c:1027 msgid "_backward" msgstr "_en arrière" #: e2_view_dialog.c:1030 msgid "If activated, searching cycles from either end to the other" msgstr "" "Si activé, la recherche reboucle lorsqu'elle arrive en fin ou en début de " "texte vers l'autre bout." #: e2_view_dialog.c:1030 msgid "_loop" msgstr "_reboucler" #: e2_view_dialog.c:1518 msgid "displaying file" msgstr "affichage du fichier" #: e2_view_dialog.c:1592 msgid "_wrap" msgstr "_retour automatique" #: e2_view_dialog.c:1605 msgid "Show the search options bar" msgstr "Afficher la barre des options de recherche" #: e2_view_dialog.c:1848 msgid "wrap text" msgstr "retour automatique à la ligne du texte" #: e2_view_dialog.c:1849 msgid "This causes the view window to open with text-wrapping enabled" msgstr "" "Ceci entraîne l'activation du retour automatique à la ligne lors de " "l'ouverture de la fenêtre de visualisation." #: e2_view_dialog.c:1853 msgid "window width" msgstr "largeur de fenêtre" #: e2_view_dialog.c:1854 msgid "" "The view window will default to showing this many characters per line (but " "the the displayed buttons may make it wider than this)" msgstr "" "La fenêtre de visualisation affichera par défaut ce nombre de caractères par " "ligne (mais les boutons affichés peuvent la rendre plus large que ça)." #: e2_view_dialog.c:1858 msgid "window height" msgstr "hauteur de fenêtre" #: e2_view_dialog.c:1859 msgid "The view window will default to showing this many lines of text" msgstr "" "La fenêtre de visualisation affichera par défaut ce nombre de lignes de texte" #: e2_view_dialog.c:1866 msgid "custom font for viewing files" msgstr "police personnalisée pour l'affichage des fichiers" #: e2_view_dialog.c:1867 msgid "This is the font used for text in each view dialog" msgstr "" "Ceci est la fonte utilisée pour le texte dans le dialogue d'affichage de " "fichier." #: e2_view_dialog.c:1870 msgid "case sensitive searches" msgstr "rechercher en respectant la casse" #: e2_view_dialog.c:1871 msgid "" "This causes the view window search-bar to first open with case-sensitive " "searching enabled" msgstr "" "Ceci entraîne l'activation de l'option de respect de la casse lors de " "l'ouverture initiale de la barre de recherche de la fenêtre de visualisation." #: e2_view_dialog.c:1875 msgid "show last search string" msgstr "afficher la dernière chaîne de recherche" #: e2_view_dialog.c:1876 msgid "" "This shows the last search-string in the entry field, when the view window " "search-bar is displayed" msgstr "" "Ceci entraîne l'affichage de la dernière chaîne de recherche dans le champ " "de saisie,lorsque la barre de recherche de la fenêtre de visualisation est " "affichée." #: e2_view_dialog.c:1880 msgid "keep search history" msgstr "conserver l'historique de recherche" #: e2_view_dialog.c:1881 msgid "This causes search strings to be remembered between sessions" msgstr "" "Ceci entraîne la mémorisation du texte recherché d'une session d'emelfm2 à " "l'autre." #: e2_window.c:1148 #, c-format msgid "displayed & %d concealed " msgstr "affichés et %d cachés" #: e2_window.c:1151 #, c-format msgid "%s%d selected item(s) of %d %sin %s" msgstr "%s%d élément(s) sélectionné(s) sur %d %s de %s" #: e2p_acl.c:222 msgid "Whole" msgstr "En entier" #: e2p_acl.c:229 e2p_acl.c:2096 msgid "Mask" msgstr "Masque" #: e2p_acl.c:1102 e2p_acl.c:1135 e2p_acl.c:3374 e2p_acl.c:3545 msgid "Directory ACL" msgstr "Dossier ACL" #: e2p_acl.c:1102 e2p_acl.c:1135 e2p_acl.c:3364 e2p_acl.c:3515 #, fuzzy msgid "General ACL" msgstr "Général ACL" #: e2p_acl.c:1107 #, c-format msgid "Cannot apply %s '%s' for %s" msgstr "Impossible de procéder à %s '%s' à %s" #: e2p_acl.c:1140 #, c-format msgid "Cannot apply %s '%s' for %s - Invalid" msgstr "Impossible de procéder à %s '%s' à '%s' - invalide" #: e2p_acl.c:3351 msgid "No directory-changes have been selected" msgstr "Pas de changements sur les répertoires sélectionnées" #: e2p_acl.c:3361 #, c-format msgid "The specified %s is likely to ba a problem" msgstr "La spécification %s va vraisemblablement poser problème" #: e2p_acl.c:3461 msgid "extended permissions" msgstr "permissions étendues" #: e2p_acl.c:3514 msgid "unable to display" msgstr "impossible d'afficher" #: e2p_acl.c:3620 msgid "General" msgstr "Général" #: e2p_acl.c:3663 msgid "Data:" msgstr "Données :" #: e2p_acl.c:3672 msgid "S_hown" msgstr "_Afficher" #: e2p_acl.c:3680 msgid "Changes will be based only on the data shown above" msgstr "" "Les changements seront basées unqiement sur les données présentées ci-dessus" #: e2p_acl.c:3681 msgid "_Varied" msgstr "_Varié" #: e2p_acl.c:3690 msgid "" "Changes will be based on the standard permissions of the affected item as " "modified by the data shown above" msgstr "" "Les changements seront basés selon les permissions standard de l'élément " "sélectionné\n" "comme modifié par les données présentées ci-dessus" #: e2p_acl.c:3692 msgid "S_ystem" msgstr "_Système" #: e2p_acl.c:3701 msgid "" "Changes will be based only on the standard (non-ACL) permissions of the " "affected item" msgstr "" "Les changements seront basés seulement sur les permissions standard (non " "ACL) de l'élément affecté" #: e2p_acl.c:3713 msgid "_Nuke" msgstr "_Détruire" #: e2p_acl.c:3721 msgid "Clear as much of the item's ACL as possible" msgstr "Nettoyer autant que possible les attributs ACL de l'élément" #: e2p_acl.c:3732 msgid "_Whole" msgstr "_Entier" #: e2p_acl.c:3740 msgid "" "Conveniently sets all allowed 'whole' values. For those entries, the action " "will apply to the whole of the entry, Otherwise, the action affects only the " "permissions of that entry" msgstr "" #: e2p_acl.c:3764 #, fuzzy msgid "dirs-_general" msgstr "répertoire-général" #: e2p_acl.c:3772 msgid "" "if activated, specified changes to the \"general\" ACL will be applied to " "any affected directory" msgstr "" "si activé, les changements spécifiés de l'ACL générale seront appliqués à " "tout répertoire affecté" #: e2p_acl.c:3774 #, fuzzy msgid "dirs-_default" msgstr "répertoire-défaut" #: e2p_acl.c:3782 msgid "" "if activated, specified changes to the \"directories-only\" ACL will be " "applied to any affected directory" msgstr "" "si activé, les changements spécifiés de l'ACL des répertoires seuls seront " "appliqués à tout répertoire affecté" #: e2p_acl.c:3801 msgid "Insert a row in the ACL" msgstr "Insérer une ligne dans l'ACL" #: e2p_acl.c:3805 msgid "De_lete" msgstr "_Supprimer" #: e2p_acl.c:3811 msgid "Delete the selected row from the ACL" msgstr "Supprime la ligne sélectionnée de l'ACL" #: e2p_acl.c:4303 msgid "acl" msgstr "acl" #: e2p_acl.c:4307 msgid "_Access" msgstr "_Accès" #: e2p_acl.c:4310 msgid "_Access.." msgstr "_Accès.." #: e2p_acl.c:4311 e2p_acl.c:4319 msgid "Change extended permissions of selected items" msgstr "Changer les permissions étendues des éléments sélectionnés" #: e2p_acl.c:4317 msgid "Change _ACLs.." msgstr "Changer les _ACLs..." #: e2p_acl.c:4321 msgid "_Replicate" msgstr "_Repliquer" #: e2p_acl.c:4323 msgid "" "Recursively apply ACLs of selected items to matching items in the other pane" msgstr "" "Appliquer récursivement les attributs ACL des éléments sélectionnés aux " "éléments correspondants dans l'autre panneau" #: e2p_acl.c:4363 msgid "copy_acl" msgstr "copier_acl" #: e2p_clone.c:82 e2p_clone.c:160 msgid "clone" msgstr "cloner" #: e2p_clone.c:163 msgid "C_lone.." msgstr "C_loner..." #: e2p_clone.c:164 msgid "Copy selected item(s), each with new name, to the current directory" msgstr "" "Copier les éléments sélectionnés dans le dossier courant en les renommant" #: e2p_config.c:371 #, c-format msgid "Bad configuration data for %s, not installed" msgstr "Mauvaises données de configuration pour %s, pas d'installation" #: e2p_config.c:504 #, c-format msgid "Incompatible format - %s" msgstr "Format incompatible - %s" #: e2p_config.c:677 msgid "select configuration data file" msgstr "sélectionner le fichier de configuration" #: e2p_config.c:751 msgid "save configuration data file" msgstr "sauvegarder les données de configuration" #: e2p_config.c:810 e2p_config.c:878 msgid "select icons directory" msgstr "sélectionner le dossier des icônes" #: e2p_config.c:1035 msgid "Save configuration data in" msgstr "Sauvergarder les données de configuration dans" #: e2p_config.c:1058 e2p_crypt.c:2968 msgid "backup" msgstr "sauvegarde" #: e2p_config.c:1097 e2p_config.c:1157 e2p_config.c:1284 e2p_config.c:1315 msgid "Se_lect" msgstr "_Sélectionner" #: e2p_config.c:1098 msgid "Select the file in which to store the config data" msgstr "" "Sélectionner le fichier dans lequel doivent être sauvegardées les " "informations de configuration" #: e2p_config.c:1101 msgid "Save current config data in the specified file" msgstr "Sauvegarder les informations de configuration dans le fichier spécifié" #: e2p_config.c:1107 msgid "export" msgstr "exporter" #: e2p_config.c:1126 msgid "Get configuration data from" msgstr "Extraire les informations de configuration de" #: e2p_config.c:1158 msgid "Select the config file from which to get the data" msgstr "" "Sélectionner le fichier de configuration d'où doivent être extraites les " "informations" #: e2p_config.c:1162 msgid "Import config data in accord with choices below" msgstr "" "Importer les données de configuration en tenant compte des options ci-dessous" #: e2p_config.c:1171 msgid "_all options" msgstr "_toutes les options" #: e2p_config.c:1173 msgid "all '_non-group' options" msgstr "toutes les options _autres que les options de groupe" #: e2p_config.c:1177 msgid "all 'g_roup' options" msgstr "toutes les options de g_roupe" #: e2p_config.c:1179 msgid "_specific group option(s)" msgstr "option(s) de groupe spécifique(s)" #: e2p_config.c:1180 msgid "_groups" msgstr "_groupes" #: e2p_config.c:1235 msgid "import" msgstr "importer" #: e2p_config.c:1254 msgid "Use icons in" msgstr "Utiliser les icônes de" #: e2p_config.c:1285 msgid "Select the directory where the icons are" msgstr "Sélectionner le dossier contenant les icônes" #: e2p_config.c:1289 msgid "Apply the chosen icon directory" msgstr "Appliquer le dossier choisi" #: e2p_config.c:1297 msgid "Copy current icons to" msgstr "Copier les icônes courantes dans" #: e2p_config.c:1316 msgid "Select the directory where the icons will be saved" msgstr "Sélectionner le dossier où seront sauvegardées les icônes" #: e2p_config.c:1319 msgid "C_opy" msgstr "C_opier" #: e2p_config.c:1320 msgid "Copy the icons to the chosen directory" msgstr "Copier les icônes vers le répertoire choisi" #: e2p_config.c:1341 msgid "manage configuration data" msgstr "gérer les informations de configuration" #: e2p_config.c:1376 msgid "manage" msgstr "gérer" #: e2p_config.c:1378 msgid "_Configure.." msgstr "_Configurer..." #: e2p_config.c:1379 msgid "Export or import configuration data" msgstr "Exporter ou importer les informations de configuration" #: e2p_cpbar.c:402 #, c-format msgid "" "copying %s\n" "to %s\n" "this is item %s of %s" msgstr "" "copie de %s\n" "vers %s\n" "élément %s sur %s" #: e2p_cpbar.c:413 e2p_mvbar.c:422 #, c-format msgid "%.2f MB of %.2f MB (%.0f\\%%)" msgstr "%.2f Mo sur %.2f Mo (%.0f\\%%)" #: e2p_cpbar.c:673 e2p_mvbar.c:715 #, c-format msgid "Cannot put anything in %s" msgstr "Impossible de mettre quoique ce soit dans %s" #: e2p_cpbar.c:695 msgid "copying" msgstr "copie en cours" #: e2p_cpbar.c:720 e2p_mvbar.c:794 msgid "_Resume" msgstr "_Redémarrer" #: e2p_cpbar.c:721 msgid "Resume copying after pause" msgstr "Redémarrer la copie après suspension" #: e2p_cpbar.c:726 e2p_mvbar.c:800 msgid "_Pause" msgstr "_Suspendre" #: e2p_cpbar.c:727 msgid "Suspend copying, after the current item" msgstr "Suspendre le processus de copie, après copie de l'élément courant" #: e2p_cpbar.c:732 e2p_cpbar.c:736 msgid "Abort the copying" msgstr "Annuler la copie" #: e2p_cpbar.c:871 msgid "cpbar" msgstr "barre_de_copie" #: e2p_cpbar.c:872 msgid "cpbar_with_time" msgstr "barre_de_copie_avec_duree" #: e2p_cpbar.c:886 msgid "Copy selected item(s), with displayed progress details" msgstr "" "Copier les éléments sélectionnés, avec affichage du détail de la progression" #: e2p_cpbar.c:888 msgid "Copy with _times" msgstr "Copier en gardant l'horodatage" #: e2p_cpbar.c:890 msgid "" "Copy selected item(s), with preserved time-properties and displayed progress " "details" msgstr "" "Copier les éléments sélectionnés, en préservant leurs dates et en affichant " "le détail de la progression" #: e2p_crypt.c:515 #, c-format msgid "No LZO compression-library for file %s" msgstr "Pas de bibliothèque de compression LZO pour le fichier %s" #: e2p_crypt.c:550 #, c-format msgid "No ZLIB compression-library for file %s" msgstr "Pas de bibliothèque de compression ZLIB pour le fichier %s<" #: e2p_crypt.c:585 #, c-format msgid "No BZIP compression-library for file %s" msgstr "Pas de bibliothèque de compression BZIP pour le fichier %s<" #: e2p_crypt.c:590 #, c-format msgid "Unknown compression-library for file %s" msgstr "Bibliothèque de compression inconnue pour le fichier %s" #: e2p_crypt.c:913 e2p_crypt.c:1811 #, c-format msgid "Cannot open '%s' for writing" msgstr "Impossible d'ouvrir '%s' en écriture" #: e2p_crypt.c:1131 #, c-format msgid "Wrong password for %s" msgstr "Mauvais mot de passe pour %s" #: e2p_crypt.c:1247 #, c-format msgid "Error decompressing file %s" msgstr "Erreur dans la décompression du fichier %s" #: e2p_crypt.c:1960 #, c-format msgid "" "%s does not end with \"%s\".\n" "Process this file anyway?" msgstr "" "%s ne finit pas par \"%s\".\n" "Voulez-vous néanmoins continuer ?" #: e2p_crypt.c:2303 #, c-format msgid "Cannot process all of %s" msgstr "Impossible de travailler sur tous les %s" #: e2p_crypt.c:2785 msgid "en/decrypt file" msgstr "(dé)chiffre le fichier" #: e2p_crypt.c:2801 msgid "Symbolic link" msgstr "Lien symbolique" #: e2p_crypt.c:2817 #, c-format msgid " to %s" msgstr "vers %s" #: e2p_crypt.c:2825 msgid "encrypt" msgstr "chiffrer" #: e2p_crypt.c:2828 msgid "decrypt" msgstr "déchiffrer" #: e2p_crypt.c:2832 msgid "encrypted file will have same name" msgstr "le fichier chiffré aura le même nom" #: e2p_crypt.c:2837 msgid "append this to encrypted file name" msgstr "accoler ceci au nom du fichier chiffré" #: e2p_crypt.c:2857 msgid "encrypted file name will be" msgstr "le nom du fichier chiffré sera" #: e2p_crypt.c:2876 msgid "decrypted file will have same name" msgstr "le fichier déchiffré aura le même nom" #: e2p_crypt.c:2880 msgid "decrypted file will have embedded name" msgstr "le fichier déchiffré aura le nom embarqué" #: e2p_crypt.c:2885 msgid "strip this from end of decrypted file name" msgstr "retirer ceci de la fin du nom du fichier déchiffré" #: e2p_crypt.c:2904 msgid "decrypted file name will be" msgstr "enlever ceci de la fin du fichier déchiffré" #: e2p_crypt.c:2948 msgid "decrypted file will have stored owners, permissions and dates" msgstr "" "le fichier déchiffré aura les propriétaires, les permissions et les dates " "sauvegardées" #: e2p_crypt.c:2948 msgid "restore properties" msgstr "restaurer les propriétés" #: e2p_crypt.c:2953 e2p_crypt.c:2957 msgid "compress" msgstr "compresser" #: e2p_crypt.c:2953 e2p_crypt.c:2957 msgid "compress file before encryption" msgstr "compresser le fichier avant le chiffrement" #: e2p_crypt.c:2965 msgid "store current name, permissions etc in the encrypted file" msgstr "" "sauvegarder le nom courant, les permissions, etc. dans le fichier chiffré" #: e2p_crypt.c:2965 msgid "store properties" msgstr "sauvegarder les propriétés" #: e2p_crypt.c:2968 msgid "backup an existing file with the same name as the processed file" msgstr "" "faire une copie de sauvegarde avec un nom de fichier identique au fichier " "travaillé" #: e2p_crypt.c:2970 msgid "do not remove the original file, after processing it" msgstr "ne pas effacer le fichier original après avoir travaillé avec" #: e2p_crypt.c:2970 msgid "keep original" msgstr "garder l'original" #: e2p_crypt.c:2973 msgid "if file is a symlink, process its target" msgstr "si le fichier est un lien symbolique, travailler sur sa cible" #: e2p_crypt.c:2973 msgid "through links" msgstr "à travers les liens" #: e2p_crypt.c:2975 msgid "recurse directories" msgstr "parcourir les sous-dossiers" #: e2p_crypt.c:3219 #, c-format msgid "You do not have authority to modify %s" msgstr "Vous n'êtes pas autorisé à modifier %s" #: e2p_crypt.c:3336 msgid "crypt" msgstr "chiffrer" #: e2p_crypt.c:3339 msgid "_En/decrypt.." msgstr "(Dé)_chiffrer" #: e2p_crypt.c:3340 msgid "Encrypt or decrypt selected items" msgstr "Chiffrer ou déchiffrer les éléments sélectionnées" #: e2p_dircmp.c:876 msgid "compare" msgstr "comparer" #: e2p_dircmp.c:879 msgid "C_ompare" msgstr "_Comparer" #: e2p_dircmp.c:880 msgid "Select active-pane items which are duplicated in the other pane" msgstr "" "Sélectionne les éléments du panneau actif qui doivent être dupliqués dans " "l'autre panneau" #: e2p_du.c:165 msgid "total size: " msgstr "taille totale : " #: e2p_du.c:181 msgid "kilobytes" msgstr "Kilooctets" #: e2p_du.c:194 msgid "Megabytes" msgstr "Megaoctets" #: e2p_du.c:207 msgid "gigabytes" msgstr "Gigaoctets" #: e2p_du.c:222 e2p_rename.c:934 e2p_rename.c:1049 msgid "in" msgstr "dans" #: e2p_du.c:224 msgid "(one or more are hidden)" msgstr "(un ou plusieurs sont cachés)" #: e2p_du.c:270 msgid "_Disk usage" msgstr "_Occupation disque" #: e2p_du.c:271 msgid "Calculate the disk usage of selected item(s)" msgstr "Calcule l'espace disque occupé par les éléments sélectionnés" #: e2p_find.c:316 msgid "all files" msgstr "tous les fichiers" #: e2p_find.c:319 msgid "images" msgstr "images" #: e2p_find.c:320 msgid "music" msgstr "musique" #: e2p_find.c:321 msgid "videos" msgstr "vidéos" #: e2p_find.c:322 msgid "text files" msgstr "fichiers texte" #: e2p_find.c:323 msgid "development files" msgstr "fichiers de développement" #: e2p_find.c:324 msgid "other files" msgstr "autres fichiers" #: e2p_find.c:334 msgid "conversations" msgstr "conversations" #: e2p_find.c:336 msgid "applications" msgstr "applications" #: e2p_find.c:338 msgid "emails" msgstr "Messages e-mails" #: e2p_find.c:339 msgid "email attachments" msgstr "Pièces jointes aux messages" #: e2p_find.c:2623 e2p_rename.c:1460 msgid "Search for items:" msgstr "Chercher des fichiers :" #: e2p_find.c:2626 e2p_rename.c:1461 msgid "any_where" msgstr "n'importe _où" #: e2p_find.c:2629 e2p_rename.c:1464 msgid "in _active directory" msgstr "dans le dossier du panneau _actif" #: e2p_find.c:2637 e2p_rename.c:1466 msgid "in _other directory" msgstr "dans le dossier du panneau _inactif" #: e2p_find.c:2645 e2p_rename.c:1468 msgid "in _this directory" msgstr "dans le dossier _suivant" #: e2p_find.c:2669 msgid "Recurse subdirectories" msgstr "Parcourir les sous-dossiers" #: e2p_find.c:2712 msgid "name" msgstr "nom" #: e2p_find.c:2719 msgid "Find items whose name:" msgstr "Chercher les fichiers dont le nom :" #: e2p_find.c:2724 e2p_find.c:3177 msgid "is" msgstr "est de la forme" #: e2p_find.c:2726 e2p_find.c:3179 msgid "is like" msgstr "contient" #: e2p_find.c:2728 e2p_find.c:3181 msgid "matches this regex" msgstr "correspond à cette expression régulière" #: e2p_find.c:2730 e2p_find.c:3183 msgid "ignore case" msgstr "ne pas tenir compte de la casse" #: e2p_find.c:2754 msgid "size" msgstr "taille" #: e2p_find.c:2762 msgid "Find items whose size is:" msgstr "Chercher les fichiers dont la taille est :" #: e2p_find.c:2767 msgid "less than:" msgstr "inférieure à :" #: e2p_find.c:2769 msgid "equal to:" msgstr "égale à :" #: e2p_find.c:2772 msgid "more than" msgstr "supérieure à :" #: e2p_find.c:2800 msgid "mime" msgstr "type mime" #: e2p_find.c:2809 msgid "Find files whose mimetype is like this:" msgstr "Chercher les fichiers dont le type mime contient :" #: e2p_find.c:2835 msgid "save" msgstr "sauvegarde" #: e2p_find.c:2840 msgid "Find items most-recently saved:" msgstr "Chercher les fichiers sauvegardés récemment :" #: e2p_find.c:2845 e2p_find.c:2879 e2p_find.c:2913 msgid "before:" msgstr "avant :" #: e2p_find.c:2846 e2p_find.c:2880 e2p_find.c:2914 msgid "on/at:" msgstr "à :" #: e2p_find.c:2848 e2p_find.c:2882 msgid "after:" msgstr "après :" #: e2p_find.c:2869 msgid "access" msgstr "accès" #: e2p_find.c:2874 msgid "Find items most-recently read or executed:" msgstr "Chercher les fichiers qui ont récemment été lus ou exécutés :" #: e2p_find.c:2908 msgid "Find items whose inode was last changed:" msgstr "" "Chercher les fichiers dont l'inode a été modifié pour la dernière fois :" #: e2p_find.c:2916 msgid "after" msgstr "après" #: e2p_find.c:2936 msgid "permission" msgstr "permission" #: e2p_find.c:2941 msgid "Find items whose permissions:" msgstr "Chercher les fichiers dont les permissions :" #: e2p_find.c:2945 e2p_find.c:3007 msgid "are" msgstr "sont" #: e2p_find.c:2947 msgid "include" msgstr "incluent" #: e2p_find.c:2949 msgid "exclude" msgstr "n'incluent pas" #: e2p_find.c:2955 msgid "owner read" msgstr "le propriétaire peut lire" #: e2p_find.c:2957 msgid "group read" msgstr "le groupe peut lire" #: e2p_find.c:2959 msgid "anyone read" msgstr "n'importe qui peut lire" #: e2p_find.c:2963 msgid "owner write" msgstr "le propriétaire peut écrire" #: e2p_find.c:2965 msgid "group write" msgstr "le groupe peut écrire" #: e2p_find.c:2967 msgid "anyone write" msgstr "n'importe qui peut écrire" #: e2p_find.c:2971 msgid "owner execute" msgstr "le propriétaire peut exécuter" #: e2p_find.c:2973 msgid "group execute" msgstr "le groupe peut exécuter" #: e2p_find.c:2975 msgid "anyone execute" msgstr "n'importe qui peut exécuter" #: e2p_find.c:2979 msgid "setuid" msgstr "setuid" #: e2p_find.c:2981 msgid "setgid" msgstr "setgid" #: e2p_find.c:2983 msgid "sticky" msgstr "sticky" #: e2p_find.c:3004 msgid "Find items which" msgstr "Chercher les éléments qui" #: e2p_find.c:3009 msgid "are not" msgstr "ne sont pas" #: e2p_find.c:3016 msgid "regular" msgstr "normal" #: e2p_find.c:3023 e2p_find.c:3046 msgid "block device" msgstr "périphérique bloc" #: e2p_find.c:3049 msgid "raw device" msgstr "périphérique brut (raw)" #: e2p_find.c:3053 msgid "fifo" msgstr "fifo" #: e2p_find.c:3077 msgid "Find items with:" msgstr "Chercher les éléments qui:" #: e2p_find.c:3085 msgid "any user id" msgstr "n'importe quel utilisateur" #: e2p_find.c:3087 msgid "specific user id" msgstr "un utilisateur spécifique" #: e2p_find.c:3090 msgid "current user's uid" msgstr "l'utilisateur courant" #: e2p_find.c:3093 msgid "this user id" msgstr "l'id de cet utilisateur" #: e2p_find.c:3100 msgid "unregistered user" msgstr "utilisateur non enregistré" #: e2p_find.c:3119 msgid "any group id" msgstr "n'importe quel groupe" #: e2p_find.c:3121 msgid "specific group id" msgstr "un groupe spécifique" #: e2p_find.c:3124 msgid "current user's gid" msgstr "le groupe de l'utilisateur courant" #: e2p_find.c:3127 msgid "this group id" msgstr "l'id de ce groupe" #: e2p_find.c:3134 msgid "unregistered group" msgstr "groupe non enregistré" #: e2p_find.c:3165 msgid "content" msgstr "contenu" #: e2p_find.c:3173 msgid "Using grep, find files with content that:" msgstr "En utilisant grep, chercher les fichiers dont le contenu :" #: e2p_find.c:3220 msgid "Using" msgstr "En utilisant" #: e2p_find.c:3229 msgid "find files with content that is:" msgstr "Chercher des fichiers dont le contenu :" #: e2p_find.c:3514 msgid "Day" msgstr "Jour" #: e2p_find.c:3522 msgid "Month" msgstr "Mois" #: e2p_find.c:3532 msgid "Year" msgstr "Année" #: e2p_find.c:3543 msgid "Hour" msgstr "Heure" #: e2p_find.c:3551 msgid "Minute" msgstr "Minute" #: e2p_find.c:3595 msgid "find files" msgstr "chercher des fichiers" #: e2p_find.c:3614 msgid "get advice on search options on this page" msgstr "aide sur les options de recherche de cette page" #: e2p_find.c:3621 msgid "stop the current search" msgstr "arrêter la recherche courante" #: e2p_find.c:3627 msgid "begin searching" msgstr "démarrer la recherche" #: e2p_find.c:3630 msgid "Clea_r" msgstr "E_ffacer" #: e2p_find.c:3630 msgid "clear all search parameters" msgstr "effacer tous les paramètres de recherche" #: e2p_find.c:3664 msgid "detfind" msgstr "chercher_des_fichiers" #: e2p_find.c:3668 msgid "Find and list items, using detailed criteria" msgstr "Chercher et lister des éléments, en utilisant des critères détaillés" #: e2p_for_each.c:125 msgid "repeat action" msgstr "répéter l'action" #: e2p_for_each.c:126 msgid "Action to run for each selected item:" msgstr "Action à effectuer sur chaque élément sélectionné :" #: e2p_for_each.c:180 msgid "foreach" msgstr "pour_chaque" #: e2p_for_each.c:183 msgid "For _each.." msgstr "Pour _chaque..." #: e2p_for_each.c:184 msgid "Execute an entered command on each selected item separately" msgstr "Exécuter la commande saisie séparément sur chaque élément sélectionné" #: e2p_glob.c:409 msgid "Select items:" msgstr "Sélectionner les éléments :" #: e2p_glob.c:410 msgid "selection filter" msgstr "filtre de sélection" #: e2p_glob.c:416 msgid "Named like" msgstr "Dont le nom est de la forme" #: e2p_glob.c:441 msgid "_Invert" msgstr "_Inverser" #: e2p_glob.c:448 msgid "Select items that DO NOT match the given mask" msgstr "Sélectionne les fichiers qui NE correspondent PAS au modèle saisi" #: e2p_glob.c:449 msgid "Case _sensitive" msgstr "_Respecter la casse" #: e2p_glob.c:567 msgid "glob" msgstr "sélection_globale" #: e2p_glob.c:570 msgid "_Glob.." msgstr "_Sélection globale..." #: e2p_glob.c:571 msgid "Select items matching a specified pattern" msgstr "Sélectionne les éléments correspondant à un modèle spécifié" #: e2p_gvfs.c:636 msgid "login details" msgstr "" #: e2p_gvfs.c:642 #, fuzzy msgid "Domain" msgstr "commande" #: e2p_gvfs.c:769 #, fuzzy, c-format msgid "Cannot unmount %s - %s" msgstr "Impossible de déplacer %s vers %s" #: e2p_gvfs.c:799 e2p_gvfs.c:833 #, fuzzy, c-format msgid "Cannot mount %s - %s" msgstr "Impossible de déplacer %s vers %s" #: e2p_gvfs.c:1002 e2p_gvfs.c:1164 #, fuzzy, c-format msgid "Cannot open file %s - %s" msgstr "Impossible d'ouvrir le fichier %s" #: e2p_gvfs.c:1087 #, fuzzy, c-format msgid "Error reading file %s - %s" msgstr "Erreur à la lecture du fichier %s" #: e2p_gvfs.c:1103 e2p_gvfs.c:1204 #, fuzzy, c-format msgid "Cannot close file %s - %s" msgstr "Impossible d'écrire dans le fichier cache : %s - %s" #: e2p_gvfs.c:1186 #, fuzzy, c-format msgid "Error writing file %s - %s" msgstr "Erreur à l'écriture du fichier '%s' - %s" #: e2p_gvfs.c:1888 msgid "Enable vfs functionality using gvfs library" msgstr "" #: e2p_mvbar.c:411 #, c-format msgid "" "moving %s\n" "to %s\n" "this is item %s of %s" msgstr "" "déplacement de %s\n" "vers %s\n" "élément %s sur %s" #: e2p_mvbar.c:769 msgid "moving" msgstr "déplacement en cours" #: e2p_mvbar.c:795 msgid "Resume moving after pause" msgstr "Redémarrer le déplacement après suspension" #: e2p_mvbar.c:801 msgid "Suspend moving, after the current item" msgstr "" "Suspendre l'opération de déplacement, après déplacement de l'élément courant" #: e2p_mvbar.c:806 e2p_mvbar.c:810 msgid "Abort the moving" msgstr "Annuler le déplacement" #: e2p_mvbar.c:948 msgid "mvbar" msgstr "barre_de_déplacement" #: e2p_mvbar.c:952 msgid "Move selected item(s), with displayed progress details" msgstr "" "Déplacer les éléments sélectionnés, en affichant une barre de progression" #: e2p_names_clip.c:106 msgid "copy_name" msgstr "copier_le_nom" #: e2p_names_clip.c:109 msgid "Copy _names" msgstr "Copier les _noms" #: e2p_names_clip.c:110 msgid "Copy path or name of each selected item to the clipboard" msgstr "" "Copier dans le presse-papier le chemin ou le nom de chaque élément " "sélectionné" #: e2p_pack.c:51 msgid ".tar.gz" msgstr ".tar.gz" #: e2p_pack.c:52 msgid ".tar.bz2" msgstr ".tar.bz2" #: e2p_pack.c:53 msgid ".tar" msgstr ".tar" #: e2p_pack.c:54 msgid ".zip" msgstr ".zip" #: e2p_pack.c:55 msgid ".7z" msgstr ".7z" #: e2p_pack.c:56 msgid ".rar" msgstr ".rar" #: e2p_pack.c:57 msgid ".arj" msgstr ".arj" #: e2p_pack.c:58 msgid ".zoo" msgstr ".zoo" #: e2p_pack.c:195 msgid "Filename:" msgstr "Nom du fichier : " #: e2p_pack.c:196 msgid "archive creation" msgstr "création d'archive" #: e2p_pack.c:251 msgid "pack" msgstr "archiver" #: e2p_pack.c:254 msgid "_Pack.." msgstr "_Compresser.." #: e2p_pack.c:255 msgid "Build an archive containing the selected item(s)" msgstr "Construire une archive contenant le ou les éléments sélectionnés" #: e2p_rename.c:630 msgid "No current name pattern is specified" msgstr "Aucun modèle de nom de fichier courant n'a été spécifié." #: e2p_rename.c:643 msgid "No replacement name pattern is specified" msgstr "Aucun modèle de nom de fichier n'a été spécifié pour le remplacement." #: e2p_rename.c:651 msgid "Replacement name pattern cannot have wildcard(s)" msgstr "La chaîne de remplacement ne peut comporter de joker." #: e2p_rename.c:757 #, c-format msgid "Error in regular expression %s" msgstr "Erreur dans l'expression régulière %s" #: e2p_rename.c:819 #, c-format msgid "Cannot find anything which matches %s" msgstr "Aucune correspondance trouvée pour %s" #: e2p_rename.c:934 msgid "Rename" msgstr "Renommer" #: e2p_rename.c:934 e2p_rename.c:1049 msgid "to" msgstr "en" #: e2p_rename.c:1049 msgid "Renamed" msgstr "Renommé" #: e2p_rename.c:1453 msgid "rename items" msgstr "renommer des éléments" #: e2p_rename.c:1480 msgid "R_ecurse subdirectories" msgstr "_Parcourir les sous-dossiers" #: e2p_rename.c:1485 msgid "_Selected item(s)" msgstr "Élement(s) _sélectionné(s)" #: e2p_rename.c:1490 msgid "Match _exact/wildcard" msgstr "Correspondre _exactement à / à l'expression avec jokers" #: e2p_rename.c:1491 msgid "Match regular e_xpression" msgstr "Correspondre à l'e_xpression régulière" #: e2p_rename.c:1495 msgid "Current name is like this:" msgstr "Le nom courant est de la forme :" #: e2p_rename.c:1516 msgid "New name is _upper case" msgstr "Le nouveau nom est en _majuscules :" #: e2p_rename.c:1518 msgid "New name is _lower case" msgstr "Le nouveau nom est en m_inuscules :" #: e2p_rename.c:1520 msgid "_New name is like this:" msgstr "Le nouveau nom est de la _forme :" #: e2p_rename.c:1532 msgid "Con_firm before each rename" msgstr "Confirmer chaque renommage" #: e2p_rename.c:1539 msgid "Get advice on rename options" msgstr "Obtenir de l'aide sur les options de renommage" #: e2p_rename.c:1542 msgid "Stop the current search" msgstr "Arrêter la recherche courante" #: e2p_rename.c:1547 msgid "_Rename" msgstr "_Renommer" #: e2p_rename.c:1548 msgid "Begin renaming" msgstr "Lancer le renommage" #: e2p_rename.c:1577 msgid "renext" msgstr "suivant" #: e2p_rename.c:1581 msgid "Rename item(s), using wildcards or regular-expressions" msgstr "" "Renommer le(s) élément(s), en utilisant des jokers ou des expressions " "régulières" #: e2p_sort_by_ext.c:86 msgid "sort_by_ext" msgstr "trier_par_extension" #: e2p_sort_by_ext.c:89 msgid "Extension _sort" msgstr "Trier par _extension" #: e2p_sort_by_ext.c:90 msgid "Sort the active file pane by filename extension" msgstr "Trier le panneau actif par extensions de fichier" #: e2p_thumbs.c:1106 msgid "Rotate _+" msgstr "Effectuer une rotation _+" #: e2p_thumbs.c:1107 msgid "Rotate selected images quarter-turn clockwise" msgstr "" "Effectuer une rotation des images sélectionnées d'un quart de tour horaire" #: e2p_thumbs.c:1109 msgid "Rotate _-" msgstr "Effectuer une rotation _-" #: e2p_thumbs.c:1110 msgid "Rotate selected images quarter-turn anti-clockwise" msgstr "" "Effectuer une rotation des images sélectionnées d'un quart de tour anti-" "horaire" #: e2p_thumbs.c:1112 msgid "_Flip" msgstr "_Retourner" #: e2p_thumbs.c:1113 msgid "Flip selected images top-to-bottom" msgstr "Retourner les images sélectionnées de haut en bas" #: e2p_thumbs.c:1123 msgid "_Unselect all" msgstr "Tout _désélectionner" #: e2p_thumbs.c:1129 msgid "Replicate _selection" msgstr "Répliquer la _sélection" #: e2p_thumbs.c:1137 msgid "" "If activated, items selected in this window will also be selected in the " "associated filelist" msgstr "" "Si activé, les éléments sélectionnées dans cette fenêtre le seront également " "dans la liste de fichiers associée" #: e2p_thumbs.c:1140 #, fuzzy msgid "_Clamp size" msgstr "_Masquer" #: e2p_thumbs.c:1148 msgid "" "If activated, thumbnails will be scaled up or down if needed, into the range " "32 to 128 pixels" msgstr "" #: e2p_thumbs.c:1337 msgid "Ascending" msgstr "Par ordre croissant" #: e2p_thumbs.c:1345 msgid "If activated, items are displayed in ascending order" msgstr "Si activé,les éléments sont affichés par ordre croissant" #: e2p_thumbs.c:1477 msgid "pane 1 images" msgstr "images du panneau 1" #: e2p_thumbs.c:1477 msgid "pane 2 images" msgstr "images du panneau 2" #: e2p_thumbs.c:1607 msgid "_Sort" msgstr "_Trier" #: e2p_thumbs.c:1696 msgid "thumbs" msgstr "vignettes" #: e2p_thumbs.c:1699 msgid "_Thumbnail.." msgstr "_Vignette.." #: e2p_thumbs.c:1700 msgid "Display thumbnails of image files in the active pane" msgstr "Afficher les images sous forme de vignettes dans le panneau actif" #: e2p_times.c:212 #, c-format msgid "Cannot change times of %s" msgstr "Impossible de changer l'heure de %s" #: e2p_times.c:383 #, c-format msgid "Cannot get current times of %s" msgstr "Impossible d'obtenir les dates courantes de %s" #: e2p_times.c:442 #, c-format msgid "Cannot interpret date-time %s" msgstr "Impossible d'interpréter la date/heure %s" #: e2p_times.c:534 msgid "" "Changing 'ctime' requires temporary changes to the system clock. That is " "normally unwise, as typically, other things rely on system time. Click 'ok' " "to proceed." msgstr "" "Changer 'ctime' requiert des changements temporaires sur l'horloge système. " "Ce n'est généralement pas conseillé du fait que, typiquement, d'autres " "choses reposent sur l'horloge système. Cliquer sur OK pour poursuivre quand " "même." #: e2p_times.c:689 msgid "times" msgstr "dates" #: e2p_times.c:712 msgid "Current values" msgstr "Valeurs courantes" #: e2p_times.c:713 msgid "New date" msgstr "Nouvelle date" #: e2p_times.c:714 msgid "New time" msgstr "Nouvelle heure" #: e2p_times.c:717 msgid "Content Modified" msgstr "Contenu modifié" #: e2p_times.c:718 msgid "Inode Changed" msgstr "Inode changé" #: e2p_times.c:898 #, c-format msgid "You do not have authority to change time(s) for %s" msgstr "Vous n'êtes pas autorisé à changer le(s) date(s) de %s" #: e2p_times.c:1004 msgid "timeset" msgstr "timeset" #: e2p_times.c:1007 msgid "Change _times.." msgstr "Changer les _dates..." #: e2p_times.c:1008 msgid "Change any of the time properties of selected items" msgstr "" "Changer n'importe laquelle des propriétés de date des éléments sélectionnés" #: e2p_unpack.c:327 msgid "What do you want to do with the unpacked item(s) ?" msgstr "Que voulez vous faire les éléments décompressés ?" #: e2p_unpack.c:331 msgid "Re_pack" msgstr "Re_compresser" #: e2p_unpack.c:333 msgid "_Retain" msgstr "_Retenir" #: e2p_unpack.c:418 msgid "Selected item is not a supported archive" msgstr "L'élément sélectionné ne correspond pas à un type d'archive supporté." #: e2p_unpack.c:432 msgid "Recursive unpack is not supported" msgstr "La décompression récursive n'est pas supportée" #: e2p_unpack.c:521 msgid "unpack_with_plugin" msgstr "décompresser_avec_le_greffon" #: e2p_unpack.c:527 msgid "" "Unpack archive (tar, tar.gz, tar.bz2, zip, 7z, rar, arj, zoo) into a " "temporary directory" msgstr "" "Décompresse dans un dossier temporaire une archive (tar, tar.gz, tar.bz2, " "zip, 7z, rar, arj, zoo)" #: e2p_upgrade.c:42 #, c-format msgid "" "Configuration arrangements for this version %s of %s are considerably " "different from those of old versions. To reliably ensure access to the " "program's current features, it is best to start with fresh settings.\n" "If you proceed, the superseded configuration files in\n" " %s will have '.save' appended to their names.\n" "Feel free to delete them." msgstr "" "Les éléments de configuration de cette version %s de %s sont " "considérablement différents de ceux des versions précédentes. Pour être sûr " "de pouvoir utiliser toutes les fonctions du programme, il est préférable de " "démarrer avec une configuration neuve.\n" "Dans ce cas, les anciens fichiers de configuration de\n" " %s apparaîtront avec l'extension '.save'.\n" "Vous pourrez par la suite les détruire." #: e2p_upgrade.c:50 #, c-format msgid "" "Several default configuration settings of this version %s of %s are " "different from those of recent versions (see changelog).\n" "If you click OK, those settings will be updated where possible.\n" "Or else you can Cancel, and later, via the configuration dialog, " "manuallychange individual settings, or change all settings to current " "defaults." msgstr "" "Plusieurs informations de configuration par défaut de cette version %s de %s " "sont différentes de celles des versions récentes (voir le journal des " "modifications \"changelog\").\n" " Si vous cliquez sur OK, ces informations seront autant que possible mises à " "jour.\n" "Sinon, vous pouvez cliquer sur Annuler et, changer la configuration " "manuellement par la suite, ou même réinitialiser toute la configuration à sa " "valeur par défaut, via le dialogue de configuration." #: e2p_upgrade.c:132 msgid "update information" msgstr "Information de mise à jour" #: e2p_upgrade.c:806 msgid "" msgstr "" #: e2p_view.c:80 msgid "view_with_plugin" msgstr "afficher_avec_le_greffon" #: e2p_view.c:84 #, c-format msgid "Open the first selected item with the %s text-file viewer" msgstr "" "Ouvrir le premier élément sélectionné avec l'afficheur de fichier texte %s" #~ msgid "_file selection" #~ msgstr "sélection de _fichier" #~ msgid "_stock icons" #~ msgstr "_stock d'icônes" #~ msgid "choose query" #~ msgstr "choisir la requête" #~ msgid "rdf" #~ msgstr "rdf" #~ msgid "tracker query" #~ msgstr "requête du traceur" #~ msgid "_Search for" #~ msgstr "_Chercher" #~ msgid "which match:" #~ msgstr "qui correspond à :" #~ msgid "Search for items whose _mimetype is any of:" #~ msgstr "Chercher les éléments dont le type mime est parmi :" #~ msgid "Search for items using this rdf query:" #~ msgstr "Chercher les éléments utilisant cette requête rdf :" #~ msgid "Open query-selection dialog" #~ msgstr "Afficher le dialogue de sélection de la requête" #~ msgid "Get help on constructing queries" #~ msgstr "Obtenir de l'aide sur les requêtes de recherche" #~ msgid "C_lear" #~ msgstr "_Nettoyer" #~ msgid "Clear the current query" #~ msgstr "Effacer le résultat de la recherche courante" #~ msgid "Initiate a query using currently-specified criteria" #~ msgstr "Lance la recherche en utilisant les critères courants" #~ msgid "track" #~ msgstr "tracer" #~ msgid "_Track.." #~ msgstr "_Tracer..." #~ msgid "Find items in the tracker database" #~ msgstr "Cherche des éléments dans la base de donnée du traceur" #~ msgid "" #~ msgstr "" #~ msgid "su" #~ msgstr "su" #~ msgid "wget" #~ msgstr "wget" #~ msgid "_Mounted directory" #~ msgstr "Dossier _monté" #~ msgid "_Remote directory" #~ msgstr "Dossier _distant" #~ msgid "_Archive" #~ msgstr "_Archive" #~ msgid "No password provided" #~ msgstr "Aucun mot de passe fourni" #~ msgid "match unknown users" #~ msgstr "des utilisateurs inconnus" #~ msgid "this gid" #~ msgstr "ce groupe" #~ msgid "match unknown groups" #~ msgstr "des groupes inconnus" #~ msgid "this" #~ msgstr "comprend" #~ msgid "like this" #~ msgstr "est de la forme" #~ msgid "like this regex" #~ msgstr "correspond à cette expression régulière" #, fuzzy #~ msgid "Cannot stat %s" #~ msgstr "Impossible de recueillir les informations sur %s" #~ msgid "Enabled access to %s" #~ msgstr "Impossible d'accéder à %s" #~ msgid "Cannot delete part or all of existing %s" #~ msgstr "Impossible de supprimer tout ou partie de %s" #~ msgid "Cannot read %s" #~ msgstr "Impossible de lire %s" #~ msgid "Cannot delete part or all of %s" #~ msgstr "Impossible de supprimer tout ou partie de %s" #~ msgid "Cannot access directory %s" #~ msgstr "Impossible d'accéder au dossier %s" #, fuzzy #~ msgid "Cannot write to directory %s" #~ msgstr "Impossible de créer le dossier %s" #~ msgid "'%s' is not a supported archive type" #~ msgstr "'%s' ne correspond pas à un type d'archive supporté" #, fuzzy #~ msgid "Display a virtual directory for the selected row" #~ msgstr "Ajoute un fils à la ligne sélectionnée" #~ msgid "File operation in progress, %s added to queue" #~ msgstr "Opération sur fichier en cours, ajout de %s à la file d'attente" #~ msgid "Cannot proceed - stat %s failed" #~ msgstr "Impossible de continuer - la commande stat %s a échoué" #~ msgid "Calculate the 'apparent' disk usage of selected item(s)" #~ msgstr "" #~ "Calcule l'espace disque \"apparent\" occupé par les éléments sélectionnés" emelfm2-0.4.1/po/ru.po0000600000175000017500000055627611014260437013436 0ustar cairocairo# Russian translation for emelFM2. # Copyright (C) 2006-2008 Alexander Orlov # This file is distributed under the same license as the emelFM2 package. # # Alexander Orlov , 2006-2008. # msgid "" msgstr "" "Project-Id-Version: emelfm2-0.4.1\n" "Report-Msgid-Bugs-To: alxorlov@pochta.ru\n" "POT-Creation-Date: 2006-11-01 06:09+0100\n" "PO-Revision-Date: 2008-05-15 00:23+0300\n" "Last-Translator: Alexander Orlov \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" #: e2_about_dialog.c:107 e2_action.c:851 e2_alias.c:323 e2_command.c:2781 msgid "help" msgstr "help" #: e2_about_dialog.c:118 e2_action.c:826 msgid "about" msgstr "about" #: e2_about_dialog.c:126 #, c-format msgid "" "An \"orthodox\" file manager for GTK+2\n" "\n" "Copyright © %s\n" "\n" "This program is licensed under the terms of the General Public License and " "comes with ABSOLUTELY NO WARRANTY\n" "\n" "This binary was compiled on %s\n" "using %s and GTK+%d.%d.%d" msgstr "" "\"Ортодоксальный\" файловый менеджер для GTK+2\n" "\n" "Copyright © %s\n" "\n" "Эта программа распространяется на условиях Универсальной Общественной " "Лицензии, БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ\n" "\n" "Эта программа скомпилирована %s\n" "с помощью %s и GTK+%d.%d.%d" #: e2_about_dialog.c:142 #, c-format msgid "" "The file\n" "%s\n" "gives details." msgstr "" "Детали смотрите в файле\n" "%s" #: e2_about_dialog.c:145 msgid "contributors" msgstr "участники" #: e2_about_dialog.c:150 msgid "" "This program is based on emelFM, developed by Michael Clark.\n" "\n" "Contributions have been made by many friends." msgstr "" "Эта программа основана на emelFM, который разработал Michael Clark.\n" "\n" "Множество друзей участвовали в разработке." #: e2_about_dialog.c:160 msgid "usage" msgstr "использование" #: e2_about_dialog.c:169 e2_option.c:1073 e2_output.c:980 msgid "commands" msgstr "команды" #: e2_about_dialog.c:179 e2_filetype_dialog.c:1193 msgid "Read the file" msgstr "Просмотреть файл" #: e2_about_dialog.c:179 e2_context_menu.c:554 e2_filetype_dialog.c:1192 #: e2p_view.c:83 msgid "_View" msgstr "_Просмотр" #: e2_action.c:798 msgid "bookmark" msgstr "bookmark" #: e2_action.c:799 e2_command.c:1858 msgid "command" msgstr "command" #: e2_action.c:800 msgid "configure" msgstr "configure" #: e2_action.c:801 e2_option__default.c:160 e2_option__default.c:329 #: e2_toolbar.c:2794 msgid "dialog" msgstr "dialog" #: e2_action.c:802 msgid "dirline" msgstr "dirline" #: e2_action.c:803 e2p_du.c:219 msgid "file" msgstr "file" #: e2_action.c:804 e2_task.c:3419 msgid "find" msgstr "find" #: e2_action.c:805 msgid "list" msgstr "list" #: e2_action.c:806 msgid "option" msgstr "option" #: e2_action.c:807 e2_option.c:1094 msgid "output" msgstr "output" #: e2_action.c:808 msgid "pane_active" msgstr "pane_active" #: e2_action.c:809 msgid "pane1" msgstr "pane1" #: e2_action.c:810 msgid "pane2" msgstr "pane2" #: e2_action.c:811 e2_option.c:1099 msgid "panes" msgstr "panes" #: e2_action.c:812 e2_config_dialog.c:935 msgid "plugin" msgstr "plugin" #: e2_action.c:813 msgid "toggle" msgstr "toggle" #: e2_action.c:815 msgid "separator" msgstr "separator" #: e2_action.c:816 msgid "" msgstr "<особая команда>" #: e2_action.c:817 msgid "" msgstr "<разделитель>" #: e2_action.c:818 msgid "" msgstr "<подменю>" #: e2_action.c:821 msgid "" msgstr "<действия>" #: e2_action.c:822 msgid "" msgstr "<закладки>" #: e2_action.c:823 msgid "" msgstr "<фильтры>" #: e2_action.c:824 msgid "" msgstr "<строка>" #: e2_action.c:825 msgid "" msgstr "<меню>" #: e2_action.c:827 msgid "add" msgstr "add" #: e2_action.c:828 msgid "adjust_ratio" msgstr "adjust_ratio" #: e2_action.c:829 e2_bookmark.c:302 e2_option_tree.c:667 msgid "children" msgstr "children" #: e2_action.c:830 e2_alias.c:321 msgid "clear" msgstr "clear" #: e2_action.c:831 msgid "clear_history" msgstr "clear_history" #: e2_action.c:832 msgid "complete" msgstr "complete" #: e2_action.c:833 msgid "application" msgstr "application" #: e2_action.c:834 e2_task.c:1426 msgid "copy" msgstr "copy" #: e2_action.c:835 msgid "copy_as" msgstr "copy_as" #: e2_action.c:836 msgid "copy_merge" msgstr "copy_merge" #: e2_action.c:837 msgid "copy_with_time" msgstr "copy_with_time" #: e2_action.c:838 msgid "default" msgstr "default" #: e2_action.c:839 msgid "delete" msgstr "delete" #: e2_action.c:840 msgid "edit" msgstr "edit" #: e2_action.c:841 msgid "edit_again" msgstr "edit_again" #: e2_action.c:842 msgid "filetype" msgstr "filetype" #: e2_action.c:843 msgid "focus" msgstr "focus" #: e2_action.c:844 msgid "focus_toggle" msgstr "focus_toggle" #: e2_action.c:845 msgid "fullscreen" msgstr "fullscreen" #: e2_action.c:846 msgid "go_back" msgstr "go_back" #: e2_action.c:847 msgid "go_forward" msgstr "go_forward" #: e2_action.c:848 msgid "go_up" msgstr "go_up" #: e2_action.c:849 msgid "goto_bottom" msgstr "goto_bottom" #: e2_action.c:850 msgid "goto_top" msgstr "goto_top" #: e2_action.c:852 e2_option.c:1085 msgid "history" msgstr "history" #: e2_action.c:853 e2_mkdir_dialog.c:918 msgid "info" msgstr "info" #: e2_action.c:854 msgid "insert_selection" msgstr "insert_selection" #: e2_action.c:855 msgid "invert_selection" msgstr "invert_selection" #: e2_action.c:856 msgid "mirror" msgstr "mirror" #: e2_action.c:857 msgid "mkdir" msgstr "mkdir" #: e2_action.c:858 msgid "mountpoints" msgstr "mountpoints" #: e2_action.c:859 e2_task.c:1748 msgid "move" msgstr "move" #: e2_action.c:860 msgid "move_as" msgstr "move_as" #: e2_action.c:861 e2_select_dir_dialog.c:45 msgid "open" msgstr "open" #: e2_action.c:862 msgid "open_in_other" msgstr "open_in_other" #: e2_action.c:863 msgid "open_with" msgstr "open_with" #: e2_action.c:864 e2p_find.c:3069 msgid "owners" msgstr "owners" #: e2_action.c:865 msgid "page_down" msgstr "page_down" #: e2_action.c:866 msgid "page_up" msgstr "page_up" #: e2_action.c:867 msgid "pending" msgstr "pending" #: e2_action.c:868 e2_permissions_dialog.c:318 msgid "permissions" msgstr "permissions" #: e2_action.c:869 msgid "print" msgstr "print" #: e2_action.c:870 e2_alias.c:322 msgid "quit" msgstr "quit" #: e2_action.c:871 msgid "refresh" msgstr "refresh" #: e2_action.c:872 msgid "refreshresume" msgstr "refreshresume" #: e2_action.c:873 msgid "refreshsuspend" msgstr "refreshsuspend" #: e2_action.c:874 e2_task.c:2358 msgid "rename" msgstr "rename" #: e2_action.c:875 msgid "scroll_down" msgstr "scroll_down" #: e2_action.c:876 msgid "scroll_up" msgstr "scroll_up" #: e2_action.c:877 e2_option.c:1102 msgid "search" msgstr "search" #: e2_action.c:878 msgid "send" msgstr "send" #: e2_action.c:879 msgid "set" msgstr "set" #: e2_action.c:880 msgid "show" msgstr "show" #: e2_action.c:881 msgid "show_hidden" msgstr "show_hidden" #: e2_action.c:882 msgid "show_menu" msgstr "show_menu" #: e2_action.c:883 msgid "sortaccesssed" msgstr "sortaccesssed" #: e2_action.c:884 msgid "sortchanged" msgstr "sortchanged" #: e2_action.c:885 msgid "sortgroup" msgstr "sortgroup" #: e2_action.c:886 msgid "sortmodified" msgstr "sortmodified" #: e2_action.c:887 msgid "sortname" msgstr "sortname" #: e2_action.c:888 msgid "sortpermission" msgstr "sortpermission" #: e2_action.c:889 msgid "sortsize" msgstr "sortsize" #: e2_action.c:890 msgid "sortuser" msgstr "sortuser" #: e2_action.c:891 msgid "switch" msgstr "switch" #: e2_action.c:892 e2_option__default.c:274 e2p_find.c:3020 msgid "symlink" msgstr "symlink" #: e2_action.c:893 msgid "symlink_as" msgstr "symlink_as" #: e2_action.c:894 msgid "sync" msgstr "sync" #: e2_action.c:895 msgid "toggle_direction" msgstr "toggle_direction" #: e2_action.c:897 msgid "toggle_select_all" msgstr "toggle_select_all" #: e2_action.c:898 msgid "toggle_selected" msgstr "toggle_selected" #: e2_action.c:899 e2_bookmark.c:564 msgid "trash" msgstr "trash" #: e2_action.c:900 msgid "trashempty" msgstr "trashempty" #: e2_action.c:902 msgid "tree" msgstr "tree" #: e2_action.c:904 msgid "unpack" msgstr "unpack" #: e2_action.c:905 e2_option.c:1107 msgid "view" msgstr "view" #: e2_action.c:906 msgid "view_again" msgstr "view_again" #: e2_action.c:907 msgid "view_at" msgstr "view_at" #: e2_action.c:909 e2_bookmark.c:302 e2_option_tree.c:667 msgid "child" msgstr "child" #: e2_action.c:910 msgid "ctrl" msgstr "ctrl" #: e2_action.c:911 msgid "dirs" msgstr "dirs" #: e2_action.c:912 msgid "escape" msgstr "escape" #: e2_action.c:913 msgid "expand" msgstr "expand" #: e2_action.c:914 e2p_du.c:219 msgid "files" msgstr "files" #: e2_action.c:915 msgid "off" msgstr "off" #: e2_action.c:916 msgid "on" msgstr "on" #: e2_action.c:917 msgid "quote" msgstr "quote" #: e2_action.c:918 msgid "shift" msgstr "shift" #: e2_action.c:919 msgid "top" msgstr "top" #: e2_action.c:923 msgid "" msgstr "<меню_пробел>" #: e2_action.c:924 msgid "dummy" msgstr "dummy" #: e2_action.c:925 msgid "namespace" msgstr "namespace" #: e2_action.c:926 msgid "unpack_in_other" msgstr "unpack_in_other" #: e2_action.c:929 msgid "key" msgstr "key" #: e2_action.c:930 e2_vfs_dialog.c:699 msgid "alias" msgstr "alias" #: e2_alias.c:316 msgid "x" msgstr "x" #: e2_alias.c:317 e2_alias.c:318 e2_toolbar.c:2893 msgid "Done. Press enter " msgstr "Готово. Нажмите ввод " #: e2_alias.c:317 msgid "xx" msgstr "xx" #: e2_alias.c:324 e2_output.c:985 msgid "keys" msgstr "клавиши" #: e2_alias.c:325 msgid "e2ps" msgstr "e2ps" #: e2_alias.c:327 msgid "cns" msgstr "cns" #: e2_alias.c:350 msgid "Match" msgstr "Совпадение" #: e2_alias.c:353 msgid "Stop" msgstr "Стоп" #: e2_alias.c:355 msgid "Replace" msgstr "Замена" #: e2_bookmark.c:298 msgid "Are you sure that you want to delete the bookmark" msgstr "Вы уверены, что хотите удалить закладку" #: e2_bookmark.c:302 e2p_du.c:222 msgid "and" msgstr "и" #: e2_bookmark.c:307 msgid "confirm bookmark delete" msgstr "подтверждение удаления закладки" #: e2_bookmark.c:375 msgid "_Add after" msgstr "_Добавить" #: e2_bookmark.c:376 msgid "Bookmark the current directory after the selected bookmark" msgstr "Добавить текущий каталог в закладки, сразу после выбранной закладки" #: e2_bookmark.c:382 msgid "Add as _child" msgstr "Добавить _вложенный" #: e2_bookmark.c:383 msgid "Bookmark the current directory a a child of the selected bookmark" msgstr "Добавить текущий каталог в закладки, вложенным в выбранную закладку" #: e2_bookmark.c:389 e2_context_menu.c:563 e2p_unpack.c:335 msgid "_Delete" msgstr "_Удалить" #: e2_bookmark.c:390 msgid "Delete the selected bookmark, and its children if any" msgstr "Удалить выбранную закладку, и все вложенные закладки" #: e2_bookmark.c:552 msgid "_home" msgstr "_домой" #: e2_bookmark.c:555 msgid "cdrom" msgstr "cdrom" #: e2_bookmark.c:557 msgid "root" msgstr "корень" #: e2_bookmark.c:558 msgid "Your home directory" msgstr "Ваш домашний каталог" #: e2_bookmark.c:558 msgid "home" msgstr "домой" #: e2_bookmark.c:559 msgid "media" msgstr "media" #: e2_bookmark.c:560 msgid "mnt" msgstr "mnt" #: e2_bookmark.c:561 msgid "usr" msgstr "usr" #: e2_bookmark.c:562 msgid "usr/local" msgstr "usr/local" #: e2_bookmark.c:564 msgid "default trash directory" msgstr "каталог корзины по-умолчанию" #: e2_bookmark.c:582 e2_context_menu.c:613 e2_menu.c:995 e2_plugins.c:1278 #: e2_toolbar.c:2851 e2_toolbar.c:2918 e2_toolbar.c:3085 msgid "Label" msgstr "Название" #: e2_bookmark.c:584 e2_context_menu.c:615 e2_menu.c:993 e2_plugins.c:1280 #: e2_toolbar.c:2853 e2_toolbar.c:2920 e2_toolbar.c:3087 msgid "Icon" msgstr "Значёк" #: e2_bookmark.c:586 e2_menu.c:997 e2_plugins.c:1282 e2_toolbar.c:2855 #: e2_toolbar.c:2922 e2_toolbar.c:3089 msgid "Tooltip" msgstr "Подсказка" #: e2_bookmark.c:588 e2_plugins.c:1286 e2_vfs_dialog.c:1429 msgid "Path" msgstr "Путь" #: e2_bookmark.c:596 msgid "open bookmark in other pane on middle-button click" msgstr "при нажатии средней кнопки, открывать закладку в другой панели" #: e2_bookmark.c:597 msgid "" "Clicking the middle mouse button on a bookmark will open it in the other " "file pane" msgstr "Нажатие средней кнопки мыши на закладке откроет ее в другой панели" #: e2_bookmark.c:601 msgid "focus file pane after opening a bookmark in it" msgstr "активировать файловую панель при открытии закладки" #: e2_bookmark.c:602 msgid "" "After opening a bookmark in the inactive file pane, that pane will become " "the active one" msgstr "" "После открытия закладки в неактивной файловой панели, эта панель станет " "активной" #: e2_bookmark.c:608 msgid "confirm any delete of a selected bookmark" msgstr "подтверждать удаление выбранной закладки" #: e2_bookmark.c:609 msgid "You will be asked to confirm, before deleting any bookmark" msgstr "У вас будет запрашиваться подтверждение, перед удалением закладки" #: e2_bookmark.c:613 msgid "confirm any delete of multiple bookmarks" msgstr "подтверждать удаление нескольких закладок" #: e2_bookmark.c:614 msgid "" "You will be asked to confirm, before deleting any bookmark that has " "'children'" msgstr "" "У вас будет запрашиваться подтверждение, перед удалением закладки, " "содержашей 'вложения'" #: e2_button.c:25 e2p_upgrade.c:107 msgid "_OK" msgstr "_ОК" #: e2_button.c:28 e2p_upgrade.c:116 msgid "_Cancel" msgstr "_Отмена" #: e2_button.c:30 msgid "_Yes" msgstr "_Да" #: e2_button.c:33 msgid "_No" msgstr "_Нет" #: e2_button.c:36 msgid "Yes to _all" msgstr "Да для _всех" #: e2_button.c:38 e2p_cpbar.c:731 e2p_cpbar.c:735 e2p_mvbar.c:805 #: e2p_mvbar.c:809 msgid "_Stop" msgstr "_Остановить" #: e2_button.c:40 e2p_config.c:1161 e2p_config.c:1288 msgid "_Apply" msgstr "_Применить" #: e2_button.c:43 msgid "_Apply to all" msgstr "_Применить для всех" #: e2_button.c:45 e2_tree_dialog.c:1115 e2p_thumbs.c:1117 msgid "_Refresh" msgstr "_Обновить" #: e2_button.c:47 msgid "_Close" msgstr "_Закрыть" #: e2_button.c:49 msgid "C_reate" msgstr "_Создать" #: e2_button.c:51 e2_option_tree.c:978 e2_permissions_dialog.c:458 #: e2p_acl.c:3728 msgid "_Remove" msgstr "_Удалить" #: e2_cache.c:726 #, c-format msgid "" "%sThis file stores runtime configuration data for %s.\n" "%sThe file will be overwritten each time %s is shut down.\n" "\n" msgstr "" "%sВ этом файле хранятся рабочие данные для %s.\n" "%sЭтот файл перезаписывается каждый раз, при закрытии %s.\n" "\n" #: e2_cache.c:955 #, c-format msgid "Cannot write cache file %s - %s" msgstr "Не могу записать кэш-файл %s - %s" #: e2_cl_option.c:43 #, c-format msgid "usage: %s [option]\n" msgstr "использование: %s [параметры]\n" #: e2_cl_option.c:56 msgid "" "Program options:\n" "-1,--one=DIR set 1st pane's start directory to DIR\n" "-2,--two=DIR set 2nd pane's start directory to DIR\n" "-c,--config=DIR set config directory to DIR (default: ~/.config/" "emelfm2)\n" "-e,--encoding=TYPE set filesystem character encoding to TYPE\n" "-f,--fallback-encoding set fallback encoding (default: ISO-8859-1)\n" "-i,--ignore-problems ignore encoding/locale problems (at your own risk!)\n" "-l,--log-all maximise scope of error logging\n" "-m,--daemon run program as daemon\n" "-r,--run-at-start=CMD run command CMD at session start\n" "-s,--set-option=OPT set one-line gui option using config-file formatted " "OPT\n" "-t,--trash=DIR set trash directory to DIR (default: ~/.local/share/" "Trash/files)\n" "\n" "Help options:\n" "-h,--help show this help message\n" "-u,--usage display brief usage messsage\n" "-v,--version display version and build info\n" msgstr "" "Параметры программы:\n" "-1,--one=DIR установить начальный каталог 1ой панели в DIR\n" "-2,--two=DIR установить начальный каталог 2ой панели в DIR\n" "-c,--config=DIR установить каталог настроек в DIR (по-умолчанию: ~/." "config/emelfm2)\n" "-e,--encoding=TYPE установить кодировку файловой системы в TYPE\n" "-f,--fallback-encoding установить кодировку (по-умолчанию: ISO-8859-1)\n" "-i,--ignore-problems игнорировать проблемы с кодировками/локалями (на ваш " "страх и риск!)\n" "-l,--log-all выводить все сообщения об ошибках\n" "-m,--daemon запустить программу в режиме демона\n" "-r,--run-at-start=CMD запустить команду CMD после загрузки\n" "-s,--set-option=OPT установить параметр оболочки, ипользуя строку OPT\n" "-t,--trash=DIR установить каталог корзины в DIR (по-умолчанию: ~/." "local/share/Trash/files)\n" "\n" "Параметры справки:\n" "-h,--help показать это сообщение\n" "-u,--usage показать короткую справку об использовании\n" "-v,--version показать версию и параметры сборки\n" #: e2_cl_option.c:78 msgid "" "-d,--debug=[1-5] set debug level from 1 (low) to 5 (high)\n" "-x,--verbose display time/location info on debug messages\n" msgstr "" "-d,--debug=[1-5] установить уровень отладки от 1 (низкий) до 5 " "(высокий)\n" "-x,--verbose показывать время/позицию в отладочных сообщениях\n" #: e2_cl_option.c:91 #, c-format msgid "" "%s v. %s\n" "Licensed under the GPL\n" "Copyright (C) %s\n" "Build date: %s\n" "Build platform: GTK+ %d.%d.%d %s\n" msgstr "" "%s v. %s\n" "Распространяется по лицензии GPL\n" "Copyright (C) %s\n" "Дата сборки: %s\n" "Платформа сборки: GTK+ %d.%d.%d %s\n" #: e2_cl_option.c:299 msgid "Startup options must begin with \"-\" or \"--\"\n" msgstr "Параметры программы должны начинаться с \"-\" или \"--\"\n" #: e2_command.c:643 e2_command.c:1301 e2_command.c:1587 msgid "returned" msgstr "вернул" #: e2_command.c:789 #, c-format msgid "Command '%s' - %s" msgstr "Команда '%s' - %s" #: e2_command.c:1066 #, c-format msgid "Error while launching '%s'" msgstr "Ошибка при запуске '%s'" #: e2_command.c:1092 msgid "Cannot find last child process" msgstr "Не могу найти последний дочерний процесс" #: e2_command.c:1100 #, c-format msgid "Cannot find child process with pid %ld" msgstr "Не могу найти дочерний процесс с pid %ld" #: e2_command.c:1103 #, c-format msgid "Cannot communicate to process %ld" msgstr "Невозможно общение с процессом %ld" #: e2_command.c:1118 msgid "Failed writing to child" msgstr "Ошибка при записи в дочерний процесс" #: e2_command.c:1594 #, c-format msgid "Strange error: could not run '%s'" msgstr "Странная ошибка: не могу запустить '%s'" #: e2_command.c:1617 #, c-format msgid "The process with pid %ld is not our child" msgstr "Процесс с pid %ld не является нашим потомком" #: e2_command.c:1629 e2_command.c:1640 #, c-format msgid "Failed writing to child: %s" msgstr "Ошибка при общении с потомком: %s" #: e2_command.c:1854 msgid "nothing is waiting" msgstr "ничто не ожидает" #: e2_command.c:1878 e2_command.c:1982 e2_command.c:2045 msgid "" msgstr "<выделенные элементы>" #: e2_command.c:1953 msgid "nothing is running" msgstr "ничто не запущено" #: e2_command.c:1957 msgid " pid || directory || command" msgstr " pid || каталог || команда" #: e2_command.c:2019 msgid "command || directory || result" msgstr "команда || каталог || результат" #: e2_command.c:2051 e2_command.c:2063 msgid "OK" msgstr "ОК" #: e2_command.c:2051 e2_command.c:2066 msgid "error" msgstr "ошибка" #: e2_command.c:2321 #, c-format msgid "Cannot run '%s'" msgstr "Не могу запустить '%s'" #: e2_command.c:2439 msgid "Failed to expand macros" msgstr "Ошибка при обработке макроса" #: e2_command.c:2587 #, c-format msgid "Failed parsing command '%s' - %s" msgstr "Ошибка при разборе команды '%s' - %s" #: e2_command.c:2675 #, c-format msgid "Cannot send \"%s\" to a child process" msgstr "Не могу послать \"%s\" дочернему процессу" #: e2_command.c:2870 msgid "x terminal emulator:" msgstr "эмулятор X-терминала:" #: e2_command.c:2871 msgid "" "This is the external command/application that will be be run when emelFM2 is " "asked to open a terminal" msgstr "" "Это внешняя программа которая будет запускаться, когда emelFM2 потребуется " "терминал" #: e2_command.c:2874 msgid "use external file-viewer" msgstr "использовать внешний просмотрщик файлов" #: e2_command.c:2875 msgid "" "If activated, the command entered below will be run to view file content, " "instead of launching the internal viewer" msgstr "" "Если включено, то для просмотра файлов будет использоваться команда, " "введенная ниже, вместо встроенного просмотрщика" #: e2_command.c:2878 msgid "viewer command:" msgstr "внешний просмотрщик:" #: e2_command.c:2879 msgid "" "This is a command to run an external application for viewing file content.\n" "The first selected item will be supplied as the first argument" msgstr "" "Это внешняя программа для просмотра файлов.\n" "Первый выбранный элемент будет использован в качестве первого аргумента " "просмотрщика" #: e2_command.c:2883 msgid "use external file-editor" msgstr "использовать внешний редактор файлов" #: e2_command.c:2884 msgid "" "If activated, the command entered below will be run to edit file content, " "instead of launching the internal editor" msgstr "" "Если включено, то для редактирования файлов будет использоваться команда, " "введенная ниже, вместо встроенного редактора" #: e2_command.c:2887 msgid "editor command:" msgstr "внешний редактор:" #: e2_command.c:2888 msgid "" "This is a command to run an external application for editing file content.\n" "The first selected item will be supplied as the first argument" msgstr "" "Это внешняя программа для редактирования файлов.\n" "Первый выбранный элемент будет использован в качестве первого аргумента " "редактора" #: e2_command.c:2892 msgid "use external encoding converter" msgstr "использовать внешний преобразователь кодировок" #: e2_command.c:2893 msgid "" "If activated, the command entered below will be run, instead of using the " "internal conversion functions, to convert file character encoding when needed" msgstr "" "Если включено, то для преобразования кодировок будет использована команда, " "введенная ниже, вместо внутренних функций" #: e2_command.c:2896 e2p_upgrade.c:744 e2p_upgrade.c:745 msgid "File encoding:" msgstr "Кодировка файла:" #: e2_command.c:2897 msgid "converter command:" msgstr "внешний конвертер:" #: e2_command.c:2898 msgid "" "A command which runs an external application to convert text encoding to UTF-" "8" msgstr "" "Команда, запускающая программу для преобразования кодировки текстов в UTF-8" #: e2_command.c:2902 msgid "use aliases" msgstr "использовать псевдонимы" #: e2_command.c:2903 msgid "This is a general switch to turn on/off alias handling for commands" msgstr "" "Это переключатель для включения/выключения обработки псевдонимов команд" #: e2_command.c:2906 msgid "interpret 'relative' paths" msgstr "обрабатывать 'относительные' пути" #: e2_command.c:2907 msgid "" "This enables correct interpretation of paths containing '..' etc. These " "might be typed in, or attached to a button, say" msgstr "Это включает правильную интерпретацию путей, содержащих '..' и т.п." #: e2_command.c:2910 msgid "running commands survive shutdown" msgstr "не закрывать работающие команды при выходе" #: e2_command.c:2911 msgid "" "If activated, commands that are still running will not be terminated at end " "of emelFM2 session" msgstr "" "Если включено, то все ещё работающие команды не будут закрываться при " "завершении сессии emelFM2" #: e2_command.c:2915 msgid "watch priority" msgstr "приоритет наблюдения" #: e2_command.c:2916 msgid "" "The watch priority of commands influences how fast program\n" "output is read from i/o channels. A too-high priority might\n" "decrease gui responsiveness and break automatic scrolling.\n" "(note: negative values mean high priority, positive values mean low priority)" msgstr "" "Приоритет наблюдения за командами влияет на то, как часто происходит чтение " "вывода программ из каналов ввода/вывода.\n" "Слишком большой приоритет может увеличить время реакции и затруднить " "автоматическую прокрутку.\n" "(замечание: отрицательные значения означают более высокий приоритет, а " "положительные -- более низкий)" #: e2_command.c:2925 msgid "stop after timeout" msgstr "остановка операций по времени" #: e2_command.c:2926 msgid "" "If activated, each file operation will be terminated if not finished within " "the time-interval set below" msgstr "" "Если включено, то любая файловая операция будет останавливаться, если не " "успеет завершиться в интервал времени, введенный ниже" #: e2_command.c:2929 msgid "timeout interval" msgstr "временной интервал" #: e2_command.c:2930 msgid "The interval (seconds) allowed to complete any file operation" msgstr "" "Временной интервал (в секундах), отведенный на завершение любой файловой " "операции" #: e2_command.c:2933 msgid "confirm any delete" msgstr "подтверждать удаление" #: e2_command.c:2934 msgid "" "If activated, you will be asked for confirmation before actually deleting " "anything" msgstr "" "Если включено, то у вас будет запрашиваться подтверждение прежде чем удалить " "что-либо" #: e2_command.c:2936 msgid "confirm any overwrite" msgstr "подтверждать перезапись" #: e2_command.c:2937 msgid "" "If activated, you will be asked for confirmation before actually overwriting " "anything" msgstr "" "Если включено, то у вас будет запрашиваться подтверждение прежде чем " "перезаписать что-либо" #: e2_command.c:2939 msgid "relative symlinks" msgstr "относительные символические ссылки" #: e2_command.c:2940 msgid "" "This gives each created symlink a relative path to its source, like '../../" "/', instead of a full path referenced to /" msgstr "" "Это позволяет создавать символические ссылки с относительными путями, как " "'../../<каталог>/<файл>', вместо указания полного пути с /" #: e2_command_line.c:577 msgid "mounts" msgstr "монтирование" #: e2_command_line.c:579 msgid "all" msgstr "все" #: e2_command_line.c:672 #, c-format msgid "Warning - process %s is not active" msgstr "Предупреждение - процесс %s не активный" #: e2_command_line.c:988 msgid "show last" msgstr "показывать последнюю команду" #: e2_command_line.c:989 msgid "" "If activated, the last-entered command will be displayed, instead of an " "empty line" msgstr "" "Если включено, то будет показываться последняя введенная команда, вместо " "пустой строки" #: e2_command_line.c:994 e2_command_line.c:1041 msgid "maximum number of history entries" msgstr "максимальное количество элементов в истории" #: e2_command_line.c:995 msgid "" "This is the largest number of command-line history entries that will be " "recorded" msgstr "" "Это максимальное количество элементов в истории введенных команд, которое " "будет сохранено" #: e2_command_line.c:998 e2_command_line.c:1046 msgid "double entries" msgstr "повторения элементов" #: e2_command_line.c:999 e2_command_line.c:1047 msgid "" "This allows entries to be recorded more than once in the history list, so " "the last entry is always close to hand" msgstr "" "Это позволяет элементам сохранятся в истории более одного раза, так что " "последний элемент всегда будет под рукой" #: e2_command_line.c:1002 e2_command_line.c:1051 msgid "cyclic list" msgstr "циклический список" #: e2_command_line.c:1003 e2_command_line.c:1052 msgid "" "When scanning the history list, cycle from either end around to the other " "end, instead of stopping" msgstr "" "При просмотре списка истории, циклически перемещаться из одного конца списка " "в другой конец, вместо остановки" #: e2_command_line.c:1007 e2_command_line.c:1056 msgid "show as a menu" msgstr "показывать как меню" #: e2_command_line.c:1008 msgid "" "If activated, the history entries will be presented as a menu. For most Gtk" "+2 themes, this will not be as attractive as the list view" msgstr "" "Если включено, то элементы истории будут отображаться в виде меню. Для " "большинства Gtk+2 тем, это будет не столь привлекательно как обычный список" #: e2_command_line.c:1015 msgid "append space after unique items" msgstr "добавлять пробел после совпавших элементов" #: e2_command_line.c:1016 msgid "" "This appends a 'space' character to the end of a unique successful match of " "a file" msgstr "Это добавляет символ 'пробел' в конец уникальных совпавших элементов" #: e2_command_line.c:1023 msgid "show last entry" msgstr "показывать последний элемент" #: e2_command_line.c:1024 msgid "" "If activated, the last-entered directory will be displayed, instead of an " "empty line" msgstr "" "Если включено, то будет отображаться последний введенный каталог, вместо " "пустой строки" #: e2_command_line.c:1028 msgid "show pathname as a tooltip" msgstr "показывать путь в подсказке" #: e2_command_line.c:1029 msgid "" "If activated, the full directory pathname will display as a tooltip. This is " "useful when the path is too long for the normal display" msgstr "" "Если включено, то в подсказке будет отображаться полный путь к каталогу. Это " "полезно, когда путь слишком длинный для нормального отображения" #: e2_command_line.c:1034 msgid " only" msgstr "только " #: e2_command_line.c:1034 msgid "inserted" msgstr "вставлено" #: e2_command_line.c:1034 msgid "selected" msgstr "выделено" #: e2_command_line.c:1035 msgid "directory path completion" msgstr "автодополнение текущего пути" #: e2_command_line.c:1036 msgid "This determines the mode of completion when keying a directory-path" msgstr "" "Это определяет режим автодополнения при наборе пути к текущему каталогу" #: e2_command_line.c:1042 msgid "" "This is the largest number of directory-line history entries that will be " "retained" msgstr "" "Это максимальное количество элементов в истории текущего каталога, которое " "будет запоминаться" #: e2_command_line.c:1057 msgid "" "If activated, the directory line history will be presented as a menu. For " "most for most Gtk+2 themes, this will not be as attractive as the list view" msgstr "" "Если включено, то история текущего каталога будет показываться в виде меню. " "Для большинства Gtk+2 тем это будет не столь привлекательно, как список" #: e2_config_dialog.c:175 e2_config_dialog.c:1484 msgid "configuration" msgstr "настройка" #: e2_config_dialog.c:658 msgid "Reverting to default configuration cannot be undone" msgstr "Возврат к настройкам по-умолчанию невозможно отменить" #: e2_config_dialog.c:781 e2_option_tree_context_menu.c:243 msgid "_Expand" msgstr "_Развернуть" #: e2_config_dialog.c:782 msgid "Expand all rows" msgstr "Развернуть все строки" #: e2_config_dialog.c:783 e2_option_tree_context_menu.c:247 #: e2_tree_dialog.c:1113 msgid "C_ollapse" msgstr "_Свернуть" #: e2_config_dialog.c:784 msgid "Collapse all rows" msgstr "Свернуть все строки" #: e2_config_dialog.c:922 msgid "choose plugin" msgstr "выбор модуля" #: e2_config_dialog.c:1001 #, c-format msgid "Choose font: %s" msgstr "Выбор шрифта: %s" #: e2_config_dialog.c:1041 msgid "abcd efgh ABCD EFGH" msgstr "абвг АБВГ abcd ABCD" #: e2_config_dialog.c:1041 msgid "example:" msgstr "пример:" #: e2_config_dialog.c:1136 msgid "Color data are not stored there" msgstr "Цветовые параметры здесь не хранятся" #: e2_config_dialog.c:1154 msgid "The current color descriptor is not valid" msgstr "Ошибка в текущем дескрипторе цвета" #: e2_config_dialog.c:1161 msgid "Set filetype color" msgstr "Выбрать цвет типа файла" #: e2_config_dialog.c:1219 #, c-format msgid "Choose color: %s" msgstr "Выбор цвета: %s" #: e2_config_dialog.c:1264 msgid "abCD" msgstr "abCD" #: e2_config_dialog.c:1264 msgid "currently:" msgstr "текущий:" #: e2_config_dialog.c:1517 e2_filetype_dialog.c:872 msgid "Categories" msgstr "Категории" #: e2_config_dialog.c:1631 e2_config_dialog.c:1658 e2p_find.c:2903 msgid "change" msgstr "изменить" #: e2_config_dialog.c:1632 msgid "Click to open a font select dialog" msgstr "Нажмите для выбора шрифта" #: e2_config_dialog.c:1659 msgid "Click to open a color selection dialog" msgstr "Нажмите для выбора цвета" #: e2_config_dialog.c:1771 msgid "_Default" msgstr "_Сбросить" #: e2_config_dialog.c:1772 msgid "Revert all options to their default settings" msgstr "Вернуть все настройки к их первоначальным значениям" #: e2_config_dialog.c:1776 msgid "_Basic" msgstr "П_ростой" #: e2_config_dialog.c:1777 msgid "Display only the basic configuration options" msgstr "Показывать только основные параметры конфигурации" #: e2_config_dialog.c:1781 msgid "Ad_vanced" msgstr "_Расширенный" #: e2_config_dialog.c:1782 msgid "Display all configuration options" msgstr "Показывать все параметры конфигурации" #: e2_context_menu.c:209 e2_filetype.c:119 e2_filetype.c:299 e2_filetype.c:493 #: e2_output.c:718 e2_task.c:3192 e2_task_backend.c:1929 msgid "" msgstr "<каталог>" #: e2_context_menu.c:273 e2_filetype.c:120 e2_filetype.c:301 e2_filetype.c:501 #: e2_output.c:757 e2_task.c:3198 msgid "" msgstr "<исполняемый>" #: e2_context_menu.c:552 e2p_thumbs.c:1102 msgid "Open _with.." msgstr "Открыть _с помощью.." #: e2_context_menu.c:556 msgid "_Info" msgstr "_Информация" #: e2_context_menu.c:558 msgid "_Actions" msgstr "_Действия" #: e2_context_menu.c:559 e2_dnd.c:726 e2_file_info_dialog.c:246 #: e2_filetype_dialog.c:691 e2_option_tree_context_menu.c:231 #: e2_toolbar.c:2816 e2_tree_dialog.c:1109 e2_view_dialog.c:946 #: e2p_cpbar.c:875 msgid "_Copy" msgstr "_Копировать" #: e2_context_menu.c:560 e2_dnd.c:732 e2_toolbar.c:2818 e2p_mvbar.c:951 msgid "_Move" msgstr "_Переместить" #: e2_context_menu.c:561 e2_dnd.c:738 e2_toolbar.c:2820 msgid "_Link" msgstr "_Ссылка" #: e2_context_menu.c:562 e2_toolbar.c:2824 msgid "_Trash" msgstr "_Корзина" #: e2_context_menu.c:565 e2p_rename.c:1580 msgid "_Rename.." msgstr "_Переименовать.." #: e2_context_menu.c:566 msgid "Change _owners.." msgstr "Изменить _владельца.." #: e2_context_menu.c:568 msgid "Change _permissions.." msgstr "Изменить _права.." #: e2_context_menu.c:570 msgid "Copy as.." msgstr "Копировать как.." #: e2_context_menu.c:571 msgid "Move as.." msgstr "Переместить как.." #: e2_context_menu.c:572 msgid "Link as.." msgstr "Создать ссылку как.." #: e2_context_menu.c:573 msgid "_Plugins" msgstr "_Модули" #: e2_context_menu.c:576 msgid "_Edit plugins.." msgstr "_Настроить модули.." #: e2_context_menu.c:577 msgid "_User commands" msgstr "Пользовательские _команды" #: e2_context_menu.c:578 msgid "Enter file name:" msgstr "Введите имя файла:" #: e2_context_menu.c:578 msgid "_Make new file.." msgstr "_Создать файл.." #: e2_context_menu.c:579 msgid "The files are different" msgstr "Файлы различны" #: e2_context_menu.c:579 msgid "The files are identical" msgstr "Файлы одинаковы" #: e2_context_menu.c:579 msgid "_Compare files" msgstr "_Сравнить файлы" #: e2_context_menu.c:580 msgid "Compare _directories" msgstr "Сравнить _каталоги" #: e2_context_menu.c:582 msgid "_Remove spaces" msgstr "_Удалить пробелы" #: e2_context_menu.c:583 msgid "Enter the piece-size (in kB):" msgstr "Введите размер фрагментов (в кБ):" #: e2_context_menu.c:583 msgid "_Split file.." msgstr "_Разбить файл.." #: e2_context_menu.c:584 msgid "Co_ncatenate files.." msgstr "_Объединить файлы.." #: e2_context_menu.c:584 msgid "Enter the name of the combined file:" msgstr "Введите имя объединённого файла:" #: e2_context_menu.c:585 msgid "_Free space" msgstr "_Свободное место" #: e2_context_menu.c:585 msgid "percent free" msgstr "процентов свободно" #: e2_context_menu.c:587 msgid "_Edit user commands.." msgstr "_Настроить пользовательские команды.." #: e2_context_menu.c:588 e2_toolbar.c:2826 msgid "Ma_ke dir.." msgstr "Создать _каталог.." #: e2_context_menu.c:590 msgid "_Bookmarks" msgstr "_Закладки" #: e2_context_menu.c:591 e2_toolbar.c:2979 e2_toolbar.c:3044 msgid "Add _top" msgstr "Добавить в _начало" #: e2_context_menu.c:593 e2_toolbar.c:2983 e2_toolbar.c:3048 msgid "Add _bottom" msgstr "Добавить в _конец" #: e2_context_menu.c:595 msgid "_Edit bookmarks.." msgstr "_Настроить закладки.." #: e2_context_menu.c:597 msgid "_Edit filetype.." msgstr "_Настроить типы файлов.." #: e2_context_menu.c:618 msgid "Shift" msgstr "Shift" #: e2_context_menu.c:620 msgid "Ctrl" msgstr "Ctrl" #: e2_context_menu.c:622 e2_keybinding.c:1148 e2_toolbar.c:2857 #: e2_toolbar.c:2924 e2_toolbar.c:3091 msgid "Action" msgstr "Действие" #: e2_context_menu.c:625 e2_keybinding.c:1151 e2_toolbar.c:2860 #: e2_toolbar.c:2927 e2_toolbar.c:3094 msgid "Argument" msgstr "Параметры" #: e2_date_filter_dialog.c:216 msgid "Display only the items:" msgstr "Показывать только эти элементы:" #: e2_date_filter_dialog.c:217 msgid "date filter" msgstr "фильтр по дате" #: e2_date_filter_dialog.c:225 e2p_glob.c:515 msgid "accessed since" msgstr "считан после" #: e2_date_filter_dialog.c:225 e2p_glob.c:515 msgid "modified before" msgstr "изменён до" #: e2_date_filter_dialog.c:225 e2p_glob.c:515 msgid "modified since" msgstr "изменён после" #: e2_date_filter_dialog.c:226 e2p_glob.c:516 msgid "accessed before" msgstr "считан до" #: e2_date_filter_dialog.c:226 e2p_glob.c:516 msgid "changed before" msgstr "создан до" #: e2_date_filter_dialog.c:226 e2p_glob.c:516 msgid "changed since" msgstr "создан после" #: e2_dialog.c:684 e2_tree_dialog.c:1458 msgid "_Hidden" msgstr "_Скрытые" #: e2_dialog.c:686 msgid "Toggle display of hidden items" msgstr "Включить/Выключить показ скрытых элементов" #: e2_dialog.c:831 msgid "user input" msgstr "ввод" #: e2_dialog.c:1001 msgid "older" msgstr "более старый" #: e2_dialog.c:1003 msgid "newer" msgstr "более новый" #: e2_dialog.c:1012 msgid "existing" msgstr "существует" #: e2_dialog.c:1027 #, c-format msgid "" "Remove all contents of %s\n" "%s ?" msgstr "" "Удалить все содержимое %s\n" "%s ?" #: e2_dialog.c:1032 #, c-format msgid "" "Overwrite %s %s\n" "in %s ?" msgstr "" "Перезаписать %s %s\n" "в %s ?" #: e2_dialog.c:1039 e2_dialog.c:1079 e2_dialog.c:1108 e2_edit_dialog.c:696 #: e2_edit_dialog.c:928 e2_task.c:2117 e2p_crypt.c:430 e2p_rename.c:899 msgid "confirm" msgstr "подтверждение" #: e2_dialog.c:1106 #, c-format msgid "%s is taking a long time. Continue waiting ?" msgstr "%s идёт слишком долго. Подождать еще ?" #: e2_dialog.c:1113 msgid "_Quiet" msgstr "_Тихо" #: e2_dialog.c:1114 msgid "Don't ask any more" msgstr "Больше не спрашивать" #: e2_dialog.c:1118 #, c-format msgid "Cancel the %s" msgstr "Отменить %s" #: e2_dialog.c:1122 msgid "Wait some more" msgstr "Подождать еще" #: e2_dialog.c:1224 msgid "center" msgstr "в центре" #: e2_dialog.c:1224 msgid "mouse" msgstr "на курсоре" #: e2_dialog.c:1224 e2_output.c:2442 e2_toolbar.c:2729 e2p_acl.c:3513 msgid "none" msgstr "нет" #: e2_dialog.c:1225 msgid "always center" msgstr "всегда в центре" #: e2_dialog.c:1225 msgid "center on parent" msgstr "в центре предыдущего" #: e2_dialog.c:1227 msgid "dialog position" msgstr "положение диалоговых окон" #: e2_dialog.c:1228 msgid "This determines the position where dialog windows will pop up" msgstr "Это определяет место, где будут появляться диалоговые окна" #: e2_dnd.c:749 msgid "C_ancel" msgstr "_Отменить" #: e2_edit_dialog.c:119 e2_edit_dialog.c:344 msgid "file save as" msgstr "сохранение файла" #: e2_edit_dialog.c:233 #, c-format msgid "Cannot save %s in its original encoding %s. Reverting to UTF-8" msgstr "" "Не могу сохранить %s в его оригинальной кодировке %s. Перекодирую в UTF-8" #: e2_edit_dialog.c:240 #, c-format msgid "Encoding conversion failed for %s" msgstr "Перекодирование %s не удалось" #: e2_edit_dialog.c:312 #, c-format msgid "Original encoding of '%s' is unknown, now saved as UTF-8" msgstr "Оригинальная кодировка '%s' неизвестна, сохраняю как UTF-8" #: e2_edit_dialog.c:412 msgid "Reverting to saved version cannot be undone" msgstr "Возврат к сохраненной версии невозможен" #: e2_edit_dialog.c:477 #, c-format msgid "Cannot initialize spell checking: %s" msgstr "Не могу запустить проверку правописания: %s" #: e2_edit_dialog.c:524 msgid "choose language" msgstr "выберите язык" #: e2_edit_dialog.c:525 msgid "Enter the spell-checker language (like en_CA):" msgstr "Введите язык для проверки орфографии (в виде ru_RU):" #: e2_edit_dialog.c:535 #, c-format msgid "Cannot set speller language to %s (%s)" msgstr "Не могу установить язык проверки правописания %s (%s)" #: e2_edit_dialog.c:696 msgid "Replace this one ?" msgstr "Заменить его ?" #: e2_edit_dialog.c:810 msgid "Spelling suggestions" msgstr "Возможные варианты написания" #: e2_edit_dialog.c:817 e2_edit_dialog.c:1538 e2_vfs_dialog.c:1025 #: e2p_config.c:1100 msgid "_Save" msgstr "_Сохранить" #: e2_edit_dialog.c:818 msgid "Save the file" msgstr "Сохранить файл" #: e2_edit_dialog.c:820 msgid "Save as.." msgstr "Сохранить как.." #: e2_edit_dialog.c:821 msgid "Save the file with a new name" msgstr "Сохранить файл под новым именем" #: e2_edit_dialog.c:823 msgid "Save se_lection.." msgstr "Сохранить _выделенное.." #: e2_edit_dialog.c:824 msgid "Save the selected text" msgstr "Сохранить выделенный текст" #: e2_edit_dialog.c:829 e2_view_dialog.c:952 msgid "Print file" msgstr "Напечатать файл" #: e2_edit_dialog.c:829 e2_view_dialog.c:952 msgid "Print selected text" msgstr "Напечатать выделенный текст" #: e2_edit_dialog.c:830 e2_view_dialog.c:953 msgid "_Print.." msgstr "_Печать.." #: e2_edit_dialog.c:834 msgid "R_efresh" msgstr "_Обновить" #: e2_edit_dialog.c:835 msgid "Reload the file being edited" msgstr "Загрузить редактируемый файл заново" #: e2_edit_dialog.c:838 e2_toolbar.c:2889 e2p_find.c:3667 msgid "_Find.." msgstr "_Поиск.." #: e2_edit_dialog.c:839 msgid "Find matching text" msgstr "Найти текст" #: e2_edit_dialog.c:840 msgid "_Replace.." msgstr "_Замена.." #: e2_edit_dialog.c:841 msgid "Find and replace matching text" msgstr "Найти и заменить текст" #: e2_edit_dialog.c:843 e2_edit_dialog.c:1549 e2_output.c:1346 #: e2_view_dialog.c:1579 msgid "_Hide" msgstr "Сп_рятать" #: e2_edit_dialog.c:844 e2_view_dialog.c:1587 msgid "Hide the search options bar" msgstr "Спрятать панель поиска" #: e2_edit_dialog.c:847 e2_edit_dialog.c:1550 msgid "_Undo" msgstr "_Отмена" #: e2_edit_dialog.c:848 msgid "Undo last change" msgstr "Отменить последнее изменение" #: e2_edit_dialog.c:852 e2_edit_dialog.c:1552 msgid "Re_do" msgstr "_Повтор" #: e2_edit_dialog.c:853 msgid "Reverse last undo" msgstr "Вернуть последнюю отмену" #: e2_edit_dialog.c:858 msgid "_Check spelling" msgstr "Проверка _правописания" #: e2_edit_dialog.c:859 msgid "Flag mis-spelt words" msgstr "Помечать неправильно написанные слова" #: e2_edit_dialog.c:861 msgid "_Clear spellcheck" msgstr "_Сбросить проверку правописания" #: e2_edit_dialog.c:862 msgid "Remove all spell-check flags" msgstr "Убрать все отметки проверки правописания" #: e2_edit_dialog.c:867 msgid "_Language.." msgstr "_Язык.." #: e2_edit_dialog.c:868 msgid "Set spell-checker language" msgstr "Установить язык проверки правописания" #: e2_edit_dialog.c:872 msgid "_Wrap" msgstr "_Перенос" #: e2_edit_dialog.c:880 e2_view_dialog.c:1593 msgid "If activated, text in the window will be word-wrapped" msgstr "Если включено, то текст в окне будет переноситься автоматически" #: e2_edit_dialog.c:883 msgid "Se_ttings" msgstr "_Настройки" #: e2_edit_dialog.c:884 e2_view_dialog.c:959 msgid "Open the configuration dialog at the options page" msgstr "Открыть диалог настройки на странице параметров" #: e2_edit_dialog.c:928 msgid "Save modified file ?" msgstr "Сохранить изменённый файл ?" #: e2_edit_dialog.c:1416 msgid "editing file" msgstr "редактирование файла" #: e2_edit_dialog.c:1491 msgid "Replacements" msgstr "Замена" #: e2_edit_dialog.c:1495 msgid "If activated, the next match will be sought after each replacement" msgstr "" "Если включено, то после каждой замены автоматически будет предлагаться " "следующая" #: e2_edit_dialog.c:1495 msgid "r_epeat" msgstr "_повтор" #: e2_edit_dialog.c:1498 msgid "If activated, all matches will be replaced at once" msgstr "Если включено, то все совпадения будут заменены сразу" #: e2_edit_dialog.c:1498 msgid "_all" msgstr "_все" #: e2_edit_dialog.c:1501 msgid "If activated, confirmation will be sought when \"replacing all\"" msgstr "" "Если включено, то перед \"заменой всего\" будет запрашиваться подтверждение" #: e2_edit_dialog.c:1501 msgid "co_nfirm" msgstr "под_тверждение" #: e2_edit_dialog.c:1509 e2_view_dialog.c:1571 msgid "not found" msgstr "не найдено" #: e2_edit_dialog.c:1516 msgid "_Replace" msgstr "_Заменить" #: e2_edit_dialog.c:1524 msgid "Replace this match" msgstr "Заменить текст" #: e2_edit_dialog.c:1527 e2_view_dialog.c:1596 e2p_find.c:3627 msgid "_Find" msgstr "_Поиск" #: e2_edit_dialog.c:1535 e2_view_dialog.c:1605 msgid "Find the next match" msgstr "Искать дальше" #: e2_edit_dialog.c:1705 msgid "backup when saving" msgstr "делать резервную копию перед сохранением" #: e2_edit_dialog.c:1706 msgid "" "When saving an edited file, an existing file with the same name will be " "renamed" msgstr "Перед сохраненем редактируемого файла, его оригинал будет переименован" #: e2_file_info_dialog.c:207 e2_file_info_dialog.c:210 #: e2_file_info_dialog.c:446 e2_file_info_dialog.c:478 #: e2_file_info_dialog.c:499 msgid "Type:" msgstr "Тип:" #: e2_file_info_dialog.c:215 msgid "Item:" msgstr "Элемент:" #: e2_file_info_dialog.c:217 e2_file_info_dialog.c:645 msgid "Size:" msgstr "Размер:" #: e2_file_info_dialog.c:218 e2_file_info_dialog.c:672 msgid "User:" msgstr "Пользователь:" #: e2_file_info_dialog.c:219 e2_file_info_dialog.c:680 msgid "Group:" msgstr "Группа:" #: e2_file_info_dialog.c:220 e2_file_info_dialog.c:692 msgid "Permissions:" msgstr "Права:" #: e2_file_info_dialog.c:221 e2_file_info_dialog.c:703 msgid "Accessed:" msgstr "Считан:" #: e2_file_info_dialog.c:222 e2_file_info_dialog.c:717 msgid "Modified:" msgstr "Изменён:" #: e2_file_info_dialog.c:223 e2_file_info_dialog.c:731 msgid "Changed:" msgstr "Создан:" #: e2_file_info_dialog.c:247 msgid "Copy displayed data" msgstr "Копировать эти данные" #: e2_file_info_dialog.c:344 msgid "file info" msgstr "информация о файле" #: e2_file_info_dialog.c:365 msgid "Virtual file" msgstr "Виртуальный файл" #: e2_file_info_dialog.c:381 e2_file_info_dialog.c:497 e2_utf8.c:339 #: e2_utf8.c:369 e2_utf8.c:397 e2_utf8.c:427 msgid "Unknown" msgstr "Неизвестный" #: e2_file_info_dialog.c:464 e2p_acl.c:3609 e2p_crypt.c:2798 msgid "Directory" msgstr "Каталог" #: e2_file_info_dialog.c:487 msgid "Symbolic Link" msgstr "Символическая ссылка" #: e2_file_info_dialog.c:489 msgid "Character Device" msgstr "Символьное устройство" #: e2_file_info_dialog.c:491 msgid "Block Device" msgstr "Блочное устройство" #: e2_file_info_dialog.c:493 msgid "FIFO Pipe" msgstr "FIFO Pipe" #: e2_file_info_dialog.c:495 msgid "Socket" msgstr "Сокет" #: e2_file_info_dialog.c:523 #, c-format msgid "to %s" msgstr "на %s" #: e2_file_info_dialog.c:571 msgid "(which is missing)" msgstr "(который не существует)" #: e2_file_info_dialog.c:573 msgid "(which is itself)" msgstr "(который указывает на себя)" #: e2_file_info_dialog.c:588 #, c-format msgid "and ultimately to %s" msgstr "и окончательно на %s" #: e2_file_info_dialog.c:602 msgid "(Cannot resolve the link)" msgstr "(Не могу определить ссылку)" #: e2_file_info_dialog.c:623 msgid "Encoding:" msgstr "Кодировка файла:" #: e2_file_info_dialog.c:623 e2_file_info_dialog.c:627 #: e2_file_info_dialog.c:633 msgid "Mime:" msgstr "Тип mime:" #: e2_file_info_dialog.c:666 e2_size_filter_dialog.c:192 e2p_du.c:170 #: e2p_find.c:2781 e2p_glob.c:501 msgid "bytes" msgstr "байт" #: e2_file_info_dialog.c:711 msgid "Item was last opened, run, read etc" msgstr "Элемент был открыт, запущен, прочитан и т.п." #: e2_file_info_dialog.c:725 msgid "Content of the item was last altered" msgstr "Содержимое файла было изменено" #: e2_file_info_dialog.c:739 msgid "Property of the item (name, permission, owner etc) was last altered" msgstr "Параметр элемента (имя, права, владелец и т.п.) был изменен" #: e2_filelist.c:261 msgid "Something is wrong with the auto-refresh mechanism" msgstr "Неисправен механизм авто-обновления" #: e2_filelist.c:773 e2_toolbar.c:1621 msgid "k" msgstr "k" #: e2_filelist.c:778 e2_toolbar.c:1626 msgid "M" msgstr "M" #: e2_filetype.c:121 e2_filetype.c:307 e2_filetype.c:502 msgid "" msgstr "<нет>" #: e2_filetype.c:491 e2p_du.c:220 msgid "directories" msgstr "каталоги" #: e2_filetype.c:495 e2_filetype.c:602 e2_filetype.c:616 e2_filetype.c:631 #: e2_filetype.c:645 e2_filetype.c:680 e2_filetype_dialog.c:1201 #: e2_output.c:1397 e2_vfs_dialog.c:1029 e2_vfs_dialog.c:1571 msgid "_Open" msgstr "_Открыть" #: e2_filetype.c:496 e2_filetype.c:603 e2_filetype.c:617 e2_filetype.c:632 #: e2_filetype.c:646 e2_filetype.c:681 msgid "O_pen in other" msgstr "Открыть в _другой" #: e2_filetype.c:497 msgid "_Mount" msgstr "_Монтировать" #: e2_filetype.c:498 msgid "_Unmount" msgstr "_Размонтировать" #: e2_filetype.c:499 msgid "executables" msgstr "исполняемые" #: e2_filetype.c:505 e2_filetype_dialog.c:1198 e2p_upgrade.c:742 #: e2p_upgrade.c:743 msgid "_Run" msgstr "_Запустить" #: e2_filetype.c:508 e2_filetype.c:691 msgid "Edit _with.." msgstr "Редактировать _в.." #: e2_filetype.c:508 e2_filetype.c:691 e2p_upgrade.c:746 e2p_upgrade.c:747 msgid "Editor command:" msgstr "Команда редактора:" #: e2_filetype.c:509 e2_filetype.c:543 e2_filetype.c:694 #: e2_filetype_dialog.c:1188 e2_output.c:1394 msgid "_Edit" msgstr "_Редактировать" #: e2_filetype.c:510 e2p_find.c:318 msgid "office documents" msgstr "офисные документы" #: e2_filetype.c:516 e2_filetype.c:553 msgid "_Openoffice" msgstr "_Openoffice" #: e2_filetype.c:517 msgid "HTML documents" msgstr "HTML документы" #: e2_filetype.c:522 msgid "_Firefox" msgstr "_Firefox" #: e2_filetype.c:523 msgid "_Mozilla" msgstr "_Mozilla" #: e2_filetype.c:524 msgid "_Lynx" msgstr "_Lynx" #: e2_filetype.c:525 msgid "_Opera" msgstr "_Opera" #: e2_filetype.c:526 msgid "PDF documents" msgstr "PDF документы" #: e2_filetype.c:532 msgid "postscript documents" msgstr "postscript документы" #: e2_filetype.c:537 msgid "text documents" msgstr "текстовые документы" #: e2_filetype.c:542 msgid "View" msgstr "Просмотр" #: e2_filetype.c:547 msgid "spreadsheets" msgstr "электронные таблицы" #: e2_filetype.c:554 msgid "audio files" msgstr "аудио файлы" #: e2_filetype.c:565 msgid "image files" msgstr "изображения" #: e2_filetype.c:585 msgid "video files" msgstr "видео файлы" #: e2_filetype.c:595 msgid "plain tarballs" msgstr "архив tar" #: e2_filetype.c:599 e2_filetype.c:605 e2_filetype.c:619 e2_filetype.c:634 #: e2_filetype.c:648 msgid "Unpack" msgstr "Рапаковать" #: e2_filetype.c:600 msgid "Unpack in other pane" msgstr "Распаковать в другой панели" #: e2_filetype.c:607 e2_filetype.c:621 e2_filetype.c:636 e2_filetype.c:650 msgid "_List contents" msgstr "_Просмотреть содержимое" #: e2_filetype.c:608 msgid "gzip tarballs" msgstr "архивы gzip" #: e2_filetype.c:613 e2_filetype.c:628 e2p_unpack.c:526 msgid "_Unpack" msgstr "_Распаковать" #: e2_filetype.c:614 e2_filetype.c:629 msgid "Unpack in _other pane" msgstr "Распаковать в _другой панели" #: e2_filetype.c:622 msgid "bzip2 tarballs" msgstr "архивы bzip2" #: e2_filetype.c:637 msgid "zip archives" msgstr "архивы zip" #: e2_filetype.c:641 msgid "_Unzip" msgstr "_Распаковать" #: e2_filetype.c:643 msgid "Unzip in _other pane" msgstr "Распаковать в _другой панели" #: e2_filetype.c:673 msgid "RPM packages" msgstr "пакеты RPM" #: e2_filetype.c:677 msgid "In_formation" msgstr "_Информация" #: e2_filetype.c:678 msgid "_Install" msgstr "_Установить" #: e2_filetype.c:683 msgid "source code files" msgstr "исходные коды" #: e2_filetype.c:695 msgid "object files" msgstr "объектные файлы" #: e2_filetype.c:701 msgid "_View symbols" msgstr "_Просмотреть символы" #: e2_filetype.c:727 e2_keybinding.c:1142 msgid "Category" msgstr "Категория" #: e2_filetype.c:732 e2_menu.c:999 msgid "Command" msgstr "Команда" #: e2_filetype.c:739 msgid "case-insensitive filetypes" msgstr "не учитывать регистр у расширений" #: e2_filetype.c:740 msgid "" "This causes text-case to always be ignored when matching a file-extension" msgstr "Это заставляет не учитывать регистр расширений у файлов при обработке" #: e2_filetype_dialog.c:443 msgid "new" msgstr "новый" #: e2_filetype_dialog.c:689 e2_option_tree_context_menu.c:227 msgid "Cu_t" msgstr "_Вырезать" #: e2_filetype_dialog.c:690 msgid "Cut selected row(s)" msgstr "Вырезать выбранную строку(ки)" #: e2_filetype_dialog.c:692 msgid "Copy selected row(s)" msgstr "Копировать выбранную строку(ки)" #: e2_filetype_dialog.c:697 e2_option_tree_context_menu.c:235 msgid "_Paste" msgstr "В_ставить" #: e2_filetype_dialog.c:698 e2_option_tree_context_menu.c:236 msgid "Paste previously copied or cut row(s) after current row" msgstr "Вставить заранее скопированную или вырезанную строку после текущей" #: e2_filetype_dialog.c:702 e2_option_tree_context_menu.c:256 #: e2_permissions_dialog.c:454 e2p_acl.c:3725 msgid "_Add" msgstr "_Добавить" #: e2_filetype_dialog.c:703 e2_option_tree_context_menu.c:257 msgid "Add a row after the current one" msgstr "Добавить строку после текущей" #: e2_filetype_dialog.c:705 e2_filetype_dialog.c:1002 e2_option_tree.c:969 #: e2_option_tree_context_menu.c:264 e2_toolbar.c:386 e2_toolbar.c:2998 #: e2_toolbar.c:3059 e2_toolbar.c:3065 msgid "_Up" msgstr "В_верх" #: e2_filetype_dialog.c:706 e2_option_tree_context_menu.c:265 msgid "Move selected row up" msgstr "Переместить текущую строку вверх" #: e2_filetype_dialog.c:707 e2_filetype_dialog.c:1008 e2_option_tree.c:972 #: e2_option_tree_context_menu.c:267 e2_toolbar.c:388 msgid "_Down" msgstr "В_низ" #: e2_filetype_dialog.c:708 e2_option_tree_context_menu.c:268 msgid "Move selected row down" msgstr "Переместить текущую строку вниз" #: e2_filetype_dialog.c:834 msgid "edit filetypes" msgstr "настроить типы файлов" #: e2_filetype_dialog.c:903 msgid "Extensions" msgstr "Расширения" #: e2_filetype_dialog.c:928 msgid "Labels" msgstr "Названия" #: e2_filetype_dialog.c:937 msgid "Commands" msgstr "Команды" #: e2_filetype_dialog.c:1006 e2_option_tree.c:970 msgid "Move the selected row one place up" msgstr "Переместить текущую строку на одну позицию вверх" #: e2_filetype_dialog.c:1012 e2_option_tree.c:973 msgid "Move the selected row one place down" msgstr "Переместить текущую строку на одну позицию вниз" #: e2_filetype_dialog.c:1014 e2_option_tree.c:980 e2_vfs_dialog.c:1557 #: e2p_acl.c:3795 msgid "Add a_fter" msgstr "_Добавить" #: e2_filetype_dialog.c:1018 msgid "Add a row after the currently selected one" msgstr "Добавить строку после текушей" #: e2_filetype_dialog.c:1157 msgid "ambiguous filetype" msgstr "неопределенный тип файла" #: e2_filetype_dialog.c:1157 msgid "unrecognised filetype" msgstr "неизвестный тип файла" #: e2_filetype_dialog.c:1162 #, c-format msgid "" "What would you like to do with\n" "%s\n" "in %s ?" msgstr "" "Что вы хотите сделать с\n" "%s\n" "в %s ?" #: e2_filetype_dialog.c:1173 msgid "_Add.." msgstr "_Добавить.." #: e2_filetype_dialog.c:1174 msgid "" "Create a new filetype for this extension, or add it to an existing filetype" msgstr "" "Создать новый тип файла для этого расширения, или добавить его к " "существующему типу" #: e2_filetype_dialog.c:1189 msgid "Edit the file" msgstr "Редактировать файл" #: e2_filetype_dialog.c:1199 msgid "Execute the item" msgstr "Запустить элемент" #: e2_filetype_dialog.c:1202 msgid "Open with the default application" msgstr "Открыть в программе по-умолчанию" #: e2_filetype_dialog.c:1206 msgid "_Open.." msgstr "_Открыть.." #: e2_filetype_dialog.c:1207 msgid "Enter a command with which to open the file" msgstr "Введите команду, с помощью которой следует открыть этот файл" #: e2_fileview.c:155 e2_ownership_dialog.c:217 e2_permissions_dialog.c:327 #: e2_plugins.c:1284 e2p_acl.c:3467 e2p_crypt.c:2805 e2p_times.c:697 msgid "Filename" msgstr "Имя файла" #: e2_fileview.c:156 msgid "Size" msgstr "Размер" #: e2_fileview.c:157 e2_permissions_dialog.c:386 e2p_acl.c:3573 msgid "Permissions" msgstr "Права" #: e2_fileview.c:158 msgid "Owner" msgstr "Владелец" #: e2_fileview.c:159 e2_permissions_dialog.c:355 e2_permissions_dialog.c:410 #: e2p_acl.c:229 e2p_acl.c:2093 e2p_acl.c:3495 msgid "Group" msgstr "Группа" #: e2_fileview.c:160 msgid "Modified" msgstr "Изменён" #: e2_fileview.c:161 e2p_times.c:716 msgid "Accessed" msgstr "Считан" #: e2_fileview.c:162 msgid "Changed" msgstr "Создан" #: e2_fs.c:711 #, c-format msgid "'%s' is not a directory" msgstr "'%s' не является каталогом" #: e2_fs.c:720 #, c-format msgid "Cannot access directory '%s' - No permission" msgstr "Не могу зайти в каталог '%s' - Нет доступа" #: e2_fs.c:725 #, c-format msgid "Directory '%s' does not exist" msgstr "Каталог '%s' не существует" #: e2_fs.c:1293 e2_fs.c:1378 msgid "Reading directory data" msgstr "Чтение каталога" #: e2_fs.c:1294 msgid "directory read" msgstr "чтение каталога" #: e2_fs.c:2082 #, c-format msgid "Cannot open '%s' for writing - %s" msgstr "Не могу открыть '%s' для записи - %s" #: e2_fs.c:2127 #, c-format msgid "Error writing file '%s' - %s" msgstr "Ошибка записи в файл '%s' - %s" #: e2_fs.c:2243 e2_fs.c:2289 e2_fs.c:2514 e2_view_dialog.c:491 #: e2p_config.c:652 e2p_crypt.c:1720 e2p_dircmp.c:440 #, c-format msgid "Error reading file %s" msgstr "Ошибка при чтении файла %s" #: e2_fs.c:2272 e2_fs.c:2465 e2_fs_walk.c:311 e2_fs_walk.c:471 #: e2_fs_walk.c:700 e2_output.c:825 e2_task_backend.c:1261 e2p_acl.c:1808 #: e2p_acl.c:1882 #, c-format msgid "Cannot get information about %s" msgstr "Не могу получить информацию о %s" #: e2_fs.c:2279 #, c-format msgid "Cannot open file %s" msgstr "Не могу открыть файл %s" #: e2_fs.c:2350 e2_fs.c:2454 #, c-format msgid "Cannot create file %s" msgstr "Не могу создать файл %s" #: e2_fs.c:2362 e2_fs.c:2529 e2p_crypt.c:1752 #, c-format msgid "Error writing file %s" msgstr "Ошибка при записи в файл %s" #: e2_fs.c:2440 e2p_crypt.c:867 e2p_crypt.c:1088 e2p_dircmp.c:408 #, c-format msgid "Cannot open '%s' for reading" msgstr "Не могу открыть '%s' для чтения" #: e2_fs.c:2589 #, c-format msgid "Cannot open pipe for command '%s'" msgstr "Не могу открыть pipe для команды '%s'" #: e2_fs.c:2612 #, c-format msgid "Command %s failed: not enough memory" msgstr "Команда %s ошибка: не хватает памяти" #: e2_fs.c:2805 msgid "-rwxrwxrwx" msgstr "-rwxrwxrwx" #: e2_fs.c:2810 msgid "ldbcfs" msgstr "ldbcfs" #: e2_fs.c:2815 msgid "TtSs" msgstr "TtSs" #: e2_fs_walk.c:176 #, c-format msgid "Cannot change anything in %s" msgstr "Не могу ничего изменить в %s" #: e2_fs_walk.c:176 e2_fs_walk.c:652 e2_task_backend.c:160 e2p_find.c:1766 #: e2p_find.c:2337 e2p_times.c:359 #, c-format msgid "Cannot change permissions of %s" msgstr "Не могу изменить права %s" #: e2_fs_walk.c:333 e2_fs_walk.c:341 #, c-format msgid "Directory %s not opened" msgstr "Каталог %s не открыт" #: e2_fs_walk.c:386 #, c-format msgid "Cannot open directory %s" msgstr "Не могу открыть каталог %s" #: e2_keybinding.c:840 #, c-format msgid "Cannot find a key binding named %s" msgstr "Не могу найти клавишу управления %s" #: e2_keybinding.c:850 e2_option.c:1089 msgid "key bindings" msgstr "клавиши управления" #: e2_keybinding.c:1062 msgid "Now press one of h,m,d\\n" msgstr "Теперь нажмите одно из h,m,d\\n" #: e2_keybinding.c:1144 msgid "Key" msgstr "Клавиша" #: e2_keybinding.c:1146 msgid "Continue" msgstr "Продолжать" #: e2_keybinding.c:1160 msgid "chained keybindings timeout (ms)" msgstr "время для набора сложной комбинации клавиш (мс)" #: e2_keybinding.c:1161 msgid "" "This sets the time limit (in milliseconds) for accepting 'chained' " "keybindings" msgstr "" "Это устанавливает временной интервал (в миллисекундах) для набора 'сложной' " "комбинации клавиш" #: e2_main.c:231 #, c-format msgid "Your current locale is '%s'.\n" msgstr "Ваша текущая локаль '%s'.\n" #: e2_main.c:233 #, c-format msgid "" "You have set the environment variable G_BROKEN_FILENAMES, which\n" "causes GTK+ to convert filename encoding, from the one specified\n" "by the system locale, to UTF-8.\n" "However, you have not set a system locale. Please do so, by setting\n" "the environment variable LANG or LC_CTYPE!\n" msgstr "" "Вы установили переменную окружения G_BROKEN_FILENAMES, которая\n" "позволяет GTK+ перекодировать названия файлов из кодировки, указанной\n" "в системной локале, в UTF-8.\n" "Но вы не установили системную локаль. Пожалуйста сделайте это, определив\n" "переменную окружения LANG или LC_CTYPE!\n" #: e2_main.c:241 #, c-format msgid "" "(Note: There is a command line option -i/--ignore-problems, but use it\n" "at your own risk!)\n" msgstr "" "(Примечание: Существует опция командной строки -i/--ignore-problems, но\n" "используйте ее на свой страх и риск!)\n" #: e2_main.c:246 #, c-format msgid "" "%s will ignore locale problems when reading filenames because\n" "--ignore-problems/-i has been set. This might result in segfaults and all\n" "kind of problems. You really should set a system locale with the\n" "LANG or LC_CTYPE environment variable.\n" msgstr "" "%s будет игнорировать проблемы с локалью при чтении имен файлов,\n" "потому что установлена опция --ignore-problems/-i. Это может повлечь\n" "за собой ошибки памяти и други проблемы. Вам настоятельно рекомендуется\n" "установить системную локаль с помощью переменных окружения LANG или " "LC_CTYPE.\n" #: e2_main.c:342 msgid "emelFM2" msgstr "emelFM2" #: e2_main.c:558 #, c-format msgid "%u process(es) are running" msgstr "%u процесса(ов) запущены" #: e2_menu.c:745 msgid "no children" msgstr "нет дочерних процессов" #: e2_menu.c:766 msgid "_Name filter" msgstr "Фильтр по _Имени" #: e2_menu.c:768 msgid "_Size filter" msgstr "Фильтр по _Размеру" #: e2_menu.c:770 msgid "_Date filter" msgstr "Фильтр по _Дате" #: e2_menu.c:773 msgid "_Directories too" msgstr "_Каталоги тоже" #: e2_menu.c:780 msgid "_Remove all filters" msgstr "_Убрать все фильтры" #: e2_menu.c:990 e2_plugins.c:1276 msgid "Menu" msgstr "Меню" #: e2_mkdir_dialog.c:58 msgid "new directory" msgstr "создание каталога" #: e2_mkdir_dialog.c:332 msgid "yes" msgstr "да" #: e2_mkdir_dialog.c:339 msgid "no" msgstr "нет" #: e2_mkdir_dialog.c:411 #, c-format msgid "cannot write to '%s' - %s" msgstr "невозможно записать в '%s' - %s" #: e2_mkdir_dialog.c:419 #, c-format msgid "cannot write to parent directory - %s" msgstr "невозможно записать в родительский каталог - %s" #: e2_mkdir_dialog.c:427 #, c-format msgid "only '%s' exists - %s" msgstr "только '%s' существует - %s" #: e2_mkdir_dialog.c:456 msgid "the directory already exists." msgstr "каталог уже существует" #: e2_mkdir_dialog.c:458 msgid "something is in the way." msgstr "что-то мешает на пути." #: e2_mkdir_dialog.c:468 msgid "directories will be created" msgstr "каталоги будут созданы" #: e2_mkdir_dialog.c:656 e2_mkdir_dialog.c:701 e2_task_backend.c:925 #, c-format msgid "Cannot create directory %s" msgstr "Не могу создать каталог %s" #: e2_mkdir_dialog.c:902 msgid "What is the new directory's name?" msgstr "Введите название нового каталога:" #: e2_mkdir_dialog.c:902 msgid "create directory" msgstr "создание каталога" #: e2_mkdir_dialog.c:927 msgid "parent directory:" msgstr "родительский каталог:" #: e2_mkdir_dialog.c:933 msgid "creation possible:" msgstr "создание возможно:" #: e2_mkdir_dialog.c:1003 msgid "open info frame" msgstr "показывать подсказки" #: e2_mkdir_dialog.c:1004 msgid "" "This causes make-directory dialogs to start with extra information displayed" msgstr "" "Это включает отображение дополнительной информации в окне создания нового " "каталога" #: e2_mkdir_dialog.c:1007 msgid "follow active-pane directory" msgstr "следовать за активным каталогом" #: e2_mkdir_dialog.c:1008 msgid "" "This makes the parent directory for new directories the same as the one in " "the active pane, even if the latter changes" msgstr "" "Это делает родительский каталог для новых каталогов таким же, как и в " "активной панели, даже если он изменился" #: e2_mkdir_dialog.c:1012 msgid "suggest directory name" msgstr "предлагать название каталога" #: e2_mkdir_dialog.c:1013 msgid "" "This presents a suggested name for each new directory, based on the last-" "created directory with an increasing number appended" msgstr "" "Это предлагает возможное имя для каждого нового каталога, на основании " "последнего созданного каталога с присоединением порядкового номера" #: e2_mkdir_dialog.c:1017 msgid "show last directory name" msgstr "показывать название последнего каталога" #: e2_mkdir_dialog.c:1018 msgid "" "This causes the name of the last-created directory to be shown in the entry " "field, after opening the dialog or when creating another directory" msgstr "" "Это включает отображение названия последнего созданного каталога, при " "следующем открытии диалога создания каталога" #: e2_mkdir_dialog.c:1021 msgid "replicate changes" msgstr "сохранять изменения" #: e2_mkdir_dialog.c:1022 msgid "" "This causes option-changes to be replicated in other mkdir dialogs. " "Otherwise such changes will be confined to the current dialog" msgstr "" "Это включает сохранение изменённых параметров в диалоге создания каталога. " "Иначе эти изменения будут действовать только в текущем диалоге" #: e2_name_filter_dialog.c:57 e2p_glob.c:301 msgid "Invalid filename pattern" msgstr "Неверный шаблон имени файла" #: e2_name_filter_dialog.c:161 msgid "Display only the items named like:" msgstr "Показывать только элементы с именами как:" #: e2_name_filter_dialog.c:162 msgid "name filter" msgstr "фильтр по имени" #: e2_name_filter_dialog.c:175 e2p_glob.c:438 msgid "example: *.c,*.h" msgstr "пример: *c,*.h" #: e2_name_filter_dialog.c:178 msgid "Invert" msgstr "Обратить" #: e2_name_filter_dialog.c:186 msgid "Show files that DO NOT match the given mask" msgstr "Показывать файлы, которые НЕ совпадают с маской" #: e2_name_filter_dialog.c:187 msgid "Case sensitive" msgstr "Учитывать регистр" #: e2_option.c:181 #, c-format msgid "Cannot create trash directory %s" msgstr "Не могу создать каталог корзины %s" #: e2_option.c:346 msgid "Configuration data re-loaded" msgstr "Конфигурационный файл перезагружен" #: e2_option.c:611 #, c-format msgid "" "# This is the %s configuration data file.\n" "# It will be overwritten each time the program is run!\n" "\n" "# If you're inclined to edit the file between program sessions, note this:\n" "# for tree options, you have to use \\| to escape | and you have to use \\< " "to escape <,\n" "# if that is the first non-space character on a line.\n" "\n" msgstr "" "# Это конфигурационный файл для %s.\n" "# Он будет перезаписан каждый раз при запуске программы!\n" "\n" "# Если вам нужно изменить этот файл, имейте в виду следующее:\n" "# необходимо использовать \\| вместо |, и \\< вместо <, \n" "# если это первый сивмол не-пробел в строке.\n" "\n" #: e2_option.c:704 #, c-format msgid "Cannot write config file %s - %s" msgstr "Не могу записать конфигурационный файл %s - %s" #: e2_option.c:1067 msgid "aliases" msgstr "псевдонимы" #: e2_option.c:1068 msgid "bookmarks" msgstr "закладки" #: e2_option.c:1069 msgid "colors" msgstr "цвета" #: e2_option.c:1070 msgid "columns" msgstr "столбцы" #: e2_option.c:1071 msgid "command bar" msgstr "командная панель" #: e2_option.c:1072 msgid "command line" msgstr "командная строка" #: e2_option.c:1074 msgid "confirmation" msgstr "подтверждения" #: e2_option.c:1075 msgid "context menu" msgstr "контекстное меню" #: e2_option.c:1076 msgid "custom menus" msgstr "особое меню" #: e2_option.c:1077 msgid "delays" msgstr "задержки" #: e2_option.c:1078 msgid "dialogs" msgstr "диалоги" #: e2_option.c:1079 msgid "directory lines" msgstr "текущий каталог" #: e2_option.c:1080 msgid "extensions" msgstr "расширения" #: e2_option.c:1081 msgid "file actions" msgstr "операции с файлами" #: e2_option.c:1082 msgid "filetypes" msgstr "типы файлов" #: e2_option.c:1083 msgid "fonts" msgstr "шрифты" #: e2_option.c:1084 msgid "general" msgstr "основные" #: e2_option.c:1086 e2p_config.c:1266 e2p_config.c:1301 e2p_config.c:1323 msgid "icons" msgstr "значки" #: e2_option.c:1087 msgid "interface" msgstr "внешний вид" #: e2_option.c:1088 msgid "item types" msgstr "типы файлов" #: e2_option.c:1090 msgid "make directory" msgstr "создание каталога" #: e2_option.c:1091 msgid "menus" msgstr "меню" #: e2_option.c:1092 msgid "miscellaneous" msgstr "разное" #: e2_option.c:1093 msgid "options" msgstr "параметры" #: e2_option.c:1095 msgid "pane 1" msgstr "панель 1" #: e2_option.c:1096 msgid "pane1 bar" msgstr "панель инструментов 1" #: e2_option.c:1097 msgid "pane 2" msgstr "панель 2" #: e2_option.c:1098 msgid "pane2 bar" msgstr "панель инструментов 2" #: e2_option.c:1100 msgid "plugins" msgstr "модули" #: e2_option.c:1101 msgid "position" msgstr "расположение" #: e2_option.c:1103 msgid "startup" msgstr "запуск" #: e2_option.c:1104 msgid "style" msgstr "стиль" #: e2_option.c:1105 msgid "tab completion" msgstr "автодополнение" #: e2_option.c:1106 msgid "task bar" msgstr "панель задач" #: e2_option__default.c:70 msgid "show all options in config dialogs" msgstr "показывать все параметры в окнах настройки" #: e2_option__default.c:74 msgid "reload config on external change" msgstr "перезагружать конфигурационный файл при изменении" #: e2_option__default.c:75 msgid "" "This enables automatic reloading of the configuration data for this program, " "if that data is changed by another program instance" msgstr "" "Это включает автоматическую перезагрузку конфигурационного файла, если он " "был изменён другой программой" #: e2_option__default.c:85 msgid "document containing usage advice" msgstr "файл, содержащий советы по использованию" #: e2_option__default.c:86 msgid "This document is opened from the help dialog usage page" msgstr "Этот файл содержит справку по использованию программы" #: e2_option__default.c:92 msgid "document containing configuration advice" msgstr "файл, содержащий советы по настройке" #: e2_option__default.c:93 msgid "This document is opened from the help dialog configuration page" msgstr "Этот файл содержит справку по настройке программы" #: e2_option__default.c:98 msgid "warn about running processes when shutting down" msgstr "при выходе предупреждать о выполняющихся процессах" #: e2_option__default.c:99 msgid "This enables a reminder about incomplete commands and actions" msgstr "" "Это включает напоминания о незавершенных процессах и действиях при выходе" #: e2_option__default.c:115 msgid "opacity" msgstr "прозрачность" #: e2_option__default.c:116 msgid "Window translucence, 30 (faint) to 100 (opaque)" msgstr "Прозрачность окна, от 30 (прозрачное) до 100 (непрозрачное)" #: e2_option__default.c:120 msgid "'go up' on middle-button click" msgstr "'переходить вверх' при нажатии средней кнопки" #: e2_option__default.c:121 msgid "" "This is a faster alternative to double clicking '..' or clicking the 'go up' " "button" msgstr "" "Это быстрая альтернатива двойному нажатию на '..' или нажатию на кнопку " "'переход вверх'" #: e2_option__default.c:123 msgid "match windows (TM) right-click behaviour" msgstr "вести себя как windows (TM) при нажатии правой кнопки" #: e2_option__default.c:124 msgid "" "If activated, clicking the right mouse button will also select the row where " "the mouse cursor is" msgstr "" "Если включено, то нажатие на правую кнопку мыши будет также выделять строку " "под курсором" #: e2_option__default.c:127 msgid "advanced windows (TM) right-click behaviour" msgstr "улучшенное поведение windows (TM) при нажатии правой кнопки" #: e2_option__default.c:128 msgid "" "If activated, clicking on a free area will not clear the current selection" msgstr "" "Если включено, то нажатие на пустом месте не будет сбрасывать текущее " "выделение" #: e2_option__default.c:131 msgid "show icons in dialog buttons" msgstr "показывать значки на кнопках" #: e2_option__default.c:132 msgid "If activated, dialog buttons will show an icon as well as a label" msgstr "" "Если включено, то на кнопках диалоговых окон будут отображаться и значки и " "названия" #: e2_option__default.c:137 msgid "use icons directory" msgstr "использовать каталог значков" #: e2_option__default.c:138 msgid "" "If activated, icon files in the directory shown below will be used for " "toolbars etc" msgstr "" "Если включено, то для панелей инструментов будут использованы значки из " "каталога введенного ниже" #: e2_option__default.c:142 msgid "icons directory" msgstr "каталог значков" #: e2_option__default.c:143 msgid "The directory from which icon files will be retrieved" msgstr "Каталог, из которого будут использоваться значки" #: e2_option__default.c:155 msgid "show icons in menus" msgstr "показывать значки в меню" #: e2_option__default.c:156 msgid "Some people think that icons help you recognize items more quickly" msgstr "" "Некоторые люди полагают, что значки позволяют быстрее ориентироваться в меню" #: e2_option__default.c:160 e2_option__default.c:329 e2_toolbar.c:2794 msgid "button" msgstr "кнопка" #: e2_option__default.c:160 e2_option__default.c:329 e2_toolbar.c:2794 msgid "dnd" msgstr "dnd" #: e2_option__default.c:160 e2_option__default.c:329 e2_toolbar.c:2794 msgid "menu" msgstr "меню" #: e2_option__default.c:160 e2_option__default.c:329 e2_toolbar.c:2794 msgid "toolbar large" msgstr "большая панель" #: e2_option__default.c:160 e2_option__default.c:329 e2_toolbar.c:2794 msgid "toolbar small" msgstr "маленькая панель" #: e2_option__default.c:161 msgid "menu icon size" msgstr "размер значков в меню" #: e2_option__default.c:162 msgid "This sets the icon size for ALL menus" msgstr "Это устанавливает размер значков во ВСЕХ меню" #: e2_option__default.c:167 msgid "menu popup delay (ms)" msgstr "задержка появления меню (мс)" #: e2_option__default.c:168 msgid "" "The delay (in milliseconds) after the mouse pointer arrives at a menu item, " "before any submenu will pop up" msgstr "" "Задержка (в миллисекундах) между тем, как курсор мыши попадает на пункт " "меню, и тем как появляется соответствующее подменю" #: e2_option__default.c:171 msgid "menu popdown delay (ms)" msgstr "задержка исчезновения меню (мс)" #: e2_option__default.c:172 msgid "" "The delay (in milliseconds) after the mouse pointer leaves a menu item, " "before popping down an activated submenu" msgstr "" "Задержка (в миллисекундах) между тем, как курсор мыши покидает пункт меню, и " "тем как исчезает соответствующее подменю" #: e2_option__default.c:181 msgid "auto refresh" msgstr "автоматическое обновление" #: e2_option__default.c:182 msgid "" "This enables automatic checking for changes to the content of displayed " "directories. Otherwise you need to refresh manually" msgstr "" "Это включает автоматическое слежение за содержимым показываемых каталогов. " "Если выключено, то вам нужно обновлять панели вручную" #: e2_option__default.c:185 msgid "focus the pane after completing a directory entry" msgstr "активировать панель, при входе в каталог" #: e2_option__default.c:186 msgid "" "If activated, after return/enter is pressed in a directory line, the pane " "containing that dir line will become the active one" msgstr "" "Если включено, то после входа в каталог, панель содержащая этот каталог " "станет активной" #: e2_option__default.c:190 msgid "select first item in newly-opened directories" msgstr "выделять первый элемент в только что открытом каталоге" #: e2_option__default.c:191 msgid "" "If activated, when a directory is first displayed in a session, the first " "item there will be selected" msgstr "" "Если включено, то при первом входе в каталог, будет выбираться первый " "элемент в этом каталоге" #: e2_option__default.c:196 msgid "use horizontal panes" msgstr "использовать горизонтальные панели" #: e2_option__default.c:197 msgid "Horizontal (vertical-stacked) panes present more columns at once" msgstr "" "Горизонтальные (одна над другой) панели отображают больше столбцов " "единовременно" #: e2_option__default.c:205 msgid "bottom-left" msgstr "снизу-слева" #: e2_option__default.c:205 msgid "bottom-right" msgstr "снизу-справа" #: e2_option__default.c:205 msgid "top-left" msgstr "сверху-слева" #: e2_option__default.c:205 msgid "top-right" msgstr "сверху-справа" #: e2_option__default.c:206 msgid "scrollbar position" msgstr "расположение полосы прокрутки" #: e2_option__default.c:207 msgid "The default (bottom-right) should be ok for most users" msgstr "Стандартное (снизу-справа) должно подойти большинству пользователей" #: e2_option__default.c:210 msgid "bold name header" msgstr "жирный текст заголовка" #: e2_option__default.c:210 msgid "colored headers" msgstr "цветной заголовок" #: e2_option__default.c:210 msgid "sensitivity" msgstr "затенение" #: e2_option__default.c:211 msgid "type of indicator for active pane" msgstr "индикатор активной панели" #: e2_option__default.c:212 msgid "" "This determines how the active pane is indicated on-screen. Some GTK themes " "do not allow column-header re-coloring" msgstr "" "Это определяет, как активная панель выделяется на экране. Некоторые GTK темы " "не позволяют изменение цвета заголовков стобцов" #: e2_option__default.c:216 msgid "banded background" msgstr "разноцветный фон" #: e2_option__default.c:217 msgid "If activated, lines in file lists will alternate background color" msgstr "Если включено, то строки в списке файлов будут разных цветов" #: e2_option__default.c:221 e2_output.c:2457 e2_view_dialog.c:1862 msgid "use custom font" msgstr "использовать особый шрифт" #: e2_option__default.c:222 e2_output.c:2458 e2_view_dialog.c:1863 msgid "" "If activated, the font specified below will be used, instead of the theme " "default" msgstr "" "Если включено, то вместо стандартного шрифта будет использоваться шрифт, " "указанный ниже" #: e2_option__default.c:225 msgid "custom font" msgstr "особый шрифт" #: e2_option__default.c:226 msgid "This is the font used for flle pane text" msgstr "Это шрифт, используемый для файловой панели" #: e2_option__default.c:229 msgid "Default: May 20 09:11" msgstr "По-умолчанию: Май 20 09:11" #: e2_option__default.c:230 msgid "American: 05/20/04 09:11" msgstr "Американский: 05/20/04 09:11" #: e2_option__default.c:230 msgid "Standard: 20/05/04 09:11" msgstr "Стандарт: 20/05/04 09:11" #: e2_option__default.c:231 msgid "LC_TIME locale specified" msgstr "определяется переменной LC_TIME" #: e2_option__default.c:232 msgid "date format" msgstr "формат даты" #: e2_option__default.c:233 msgid "This determines the format of all dates displayed in the panes" msgstr "Это определяет формат всех дат, отображаемых в панелях" #: e2_option__default.c:237 msgid "condensed" msgstr "сжатый" #: e2_option__default.c:237 msgid "exact" msgstr "точный" #: e2_option__default.c:238 msgid "size format" msgstr "формат размера" #: e2_option__default.c:239 msgid "" "Displayed item-sizes can be 'condensed' to show kB, MB where appropriate" msgstr "" "Размер элементов можно показывать в 'сжатом' виде, тоесть кБ, МБ где возможно" #: e2_option__default.c:243 msgid "show parent directory entry '..' in file lists" msgstr "показывать родительский каталог '..' в списке файлов" #: e2_option__default.c:244 msgid "This slows status-line updates" msgstr "Это замедлит обновление статусной строки" #: e2_option__default.c:246 msgid "filename sort is case sensitive" msgstr "сортировка имен файлов чувствительна к регистру" #: e2_option__default.c:247 msgid "" "This places all the capitalised file/directory names ahead of the others, if " "your LANG_C suports that" msgstr "" "Это поместит все файлы/каталоги, написанные большими буквами, в начало " "списка, если LANG_C поддерживает это" #: e2_option__default.c:250 msgid "both panes are like pane 1" msgstr "обе панели, как панель 1" #: e2_option__default.c:252 e2_option__default.c:258 #, c-format msgid "" "This makes pane %d options (other than toolbar placement and content) apply " "to pane %d" msgstr "" "Это настроит панель %d (кроме расположения панелей инструментов и их " "содержимого) также как %d" #: e2_option__default.c:256 msgid "both panes are like pane 2" msgstr "обе панели, как панель 2" #: e2_option__default.c:268 msgid "executable" msgstr "исполняемый" #: e2_option__default.c:269 msgid "Executable file names are listed in this color" msgstr "Исполняемые файлы отображаются этим цветом" #: e2_option__default.c:271 e2p_du.c:220 e2p_find.c:3018 msgid "directory" msgstr "каталог" #: e2_option__default.c:272 msgid "Directory names are listed in this color" msgstr "Каталоги отображаются этим цветом" #: e2_option__default.c:275 msgid "Symbolic link names are listed in this color" msgstr "Символические ссылки отображаются этим цветом" #: e2_option__default.c:277 msgid "device" msgstr "устройство" #: e2_option__default.c:278 msgid "Device names are listed in this color" msgstr "Устройства отображаются этим цветом" #: e2_option__default.c:280 e2p_find.c:3051 msgid "socket" msgstr "сокет" #: e2_option__default.c:281 msgid "Sockets are listed in this color" msgstr "Сокеты отображаются этим цветом" #: e2_option__default.c:286 msgid "custom background color" msgstr "свой цвет фона" #: e2_option__default.c:287 msgid "If enabled, the color specified below will be used for background" msgstr "" "Если включено, то цвет, указанный ниже, будет использоваться как фоновый" #: e2_option__default.c:290 msgid "background color" msgstr "цвет фона" #: e2_option__default.c:291 msgid "Background color used instead of theme color" msgstr "Цвет фона, исползуемый вместо цвета из темы" #: e2_option__default.c:295 msgid "active pane header color" msgstr "цвет заголовка активной панели" #: e2_option__default.c:296 msgid "" "This color is used for the background of column headers in the active pane" msgstr "Этот цвет используется для заголовков столбцов ативной панели" #: e2_option__default.c:298 msgid "highlight color" msgstr "цвет подсветки" #: e2_option__default.c:299 msgid "" "This color is used for the background of highlighed item-names and other text" msgstr "" "Этот цвет используется для подсветки названий элементов и других надписей" #: e2_option__default.c:320 e2_toolbar.c:2784 msgid "icon above label" msgstr "значки над подписями" #: e2_option__default.c:320 e2_toolbar.c:2784 msgid "icon beside label" msgstr "значки под подписями" #: e2_option__default.c:320 e2_toolbar.c:2784 msgid "icon only" msgstr "только значки" #: e2_option__default.c:320 e2_toolbar.c:2784 msgid "label only" msgstr "только подписи" #: e2_option__default.c:320 e2_option__default.c:329 e2_toolbar.c:2784 #: e2_toolbar.c:2794 msgid "theme" msgstr "определяется темой" #: e2_option__default.c:321 e2_toolbar.c:2785 #, c-format msgid "'%s' uses the Gtk default, '%s' leaves most space for other things" msgstr "" "'%s' использует настройки из темы GTK, '%s' оставляет больше места для " "других вещей" #: e2_option__default.c:323 msgid "toolbars button style" msgstr "стиль панелей инструментов" #: e2_option__default.c:330 e2_toolbar.c:2795 #, c-format msgid "'%s' uses the Gtk default, '%s' is smallest, '%s' is largest" msgstr "" "'%s' использует настройки из темы GTK, '%s' самый маленький, '%s' самый " "большой" #: e2_option__default.c:332 msgid "toolbars icon size" msgstr "размер значков на панели" #: e2_option_tree.c:163 e2_option_tree.c:408 msgid "Press key" msgstr "Нажмите клавишу" #: e2_option_tree.c:621 #, c-format msgid "select icon for %s" msgstr "выберите значёк для %s" #: e2_option_tree.c:666 #, c-format msgid "Are you sure that you want to delete this row and %d %s?" msgstr "Вы уверены, что хотите удалить этоу строку и %d %s?" #: e2_option_tree.c:670 msgid "confirm row delete" msgstr "подтверждение удаления строки" #: e2_option_tree.c:956 e2_toolbar.c:2899 e2p_acl.c:3791 e2p_find.c:3613 #: e2p_rename.c:1538 msgid "_Help" msgstr "_Справка" #: e2_option_tree.c:957 msgid "Get help on this option" msgstr "Посмотреть справку по настройке" #: e2_option_tree.c:960 msgid "_Select" msgstr "Вы_брать" #: e2_option_tree.c:961 msgid "Show plugin-selection dialog" msgstr "Показать диалог выбора модуля" #: e2_option_tree.c:964 msgid "Co_lor" msgstr "_Цвет" #: e2_option_tree.c:965 msgid "Show color-selection dialog" msgstr "Показать диалог выбора цвета" #: e2_option_tree.c:979 msgid "Remove the selected row" msgstr "Удалить выбранную строку" #: e2_option_tree.c:981 msgid "Add a row after the currently selected row in the tree" msgstr "Добавить строку после текущей выделенной строки в дереве" #: e2_option_tree.c:984 msgid "Add ch_ild" msgstr "В_ложить" #: e2_option_tree.c:985 msgid "Add a child row to the currently selected one" msgstr "Добавить вложенный элемент в текущий выбранный" #: e2_option_tree_context_menu.c:228 msgid "Cut selected row and any descendant(s)" msgstr "Вырезать текущую строку со всеми вложенными" #: e2_option_tree_context_menu.c:232 msgid "Copy selected row and any descendant(s)" msgstr "Копировать текущую строку со всеми вложенными" #: e2_option_tree_context_menu.c:244 msgid "Expand all rows on this page" msgstr "Развернуть все строки на этой странице" #: e2_option_tree_context_menu.c:248 msgid "Collapse all rows on this page" msgstr "Свернуть все строки на этой странице" #: e2_option_tree_context_menu.c:260 msgid "Add c_hild" msgstr "В_ложить" #: e2_option_tree_context_menu.c:261 msgid "Add a child to the selected row" msgstr "Добавить вложенный элемент в текущий" #: e2_output.c:548 msgid "output tabs" msgstr "вкладки вывода" #: e2_output.c:1070 #, c-format msgid "Cannot find any output from process %s" msgstr "Не могу найти вывод процесса %s" #: e2_output.c:1347 msgid "Do not show the output pane" msgstr "Не показывать панель вывода" #: e2_output.c:1351 msgid "_Toggle full" msgstr "_На весь экран" #: e2_output.c:1354 msgid "Toggle output pane size to/from the full window size" msgstr "Переключить панель вывода в/из полноэкранного режима" #: e2_output.c:1358 msgid "_New tab" msgstr "_Новая вкладка" #: e2_output.c:1359 msgid "Add another tab for the output pane" msgstr "Добавить новую вкладку к панели вывода" #: e2_output.c:1363 msgid "_Remove tab" msgstr "_Удалить вкладку" #: e2_output.c:1364 msgid "Close this this tab" msgstr "Закрыть эту вкладку" #: e2_output.c:1373 msgid "_Attach" msgstr "_Присоединить" #: e2_output.c:1374 msgid "Move this tab back to output pane" msgstr "Вернуть эту вкладку обратно в панель вывода" #: e2_output.c:1381 e2_output.c:1385 msgid "_Clear" msgstr "_Очистить" #: e2_output.c:1382 e2_output.c:1386 msgid "Clear this tab" msgstr "Очистить эту вкладку" #: e2_output.c:1395 msgid "Edit the tab contents" msgstr "Редактировать содержимое вкладки" #: e2_output.c:1417 msgid "Co_mmand output" msgstr "_Вывод команды" #: e2_output.c:1418 msgid "Show output from a completed command" msgstr "Показать вывод завершенной команды" #: e2_output.c:1431 e2_view_dialog.c:958 msgid "_Settings" msgstr "_Настройка" #: e2_output.c:1442 msgid "_Other" msgstr "_Дополнительно" #: e2_output.c:1443 msgid "Open the configuration dialog at the output options page" msgstr "Открыть диалог настройки на странице параметров вывода" #: e2_output.c:1772 msgid "-- end-of-output --" msgstr "-- конец-вывода --" #: e2_output.c:2415 msgid "show output pane when a new message appears" msgstr "показывать панель вывода когда появляется новое сообщение" #: e2_output.c:2416 msgid "This will ensure you don't miss any messages" msgstr "Это позволяет быть уверенным, что вы не пропустите ни одного сообщения" #: e2_output.c:2419 msgid "show output pane if the command line is focused" msgstr "показывать панель вывода, если командная строка активна" #: e2_output.c:2420 msgid "" "This causes the output pane to be opened when you are about enter a command" msgstr "Это заставляет панель вывода открываться когда вы набираете команду" #: e2_output.c:2424 msgid "hide output pane if the command line is unfocused" msgstr "скрывать панель вывода, если командная строка не активна" #: e2_output.c:2425 msgid "" "This causes the output pane to be closed when you move focus away from the " "command line" msgstr "" "Это заставляет панель вывода закрываться когда вы перестаёте работать с " "командной строкой" #: e2_output.c:2428 msgid "show commands" msgstr "показывать команды" #: e2_output.c:2429 msgid "This echoes the commands that are run and their exit value" msgstr "Это выводит названия запущенных команд и их возвращаемые значения" #: e2_output.c:2431 msgid "scroll to new output" msgstr "перемещаться за новыми сообщениями" #: e2_output.c:2432 msgid "" "This will automatically scroll the output pane content, to show new message" "(s)" msgstr "" "Это включает автоматическую прокрутку вниз, чтобы показывать новые сообщения" #: e2_output.c:2434 msgid "only scroll when really following" msgstr "перемещаться, только когда действительно необходимо" #: e2_output.c:2435 msgid "" "This stops automatic pane scrolling to new output if you've manually " "scrolled away" msgstr "" "Это останавливает автоматическую прокрутку вниз, когда вы прокручиваете окно " "вручную" #: e2_output.c:2438 msgid "only scroll when new output is at the end" msgstr "перемещаться только если в конце появились новые сообщения" #: e2_output.c:2439 msgid "" "This stops pane scrolling if a different process has displayed text after " "the current insert position" msgstr "" "Это останавливает прокрутку, если другой процесс выводит текст после текущей " "позиции вставки" #: e2_output.c:2442 msgid "everywhere" msgstr "в любом месте" #: e2_output.c:2442 msgid "words" msgstr "по словам" #: e2_output.c:2444 msgid "line wrap mode" msgstr "режим переноса строк" #: e2_output.c:2445 msgid "" "If mode is 'none', a horizontal scrollbar will be available. Mode 'words' " "will only break the line between words" msgstr "" "Если включен режим 'нет', то будет доступна горизонтальня линия прокрутки. В " "режиме 'слова' строки будут разбиваться между словами" #: e2_output.c:2448 msgid "left margin (pixels)" msgstr "отступ слева (пиксели)" #: e2_output.c:2449 msgid "This is the left margin between the output-pane edge and the text in it" msgstr "Это отступ слева между границей окна вывода и текстом в нем" #: e2_output.c:2452 msgid "right margin (pixels)" msgstr "отступ справа (пиксели)" #: e2_output.c:2453 msgid "" "This is the right margin between the output-pane edge and the text in it" msgstr "Это отступ справа между границей окна вывода и текстом в нем" #: e2_output.c:2461 msgid "custom output font" msgstr "особый шрифт" #: e2_output.c:2462 msgid "This is the font used for text in the output pane" msgstr "Это шрифт используемый для текста в окне вывода" #: e2_output.c:2467 msgid "positive color" msgstr "положительный цвет" #: e2_output.c:2468 msgid "" "This color is used for messages about successful operations or other " "'positive events'" msgstr "" "Этот цвет используется для сообщения об успешных операциях или о других " "'положительных событиях'" #: e2_output.c:2471 msgid "negative color" msgstr "отрицательный цвет" #: e2_output.c:2472 msgid "" "This color is used for messages about unsuccessful operations or other " "'negative events'" msgstr "" "Этот цвет используется для сообщений о неуспешных операциях или о других " "'отрицательных событиях'" #: e2_output.c:2475 msgid "unimportant color" msgstr "нейтральный цвет" #: e2_output.c:2476 msgid "" "This color is used for messages of minor importance or other miscellaneous " "events" msgstr "Этот цвет используется для сообщений о маловажных событиях" #: e2_ownership_dialog.c:167 msgid "You are not authorised to make any change." msgstr "Вам не позволено вносить какие-либо изменения." #: e2_ownership_dialog.c:209 msgid "ownership" msgstr "владелец" #: e2_ownership_dialog.c:217 e2_permissions_dialog.c:327 e2p_acl.c:3467 #: e2p_times.c:697 msgid "Directory name" msgstr "Имя каталога" #: e2_ownership_dialog.c:233 msgid "Permissions: " msgstr "Права: " #: e2_ownership_dialog.c:245 msgid "User: " msgstr "Владелец: " #: e2_ownership_dialog.c:251 msgid "Group: " msgstr "Группа: " #: e2_ownership_dialog.c:375 msgid "Recurse" msgstr "Рекурсивно" #: e2_pane.c:883 msgid "No trash directory is available" msgstr "Каталог корзины недоступен" #: e2_pane.c:985 msgid "_Edit places" msgstr "_Редактировать места" #: e2_pane.c:1584 #, c-format msgid "show %s column" msgstr "показывать столбец %s" #: e2_pane.c:1585 #, c-format msgid "This causes the the named column to be displayed in pane %d (duh ...)" msgstr "Это включает показ указанного столбца в панели %d (duh ...)" #: e2_pane.c:1615 #, c-format msgid "At startup, show a specific directory in pane %d" msgstr "при запуске открывать указанный каталог в панели %d" #: e2_pane.c:1617 msgid "" "This causes the directory named below, instead of the last-opened directory, " "to be shown at session start" msgstr "" "Это позволяет при запуске программы открывать каталог, укажанный ниже, " "вместо последнего использовавшегося" #: e2_pane.c:1622 #, c-format msgid "pane %d startup directory:" msgstr "начальный каталог для панели %d:" #: e2_pane.c:1624 msgid "This is the directory to show in this pane, at session start" msgstr "" "Это каталог, который следует открыть в этой панели при запуске программы" #: e2_password_dialog.c:113 e2p_crypt.c:2922 msgid "Enter password:" msgstr "Введите пароль:" #: e2_password_dialog.c:191 e2p_crypt.c:2934 msgid "Confirm password:" msgstr "Подтвердите пароль:" #: e2_password_dialog.c:249 msgid "Entered passwords are different" msgstr "Введенные пароли различаются" #: e2_permissions_dialog.c:337 e2_permissions_dialog.c:400 #: e2_vfs_dialog.c:1430 e2p_acl.c:229 e2p_acl.c:2090 e2p_acl.c:3477 #: e2p_gvfs.c:647 msgid "User" msgstr "Владелец" #: e2_permissions_dialog.c:379 msgid "currently" msgstr "текущий" #: e2_permissions_dialog.c:395 e2p_acl.c:222 msgid "Read" msgstr "Чтение" #: e2_permissions_dialog.c:396 e2p_acl.c:222 msgid "Write" msgstr "Запись" #: e2_permissions_dialog.c:397 e2p_acl.c:222 msgid "Exec" msgstr "Исполнение" #: e2_permissions_dialog.c:398 msgid "Special" msgstr "Специальные" #: e2_permissions_dialog.c:407 msgid "Set UID" msgstr "Установить UID" #: e2_permissions_dialog.c:417 msgid "Set GID" msgstr "Установить GID" #: e2_permissions_dialog.c:420 e2p_acl.c:229 e2p_acl.c:2099 msgid "Other" msgstr "Другие" #: e2_permissions_dialog.c:427 msgid "Sticky" msgstr "Sticky" #: e2_permissions_dialog.c:440 e2p_acl.c:3704 msgid "Action:" msgstr "Действие:" #: e2_permissions_dialog.c:450 e2p_acl.c:3722 e2p_times.c:751 e2p_times.c:753 #: e2p_times.c:755 msgid "_Set" msgstr "_Установить" #: e2_permissions_dialog.c:466 e2p_acl.c:3746 e2p_times.c:757 msgid "R_ecurse:" msgstr "_Рекурсивно:" #: e2_permissions_dialog.c:475 e2p_acl.c:3754 msgid "" "If activated, changes will be applied to selected items and also their " "descendents, if such items match your choice of \"directories\" and/or " "\"others\" (anything not a directory)" msgstr "" "Если включено, то изменения будут применены к выбранным элементам и ко всем " "вложениям, если эти элементы совпадают с вашим выбором \"каталоги\" и/или " "\"остальные\" (все кроме каталогов)" #: e2_permissions_dialog.c:478 e2p_acl.c:3757 msgid "d_irectories" msgstr "_каталоги" #: e2_permissions_dialog.c:481 e2p_acl.c:3760 msgid "o_thers" msgstr "_остальные" #: e2_plugins.c:299 msgid "Cannot unload plugin" msgstr "Не могу выгрузить модуль" #: e2_plugins.c:738 msgid "Plugin not loaded" msgstr "Модуль не загружен" #: e2_plugins.c:1274 msgid "Loaded" msgstr "Загружен" #: e2_select_image_dialog.c:607 #, fuzzy msgid "Choose icons directory" msgstr "использовать каталог значков" #: e2_select_image_dialog.c:627 msgid "_other" msgstr "_Дополнительно" #: e2_select_image_dialog.c:631 msgid "_stock" msgstr "_сокет" #: e2_select_image_dialog.c:646 msgid "Remove the current icon" msgstr "Удалить текущий значёк" #: e2_size_filter_dialog.c:145 msgid "Display only the items which are:" msgstr "Показывать только элементы, которые:" #: e2_size_filter_dialog.c:146 msgid "size filter" msgstr "фильтр по размеру" #: e2_size_filter_dialog.c:153 e2p_glob.c:460 msgid "bigger than" msgstr "больше чем" #: e2_size_filter_dialog.c:153 e2p_glob.c:460 msgid "equal to" msgstr "равен" #: e2_size_filter_dialog.c:153 e2p_glob.c:460 msgid "smaller than" msgstr "меньше чем" #: e2_size_filter_dialog.c:192 e2p_find.c:2785 e2p_glob.c:501 msgid "Mbytes" msgstr "Мбайт" #: e2_size_filter_dialog.c:192 e2p_find.c:2783 e2p_glob.c:501 msgid "kbytes" msgstr "Кбайт" #: e2_task.c:516 #, c-format msgid "%s added to tasks queue" msgstr "%s добавлен в список задач" #: e2_task.c:784 #, c-format msgid "%s operation incomplete - time limit exceeded" msgstr "операция %s не завершена - превышен лимит времени" #: e2_task.c:868 #, c-format msgid "The current operation (%s)" msgstr "Текущая операция (%s)" #: e2_task.c:870 msgid "operation" msgstr "операция" #: e2_task.c:1039 msgid "File operation in progress" msgstr "Идёт файловая операция" #: e2_task.c:1184 msgid "Operation not permitted - Circular directories" msgstr "Операция невозможна - кольцевые директории" #: e2_task.c:1416 e2_task.c:1742 e2_task.c:1985 e2_task.c:2352 e2p_clone.c:78 msgid "Enter new name for" msgstr "Введите новое имя для" #: e2_task.c:1991 msgid "link" msgstr "ссылка" #: e2_task.c:2113 #, c-format msgid "Are you sure you want to delete %s?" msgstr "Вы уверены, что хотите удалить %s?" #: e2_task.c:2444 e2p_rename.c:1017 e2p_rename.c:1019 #, c-format msgid "Cannot rename %s" msgstr "Не могу переименовать %s" #: e2_task.c:2582 e2p_acl.c:4165 #, c-format msgid "You do not have authority to change permission(s) of %s" msgstr "У вас недостаточно привелегий, чтобы изменить права %s" #: e2_task.c:2740 #, c-format msgid "You do not have authority to change owner(s) of %s" msgstr "У вас недостаточно привелегий, чтобы изменить владельца %s" #: e2_task.c:3248 msgid "open with" msgstr "открыть с помощью" #: e2_task.c:3249 e2_toolbar.c:2893 msgid "Enter command:" msgstr "Введите команду:" #: e2_task.c:3420 msgid "Enter a name or partial name to find:" msgstr "Введите имя, или часть имени файла который надо найти:" #: e2_task_backend.c:330 #, c-format msgid "Cannot change ownership of %s" msgstr "Не могу изменить владельца %s" #: e2_task_backend.c:490 e2_task_backend.c:506 e2_task_backend.c:1613 #, c-format msgid "Cannot delete %s" msgstr "Не могу удалить %s" #: e2_task_backend.c:663 e2_task_backend.c:1578 #, c-format msgid "Cannot delete existing %s" msgstr "Не могу удалить существующий %s" #: e2_task_backend.c:722 #, c-format msgid "Cannot create special file %s" msgstr "Не могу создать специальный файл %s" #: e2_task_backend.c:737 #, c-format msgid "Cannot create FIFO %s" msgstr "Не могу создать FIFO %s" #: e2_task_backend.c:874 #, c-format msgid "Cannot create new link to %s" msgstr "Не могу создать новую ссылку на %s" #: e2_task_backend.c:885 #, c-format msgid "Cannot get target info for link %s" msgstr "Не могу получить иформацию о ссылке %s" #: e2_task_backend.c:1357 #, c-format msgid "Cannot copy all of %s" msgstr "Не могу ничего скопировать из %s" #: e2_task_backend.c:1378 msgid "incomplete" msgstr "незавершено" #: e2_task_backend.c:1463 #, c-format msgid "Cannot move %s to %s" msgstr "Не могу переместить %s в %s" #: e2_task_backend.c:1518 #, c-format msgid "Cannot create link to %s" msgstr "Не могу создать ссылку на %s" #: e2_task_backend.c:1566 #, c-format msgid "Cannot rename %s to %s" msgstr "Не могу переименовать %s в %s" #: e2_task_backend.c:1648 e2_task_backend.c:1764 e2p_acl.c:2826 #: e2p_crypt.c:1773 e2p_times.c:319 #, c-format msgid "Cannot get current data for %s" msgstr "Не могу получить текущую дату для %s" #: e2_task_backend.c:1699 e2p_acl.c:1870 #, c-format msgid "Cannot change permission(s) of all of %s" msgstr "Не могу изменить права у %s" #: e2_task_backend.c:1718 #, c-format msgid "Cannot get current permissions of %s" msgstr "Не могу получить текущие права для %s" #: e2_task_backend.c:1834 #, c-format msgid "Cannot change ownership of all of %s" msgstr "Не могу изменить владельца у %s" #: e2_toolbar.c:357 msgid "Show _bar" msgstr "Показывать п_анель" #: e2_toolbar.c:359 msgid "Show _tooltips" msgstr "Показывать п_одсказки" #: e2_toolbar.c:361 msgid "Space _handling" msgstr "Управление _местом" #: e2_toolbar.c:364 msgid "_none" msgstr "_нет" #: e2_toolbar.c:366 msgid "_scrollbars" msgstr "_полосы прокрутки" #: e2_toolbar.c:368 msgid "_rest button" msgstr "_специальная кнопка" #: e2_toolbar.c:370 msgid "_Placement" msgstr "_Расположение" #: e2_toolbar.c:371 msgid "_horizontal" msgstr "_горизонтально" #: e2_toolbar.c:373 msgid "_Container" msgstr "_Место" #: e2_toolbar.c:376 msgid "_main window" msgstr "_главное окно" #: e2_toolbar.c:378 msgid "_both panes" msgstr "_обе панели" #: e2_toolbar.c:380 msgid "file-pane _1" msgstr "панель _1" #: e2_toolbar.c:382 msgid "flle-pane _2" msgstr "панель _2" #: e2_toolbar.c:392 msgid "_Left" msgstr "_Слева" #: e2_toolbar.c:394 msgid "_Right" msgstr "_Справа" #: e2_toolbar.c:397 msgid "_Reset order" msgstr "По-_умолчанию" #: e2_toolbar.c:399 msgid "Buttons _relief" msgstr "_Объемные кнопки" #: e2_toolbar.c:401 msgid "Buttons _same size" msgstr "Кнопки одного _размера" #: e2_toolbar.c:403 msgid "_Button style" msgstr "_Стиль кнопок" #: e2_toolbar.c:407 e2_toolbar.c:421 msgid "_theme" msgstr "_определено темой" #: e2_toolbar.c:409 msgid "icon _only" msgstr "только _значки" #: e2_toolbar.c:411 msgid "_label only" msgstr "только _подписи" #: e2_toolbar.c:413 msgid "icon _above label" msgstr "значки _над подписями" #: e2_toolbar.c:415 msgid "icon _beside label" msgstr "значки _под подписями" #: e2_toolbar.c:417 msgid "_Icon size" msgstr "_Размер значков" #: e2_toolbar.c:423 msgid "_menu" msgstr "_меню" #: e2_toolbar.c:425 msgid "toolbar _small" msgstr "_маленькая панель" #: e2_toolbar.c:427 msgid "toolbar _large" msgstr "_большая панель" #: e2_toolbar.c:429 msgid "_button" msgstr "_кнопка" #: e2_toolbar.c:431 msgid "drag '_n' drop" msgstr "drag '_n' drop" #: e2_toolbar.c:433 msgid "_dialog" msgstr "_диалог" #: e2_toolbar.c:436 msgid "Bar i_tems" msgstr "_Настроить кнопки" #: e2_toolbar.c:719 msgid "_Rest" msgstr "_Остальное" #: e2_toolbar.c:720 msgid "Show a menu of hidden items" msgstr "Показать меню скрытых элементов" #: e2_toolbar.c:1603 msgid "not" msgstr "нет" #: e2_toolbar.c:1634 msgid "accessed" msgstr "считан" #: e2_toolbar.c:1634 msgid "changed" msgstr "создан" #: e2_toolbar.c:1634 msgid "modified" msgstr "изменён" #: e2_toolbar.c:2712 msgid "show the " msgstr "показывать " #: e2_toolbar.c:2713 #, c-format msgid "This determines whether the %s is displayed or hidden" msgstr "Это определяет, показывается %s, или нет" #: e2_toolbar.c:2720 #, c-format msgid "show tooltips for %s buttons" msgstr "показывать подсказки для кнопок на %s" #: e2_toolbar.c:2721 #, c-format msgid "If deactivated, tooltips will not be displayed for %s buttons" msgstr "Если выключено, то подсказки не будут показываться для кнопок на %s" #: e2_toolbar.c:2727 #, c-format msgid "" "This determines the method for accessing %s elements that are hidden due to " "lack of screen-space" msgstr "" "Это определяет метод доступа к элементам %s которые скрыты из-за нехватки " "места на экране" #: e2_toolbar.c:2729 msgid "use rest button" msgstr "использовать специальную кнопку" #: e2_toolbar.c:2729 msgid "use scrollbars" msgstr "использовать прокрутку" #: e2_toolbar.c:2730 msgid "space handling" msgstr "управление местом" #: e2_toolbar.c:2737 msgid " container" msgstr " размещение" #: e2_toolbar.c:2738 #, c-format msgid "This determines the 'box' into which the %s is placed" msgstr "Это определяет 'место' где будет размещена %s" #: e2_toolbar.c:2740 msgid "both panes" msgstr "обе панели" #: e2_toolbar.c:2740 msgid "file-pane 1" msgstr "панель 1" #: e2_toolbar.c:2740 msgid "file-pane 2" msgstr "панель 2" #: e2_toolbar.c:2740 msgid "main window" msgstr "главное окно" #: e2_toolbar.c:2748 msgid " horizontal" msgstr " горизонтально" #: e2_toolbar.c:2749 #, c-format msgid "This determines whether the %s is displayed horizontally or vertically" msgstr "Это определяет, будет ли %s отображаться горизонтально или вертикально" #: e2_toolbar.c:2756 msgid " priority" msgstr " приоритет" #: e2_toolbar.c:2757 msgid "This determines the order of toolbars (0 = left or top)" msgstr "Это определяет порядок панелей инструментов (0 = слева или вверху)" #: e2_toolbar.c:2766 msgid " buttons have relief" msgstr " объёмные кнопки" #: e2_toolbar.c:2768 msgid "" "Buttons with relief show their outline continually, not just when 'moused'" msgstr "" "Объёмные кнопки отображаются приподнятыми всегда, а не только когда выделены " "мышью" #: e2_toolbar.c:2775 msgid " buttons are the same size" msgstr " все кнопки одного размера" #: e2_toolbar.c:2777 msgid "" "Equal-width buttons look good on a vertical bar, bad on a horizontal bar" msgstr "" "Одинаковые по размеру кнопки выглядят хорошо на вертикальной панели, но " "плохо на горизонтальной" #: e2_toolbar.c:2787 msgid "button style" msgstr "стиль кнопок" #: e2_toolbar.c:2797 msgid "icon size" msgstr "размер значков" #: e2_toolbar.c:2817 msgid "Copy item(s) selected in the active pane to the other one" msgstr "Копировать элементы выбранные в активной панели в другую панель" #: e2_toolbar.c:2819 msgid "Move item(s) selected in the active pane to the other one" msgstr "Переместить элементы выбранные в активной панели в другую панель" #: e2_toolbar.c:2821 msgid "Symlink item(s) selected in the active pane to the other one" msgstr "" "Создать символическую ссылку элемента из активной панели в другой панели" #: e2_toolbar.c:2822 msgid "Re_name.." msgstr "Пере_именовать.." #: e2_toolbar.c:2823 msgid "Rename item(s) selected in the active pane" msgstr "Переименовать элементы выбранные в активной панели" #: e2_toolbar.c:2825 msgid "Move item(s) selected in the active pane to a trashbin" msgstr "Переместить элементы выбранные в активной панели в корзину" #: e2_toolbar.c:2826 msgid "Create new directory(ies)" msgstr "Создать новый каталог" #: e2_toolbar.c:2832 msgid "Re_fresh" msgstr "_Обновить" #: e2_toolbar.c:2832 msgid "Update pane contents" msgstr "Обновить содержимое панели" #: e2_toolbar.c:2833 msgid "_Switch" msgstr "_Переключить" #: e2_toolbar.c:2834 msgid "Toggle the active pane" msgstr "Переключить активную панель" #: e2_toolbar.c:2880 msgid "Full" msgstr "На веь экран" #: e2_toolbar.c:2880 msgid "Maximize output pane" msgstr "Развернуть панель вывода на весь экран" #: e2_toolbar.c:2881 msgid "Shrink" msgstr "Уменьшить" #: e2_toolbar.c:2881 msgid "Un-maximize output pane" msgstr "Уменьшить панель вывода до нормального размера" #: e2_toolbar.c:2882 msgid "Hide" msgstr "Скрыть" #: e2_toolbar.c:2882 msgid "Hide output pane" msgstr "Скрыть панель вывода" #: e2_toolbar.c:2883 msgid "Show" msgstr "Показать" #: e2_toolbar.c:2883 msgid "Show output pane" msgstr "Показать панель вывода" #: e2_toolbar.c:2884 msgid "Clear" msgstr "Очистить" #: e2_toolbar.c:2884 msgid "Clear output pane" msgstr "Очистить панель вывода" #: e2_toolbar.c:2886 msgid "Clear command line" msgstr "Очистить командную строку" #: e2_toolbar.c:2886 msgid "cl" msgstr "cl" #: e2_toolbar.c:2887 msgid "Child processes" msgstr "Дочерние процессы" #: e2_toolbar.c:2887 msgid "ps" msgstr "ps" #: e2_toolbar.c:2888 msgid "Calculate disk usage of selected item(s)" msgstr "Подсчитать объем, занимаемый выбранными элементами" #: e2_toolbar.c:2888 e2p_du.c:267 msgid "du" msgstr "du" #: e2_toolbar.c:2889 msgid "Find item in active pane, by name" msgstr "Найти элемент в активной панели, по названию" #: e2_toolbar.c:2890 msgid "Open terminal at the active directory" msgstr "Открыть терминал в активной панели" #: e2_toolbar.c:2890 msgid "_X" msgstr "_X" #: e2_toolbar.c:2893 msgid "Run something as root" msgstr "Запустить команду от имени root" #: e2_toolbar.c:2893 msgid "su.." msgstr "su.." #: e2_toolbar.c:2895 msgid "Mount or unmount a device" msgstr "Монтировать или отмонтировать устройство" #: e2_toolbar.c:2895 msgid "mts.." msgstr "mts.." #: e2_toolbar.c:2898 msgid "View/change configuration settings for this program" msgstr "Просмотреть/Изменить настройки для этой программы" #: e2_toolbar.c:2898 msgid "_Settings.." msgstr "_Настройки.." #: e2_toolbar.c:2899 msgid "Get information about this program" msgstr "Просмотр информации о программе" #: e2_toolbar.c:2901 msgid "Close this program" msgstr "Выйти из программы" #: e2_toolbar.c:2901 msgid "_Quit" msgstr "_Выход" #: e2_toolbar.c:2950 e2_toolbar.c:3015 msgid "Hide other pane" msgstr "Скрыть другую панель" #: e2_toolbar.c:2950 e2_toolbar.c:2952 e2_toolbar.c:2955 e2_toolbar.c:3015 #: e2_toolbar.c:3017 e2_toolbar.c:3020 msgid "_Panes" msgstr "_Панели" #: e2_toolbar.c:2953 e2_toolbar.c:2956 e2_toolbar.c:3018 e2_toolbar.c:3021 msgid "Show other pane" msgstr "Показать другую панель" #: e2_toolbar.c:2958 e2_toolbar.c:3023 msgid "Show _hidden" msgstr "Показать _скрытое" #: e2_toolbar.c:2959 e2_toolbar.c:3024 msgid "Display hidden items in this directory" msgstr "Показать скрытые элементы в этом каталоге" #: e2_toolbar.c:2960 e2_toolbar.c:3025 msgid "Hide _hidden" msgstr "Скрыть _скрытое" #: e2_toolbar.c:2961 e2_toolbar.c:3026 msgid "Do not display hidden items in this directory" msgstr "Не показывать скрытые элементы в этом каталоге" #: e2_toolbar.c:2962 e2_toolbar.c:2964 e2_toolbar.c:3027 e2_toolbar.c:3029 msgid "Fil_ters" msgstr "_Фильтры" #: e2_toolbar.c:2963 e2_toolbar.c:3028 msgid "Set rules for the items to be displayed" msgstr "Установить правила, какие элементы показывать" #: e2_toolbar.c:2965 e2_toolbar.c:3030 msgid "Set/remove rules for the items to be displayed" msgstr "Установить/убрать правила, какие элементы показывать" #: e2_toolbar.c:2967 e2_toolbar.c:3032 msgid "_VFS" msgstr "_VFS" #: e2_toolbar.c:2968 e2_toolbar.c:3033 msgid "Show a virtual directory in this pane" msgstr "Показать виртуальный каталог в этой панели" #: e2_toolbar.c:2969 e2_toolbar.c:3034 msgid "_LocalFS" msgstr "_LocalFS" #: e2_toolbar.c:2977 e2_toolbar.c:3042 msgid "_Marks" msgstr "_Метки" #: e2_toolbar.c:2978 e2_toolbar.c:3043 msgid "Bookmarks" msgstr "Закладки" #: e2_toolbar.c:2980 e2_toolbar.c:3045 msgid "Add the current directory to the top of the bookmarks list" msgstr "Добавить этот каталог в начало списка закладок" #: e2_toolbar.c:2984 e2_toolbar.c:3049 msgid "Add the current directory to the bottom of the bookmarks list" msgstr "Добавить этот каталог в конец списка закладок" #: e2_toolbar.c:2987 e2_toolbar.c:3052 msgid "_Edit bookmarks" msgstr "_Настроить закладки" #: e2_toolbar.c:2988 e2_toolbar.c:3053 msgid "Open the bookmarks configuration dialog" msgstr "Открыть диалог настройки закладок" #: e2_toolbar.c:2991 e2_toolbar.c:2994 e2_toolbar.c:3056 e2_toolbar.c:3062 msgid "Mi_rror" msgstr "_Дублировать" #: e2_toolbar.c:2992 e2_toolbar.c:2995 e2_toolbar.c:3057 e2_toolbar.c:3063 msgid "Go to directory shown in other pane" msgstr "Перейти в каталог, показанный в другой панели" #: e2_toolbar.c:2997 e2_toolbar.c:3058 e2_toolbar.c:3066 msgid "Go to previous directory in history" msgstr "Перейти в предыдущий каталог в истории" #: e2_toolbar.c:2997 e2_toolbar.c:3058 e2_toolbar.c:3066 msgid "_Back" msgstr "_Назад" #: e2_toolbar.c:2998 e2_toolbar.c:3059 e2_toolbar.c:3065 msgid "Go up to parent directory" msgstr "Перейти в родительский каталог" #: e2_toolbar.c:2999 e2_toolbar.c:3060 e2_toolbar.c:3064 msgid "Go to next directory in history" msgstr "Перейти в следующий каталог в истории" #: e2_toolbar.c:2999 e2_toolbar.c:3060 e2_toolbar.c:3064 msgid "_Forward" msgstr "_Вперед" #: e2_toolbar.c:3079 msgid "bar" msgstr "кнопки" #: e2_tree_dialog.c:1109 msgid "Copy selected path" msgstr "Копировать выбранный путь" #: e2_tree_dialog.c:1113 msgid "Collapse all paths" msgstr "Свернуть все пути" #: e2_tree_dialog.c:1118 msgid "_Strict hiding" msgstr "_Точное скрытие" #: e2_tree_dialog.c:1127 msgid "" "If active, hidden ancestors of visible directories will not be displayed" msgstr "" "Если включено, скрытые дочерние каталоги видимых каталогов не будут " "показываться" #: e2_tree_dialog.c:1388 msgid "pane 1 navigator" msgstr "навигатор панели 1" #: e2_tree_dialog.c:1388 msgid "pane 2 navigator" msgstr "навигатор панели 2" #: e2_tree_dialog.c:1460 msgid "Toggle display of hidden directories" msgstr "Включить/Выключить показ скрытых каталогов" #: e2_utils.c:109 msgid "Not enough memory! Things may not work as expected" msgstr "" "Недостаточно памяти! Некоторые вещи могут работать не так, как вы ожидаете" #: e2_utils.c:159 msgid "Cannot read USAGE help document" msgstr "Не могу прочитать файл помощи USAGE" #: e2_utils.c:845 msgid "No item selected" msgstr "Не выбраны элементы" #: e2_utils.c:888 msgid "No item selected in other pane" msgstr "Не выбраны элементы в другой панели" #: e2_utils.c:978 msgid "No matching '}' found in action text" msgstr "Не найдено соответствующей '}' в тексте действия" #: e2_utils.c:1070 msgid "No matching '$' found in action text" msgstr "Не найдено соответствующего '$' в тексте действия" #: e2_utils.c:1312 #, c-format msgid "Cannot access %s, going to %s instead" msgstr "Не доступен %s, вместо этого перехожу в %s" #: e2_vfs_dialog.c:563 msgid "You must specify the site to open" msgstr "Вам необходимо указать сервер для открытия" #: e2_vfs_dialog.c:598 #, c-format msgid "Cannot open '%s'" msgstr "Не могу открыть '%s'" #: e2_vfs_dialog.c:685 msgid "vfs data" msgstr "данные vfs" #: e2_vfs_dialog.c:708 msgid "You may provide a short-name for use in labels and matching" msgstr "Вы можете ввести короткое имя для использования в названиях" #: e2_vfs_dialog.c:721 msgid "protocol" msgstr "протокол" #: e2_vfs_dialog.c:726 e2_vfs_dialog.c:732 e2p_find.c:2999 msgid "type" msgstr "тип" #: e2_vfs_dialog.c:761 msgid "host name" msgstr "имя сервера" #: e2_vfs_dialog.c:761 msgid "parent identifier" msgstr "идентификатор родителя" #: e2_vfs_dialog.c:767 msgid "Domain name of the site e.g. your.computer.net" msgstr "Доменное имя узла, например, your.computer.net" #: e2_vfs_dialog.c:768 msgid "Full URI for the parent space, if any" msgstr "Полный URI родительского места, если есть" #: e2_vfs_dialog.c:788 msgid "path to archive" msgstr "путь к архиву" #: e2_vfs_dialog.c:794 msgid "initial directory" msgstr "начальный каталог" #: e2_vfs_dialog.c:861 msgid "Absolute path of directory at the site to be initially displayed" msgstr "Абсолютный путь к каталогу на сервере, который будет показан при входе" #: e2_vfs_dialog.c:865 msgid "Absolute path of archive in this space, including archoive's full name" msgstr "Абсолютный путь к архиву в этом месте, вместе с его именем" #: e2_vfs_dialog.c:870 msgid "Path to be initially displayed, if any" msgstr "Путь, который должен быть показан при входе" #: e2_vfs_dialog.c:894 msgid "user" msgstr "пользователь" #: e2_vfs_dialog.c:905 msgid "" "Username needed to log on to the specified site, or leave blank for anonymous" msgstr "" "Имя пользователя необходимое для входа на указанный сервер; оставьте его " "пустым для анонимного входа" #: e2_vfs_dialog.c:920 msgid "password" msgstr "пароль" #: e2_vfs_dialog.c:930 msgid "" "Password needed to log on to the specified site, or leave blank for email " "address" msgstr "" "Пароль необходимый для входа на указанный сервер; оставьтеего пустым для " "анонимного входа" #: e2_vfs_dialog.c:934 msgid "Password, if any, needed to open the archive" msgstr "Пароль, если необходим, для доступа к архиву" #: e2_vfs_dialog.c:963 msgid "port" msgstr "порт" #: e2_vfs_dialog.c:973 msgid "Port number to access the specified site, or leave blank for default" msgstr "" "Номер порта для доступа к указанному серверу; оставьте пустым для значения " "по-умолчанию" #: e2_vfs_dialog.c:1005 msgid "_Previous" msgstr "_Предыдущий" #: e2_vfs_dialog.c:1006 msgid "Show data for previously-used place with same protocol/type" msgstr "Показать данные для предущего места с таким же протоколом" #: e2_vfs_dialog.c:1009 msgid "_Next" msgstr "_Следующий" #: e2_vfs_dialog.c:1010 msgid "Show data for next-used place with same protocol/type" msgstr "Показать данные для следующего места с таким же протоколом" #: e2_vfs_dialog.c:1019 e2_vfs_dialog.c:1563 msgid "_Local" msgstr "_Локально" #: e2_vfs_dialog.c:1020 e2_vfs_dialog.c:1567 msgid "Revert to native filesystem" msgstr "Вернуться к родной файловой системе" #: e2_vfs_dialog.c:1026 msgid "Store this data but do not open the place" msgstr "Сохранить эти данные но не открывать место" #: e2_vfs_dialog.c:1030 msgid "Open this place" msgstr "Открыть это место" #: e2_vfs_dialog.c:1413 msgid "vfs history" msgstr "история vfs" #: e2_vfs_dialog.c:1427 msgid "Protocol" msgstr "Протокол" #: e2_vfs_dialog.c:1428 msgid "Host" msgstr "Сервер" #: e2_vfs_dialog.c:1431 e2p_gvfs.c:652 msgid "Password" msgstr "Пароль" #: e2_vfs_dialog.c:1432 msgid "Port" msgstr "Порт" #: e2_vfs_dialog.c:1433 msgid "Alias" msgstr "Псевдоним" #: e2_vfs_dialog.c:1553 msgid "Delete the selected row" msgstr "Удалить выбранную строку" #: e2_vfs_dialog.c:1561 msgid "Add a row after the selected one" msgstr "Добавить строку после текушей" #: e2_vfs_dialog.c:1574 msgid "Open the place described in the selected row" msgstr "Открыть место, описанное в выбранной строке" #: e2_vfs_dialog.c:1580 msgid "Save and close" msgstr "Сохранить и закрыть" #: e2_view_dialog.c:401 #, c-format msgid "Cannot read '%s'" msgstr "Не могу прочитать '%s'" #: e2_view_dialog.c:417 #, c-format msgid "'%s' is a directory" msgstr "'%s' является каталогом" #: e2_view_dialog.c:468 #, c-format msgid "Encoding conversion command '%s' failed" msgstr "Команда преобразования кодировки '%s' не выполнена" #: e2_view_dialog.c:508 #, c-format msgid "Conversion from %s encoding failed: \"%s\"" msgstr "Преобразование кодировки из '%s' не выполнено: \"%s\"" #: e2_view_dialog.c:947 msgid "Copy selected text" msgstr "Копировать выделенный текст" #: e2_view_dialog.c:1004 msgid "Finds" msgstr "Поиск" #: e2_view_dialog.c:1018 msgid "If activated, text case does matter when searching" msgstr "Если включено, то при поиске будет учитываться регистр букв" #: e2_view_dialog.c:1018 msgid "_match case" msgstr "_учитывать регистр" #: e2_view_dialog.c:1023 msgid "If activated, matches must be surrounded by word-separator characters" msgstr "" "Если включено, то искомые слова должны быть окружены сивмолами-разделителями" #: e2_view_dialog.c:1023 msgid "wh_ole words" msgstr "слова _целиком" #: e2_view_dialog.c:1027 msgid "If activated, searching proceeds toward document start" msgstr "Если включено, то поиск будет идти к началу документа" #: e2_view_dialog.c:1027 msgid "_backward" msgstr "_назад" #: e2_view_dialog.c:1030 msgid "If activated, searching cycles from either end to the other" msgstr "Если включено, то поиск будет повторяться по кругу" #: e2_view_dialog.c:1030 msgid "_loop" msgstr "в _цикле" #: e2_view_dialog.c:1518 msgid "displaying file" msgstr "просмотр файла" #: e2_view_dialog.c:1592 msgid "_wrap" msgstr "_перенос строк" #: e2_view_dialog.c:1605 msgid "Show the search options bar" msgstr "Показать панель поиска" #: e2_view_dialog.c:1848 msgid "wrap text" msgstr "переносить строки" #: e2_view_dialog.c:1849 msgid "This causes the view window to open with text-wrapping enabled" msgstr "Это включает перенос строк при открытии окна просмотра файла" #: e2_view_dialog.c:1853 msgid "window width" msgstr "ширина окна" #: e2_view_dialog.c:1854 msgid "" "The view window will default to showing this many characters per line (but " "the the displayed buttons may make it wider than this)" msgstr "" "Это количество символов в строке, которое окно просмотра будет показывать по " "умолчанию (но кнопки в окне могут сделать его шире)" #: e2_view_dialog.c:1858 msgid "window height" msgstr "высота окна" #: e2_view_dialog.c:1859 msgid "The view window will default to showing this many lines of text" msgstr "" "Это количество строк, которое окно просмотра будет показывать по умолчанию" #: e2_view_dialog.c:1866 msgid "custom font for viewing files" msgstr "особый шрифт для просмотра файлов" #: e2_view_dialog.c:1867 msgid "This is the font used for text in each view dialog" msgstr "Это шрифт используемый для текста в окне просмотра файлов" #: e2_view_dialog.c:1870 msgid "case sensitive searches" msgstr "поиск с учётом регистра" #: e2_view_dialog.c:1871 msgid "" "This causes the view window search-bar to first open with case-sensitive " "searching enabled" msgstr "Это включает поиск с учетом регистра в окне просмотра" #: e2_view_dialog.c:1875 msgid "show last search string" msgstr "показывать последнюю строку поиска" #: e2_view_dialog.c:1876 msgid "" "This shows the last search-string in the entry field, when the view window " "search-bar is displayed" msgstr "" "Это включает показ предыдущей строки поиска, при отображении окна просмотра " "файла" #: e2_view_dialog.c:1880 msgid "keep search history" msgstr "хранить историю поисков" #: e2_view_dialog.c:1881 msgid "This causes search strings to be remembered between sessions" msgstr "Это включает запоминание строк поиска между запусками программы" #: e2_window.c:1148 #, c-format msgid "displayed & %d concealed " msgstr "показанных и %d скрытых " #: e2_window.c:1151 #, c-format msgid "%s%d selected item(s) of %d %sin %s" msgstr "%s%d элементов выбраны из %d %sв %s" #: e2p_acl.c:222 msgid "Whole" msgstr "Целиком" #: e2p_acl.c:229 e2p_acl.c:2096 msgid "Mask" msgstr "Маска" #: e2p_acl.c:1102 e2p_acl.c:1135 e2p_acl.c:3374 e2p_acl.c:3545 msgid "Directory ACL" msgstr "Каталог ACL" #: e2p_acl.c:1102 e2p_acl.c:1135 e2p_acl.c:3364 e2p_acl.c:3515 msgid "General ACL" msgstr "Основные ACL" #: e2p_acl.c:1107 #, c-format msgid "Cannot apply %s '%s' for %s" msgstr "Не могу применить %s '%s' для %s" #: e2p_acl.c:1140 #, c-format msgid "Cannot apply %s '%s' for %s - Invalid" msgstr "Не могу применить %s '%s' для %s - Неверно" #: e2p_acl.c:3351 msgid "No directory-changes have been selected" msgstr "Не выбраны изменения директорий" #: e2p_acl.c:3361 #, c-format msgid "The specified %s is likely to ba a problem" msgstr "Вероятно проблемой является указанный %s" #: e2p_acl.c:3461 msgid "extended permissions" msgstr "расширенные права" #: e2p_acl.c:3514 msgid "unable to display" msgstr "невозможно отобразить" #: e2p_acl.c:3620 msgid "General" msgstr "Основные" #: e2p_acl.c:3663 msgid "Data:" msgstr "Дата:" #: e2p_acl.c:3672 msgid "S_hown" msgstr "_Показанные" #: e2p_acl.c:3680 msgid "Changes will be based only on the data shown above" msgstr "Изменения будут основаны только на данных, показанных выше" #: e2p_acl.c:3681 msgid "_Varied" msgstr "_Измененные" #: e2p_acl.c:3690 msgid "" "Changes will be based on the standard permissions of the affected item as " "modified by the data shown above" msgstr "" "Изменения будут основаны на стандартных правах затронутых элементов, в " "соответствии с показанными выше данными" #: e2p_acl.c:3692 msgid "S_ystem" msgstr "_Системные" #: e2p_acl.c:3701 msgid "" "Changes will be based only on the standard (non-ACL) permissions of the " "affected item" msgstr "" "Изменения будут основаны только на стандартных (не ACL) правах затронутых " "элементов" #: e2p_acl.c:3713 msgid "_Nuke" msgstr "_Очистить" #: e2p_acl.c:3721 msgid "Clear as much of the item's ACL as possible" msgstr "Очистить как можно больше ACL прав" #: e2p_acl.c:3732 msgid "_Whole" msgstr "_Целиком" #: e2p_acl.c:3740 msgid "" "Conveniently sets all allowed 'whole' values. For those entries, the action " "will apply to the whole of the entry, Otherwise, the action affects only the " "permissions of that entry" msgstr "" "Полностью установить все возможные значения. Для этих элементов действие " "будет применено ко всему элементу целиком. Иначе действие затронет только " "права этого элемента" #: e2p_acl.c:3764 msgid "dirs-_general" msgstr "каталоги-основные" #: e2p_acl.c:3772 msgid "" "if activated, specified changes to the \"general\" ACL will be applied to " "any affected directory" msgstr "" "Если включено, то указанные \"основные\" изменения ACL будут применены к " "любому затронутому каталогу" #: e2p_acl.c:3774 msgid "dirs-_default" msgstr "каталоги-по-умолчанию" #: e2p_acl.c:3782 msgid "" "if activated, specified changes to the \"directories-only\" ACL will be " "applied to any affected directory" msgstr "" "Если включено, то указанные изменения ACL \"каталогов\" будут применены к " "любому затронутому каталогу" #: e2p_acl.c:3801 msgid "Insert a row in the ACL" msgstr "Вставить строку в ACL" #: e2p_acl.c:3805 msgid "De_lete" msgstr "_Удалить" #: e2p_acl.c:3811 msgid "Delete the selected row from the ACL" msgstr "Удалить выбранную строку из ACL" #: e2p_acl.c:4303 msgid "acl" msgstr "acl" #: e2p_acl.c:4307 msgid "_Access" msgstr "_Доступ" #: e2p_acl.c:4310 msgid "_Access.." msgstr "_Доступ.." #: e2p_acl.c:4311 e2p_acl.c:4319 msgid "Change extended permissions of selected items" msgstr "Изменить расширенные права у выбранных элементов" #: e2p_acl.c:4317 msgid "Change _ACLs.." msgstr "Изменить _ACL.." #: e2p_acl.c:4321 msgid "_Replicate" msgstr "_Повторить" #: e2p_acl.c:4323 msgid "" "Recursively apply ACLs of selected items to matching items in the other pane" msgstr "" "Рекурсивно установить ACL выбранных элементов, как у таких же элементов в " "другой панели" #: e2p_acl.c:4363 msgid "copy_acl" msgstr "copy_acl" #: e2p_clone.c:82 e2p_clone.c:160 msgid "clone" msgstr "клон" #: e2p_clone.c:163 msgid "C_lone.." msgstr "_Клонировать.." #: e2p_clone.c:164 msgid "Copy selected item(s), each with new name, to the current directory" msgstr "" "Копировать выбранные элементы, каждый с новым именем, в текущий каталог" #: e2p_config.c:371 #, c-format msgid "Bad configuration data for %s, not installed" msgstr "Неверные настройки для %s, не загружено" #: e2p_config.c:504 #, c-format msgid "Incompatible format - %s" msgstr "Неверный формат - %s" #: e2p_config.c:677 msgid "select configuration data file" msgstr "выберите файл настроек" #: e2p_config.c:751 msgid "save configuration data file" msgstr "файл сохранения настроек" #: e2p_config.c:810 e2p_config.c:878 msgid "select icons directory" msgstr "выберите каталог значков" #: e2p_config.c:1035 msgid "Save configuration data in" msgstr "Сохранить настройки в" #: e2p_config.c:1058 e2p_crypt.c:2968 msgid "backup" msgstr "backup" #: e2p_config.c:1097 e2p_config.c:1157 e2p_config.c:1284 e2p_config.c:1315 msgid "Se_lect" msgstr "_Выбор" #: e2p_config.c:1098 msgid "Select the file in which to store the config data" msgstr "Выберите файл в который необходимо сохранить настройки" #: e2p_config.c:1101 msgid "Save current config data in the specified file" msgstr "Сохранить текущие настройки в указанном файле" #: e2p_config.c:1107 msgid "export" msgstr "экспорт" #: e2p_config.c:1126 msgid "Get configuration data from" msgstr "Загрузить настройки из" #: e2p_config.c:1158 msgid "Select the config file from which to get the data" msgstr "Выберите файл из которого необходимо загрузить настройки" #: e2p_config.c:1162 msgid "Import config data in accord with choices below" msgstr "Импортировать настройки в соответствии с указанными параметрами" #: e2p_config.c:1171 msgid "_all options" msgstr "_все параметры" #: e2p_config.c:1173 msgid "all '_non-group' options" msgstr "все '_не-групповые' параметры" #: e2p_config.c:1177 msgid "all 'g_roup' options" msgstr "все '_групповые' параметры" #: e2p_config.c:1179 msgid "_specific group option(s)" msgstr "_указанные 'групповые' параметры" #: e2p_config.c:1180 msgid "_groups" msgstr "г_руппы" #: e2p_config.c:1235 msgid "import" msgstr "импорт" #: e2p_config.c:1254 msgid "Use icons in" msgstr "Использовать значки из" #: e2p_config.c:1285 msgid "Select the directory where the icons are" msgstr "Выберите каталог, содержащий значки" #: e2p_config.c:1289 msgid "Apply the chosen icon directory" msgstr "Применить выбранный каталог значков" #: e2p_config.c:1297 msgid "Copy current icons to" msgstr "Копировать текущие значки в" #: e2p_config.c:1316 msgid "Select the directory where the icons will be saved" msgstr "Выберите каталог, куда необходимо сохранить значки" #: e2p_config.c:1319 msgid "C_opy" msgstr "_Копировать" #: e2p_config.c:1320 msgid "Copy the icons to the chosen directory" msgstr "Копировать значки в выбранный каталог" #: e2p_config.c:1341 msgid "manage configuration data" msgstr "управление настройками" #: e2p_config.c:1376 msgid "manage" msgstr "управление" #: e2p_config.c:1378 msgid "_Configure.." msgstr "_Настройки.." #: e2p_config.c:1379 msgid "Export or import configuration data" msgstr "Экспортировать или импортировать настройки" #: e2p_cpbar.c:402 #, c-format msgid "" "copying %s\n" "to %s\n" "this is item %s of %s" msgstr "" "копирование %s\n" "в %s\n" "это элемент %s из %s" #: e2p_cpbar.c:413 e2p_mvbar.c:422 #, c-format msgid "%.2f MB of %.2f MB (%.0f\\%%)" msgstr "%.2f МБ of %.2f МБ (%.0f\\%%)" #: e2p_cpbar.c:673 e2p_mvbar.c:715 #, c-format msgid "Cannot put anything in %s" msgstr "Не могу ничего записать в %s" #: e2p_cpbar.c:695 msgid "copying" msgstr "копирование" #: e2p_cpbar.c:720 e2p_mvbar.c:794 msgid "_Resume" msgstr "_Продолжить" #: e2p_cpbar.c:721 msgid "Resume copying after pause" msgstr "Продолжить копирование после паузы" #: e2p_cpbar.c:726 e2p_mvbar.c:800 msgid "_Pause" msgstr "_Пауза" #: e2p_cpbar.c:727 msgid "Suspend copying, after the current item" msgstr "Приостановить копирование, после текущего элемента" #: e2p_cpbar.c:732 e2p_cpbar.c:736 msgid "Abort the copying" msgstr "Отменить копирование" #: e2p_cpbar.c:871 msgid "cpbar" msgstr "cpbar" #: e2p_cpbar.c:872 msgid "cpbar_with_time" msgstr "cpbar_with_time" #: e2p_cpbar.c:886 msgid "Copy selected item(s), with displayed progress details" msgstr "Копировать выбранные элементы, показывая детали процесса" #: e2p_cpbar.c:888 msgid "Copy with _times" msgstr "Копировать со _временем" #: e2p_cpbar.c:890 msgid "" "Copy selected item(s), with preserved time-properties and displayed progress " "details" msgstr "" "Копировать выбранные элементы, с сохранением временных свойств, показывая " "детали процесса" #: e2p_crypt.c:515 #, c-format msgid "No LZO compression-library for file %s" msgstr "Нет библиотеки сжатия LZO для файла %s" #: e2p_crypt.c:550 #, c-format msgid "No ZLIB compression-library for file %s" msgstr "Нет библиотеки сжатия ZLIB для файла %s" #: e2p_crypt.c:585 #, c-format msgid "No BZIP compression-library for file %s" msgstr "Нет библиотеки сжатия BZIP для файла %s" #: e2p_crypt.c:590 #, c-format msgid "Unknown compression-library for file %s" msgstr "Неизвестна библиотека сжатия файла %s" #: e2p_crypt.c:913 e2p_crypt.c:1811 #, c-format msgid "Cannot open '%s' for writing" msgstr "Не могу открыть '%s' для записи" #: e2p_crypt.c:1131 #, c-format msgid "Wrong password for %s" msgstr "Неверный пароль для %s" #: e2p_crypt.c:1247 #, c-format msgid "Error decompressing file %s" msgstr "Ошибка при распаковке файла %s" #: e2p_crypt.c:1960 #, c-format msgid "" "%s does not end with \"%s\".\n" "Process this file anyway?" msgstr "" "%s не заканчивается на \"%s\".\n" "Обработать файл, несмотря на это?" #: e2p_crypt.c:2303 #, c-format msgid "Cannot process all of %s" msgstr "Не могу ничего обработать из %s" #: e2p_crypt.c:2785 msgid "en/decrypt file" msgstr "за/расшифровать файл" #: e2p_crypt.c:2801 msgid "Symbolic link" msgstr "Символическая ссылка" #: e2p_crypt.c:2817 #, c-format msgid " to %s" msgstr "на %s" #: e2p_crypt.c:2825 msgid "encrypt" msgstr "зашифровать" #: e2p_crypt.c:2828 msgid "decrypt" msgstr "расшифровать" #: e2p_crypt.c:2832 msgid "encrypted file will have same name" msgstr "зашифрованный файл будет иметь то же имя" #: e2p_crypt.c:2837 msgid "append this to encrypted file name" msgstr "добавить это к имени зашифрованного файла" #: e2p_crypt.c:2857 msgid "encrypted file name will be" msgstr "зашифрованный файл будет иметь имя" #: e2p_crypt.c:2876 msgid "decrypted file will have same name" msgstr "расшифрованный файл будет иметь то же имя" #: e2p_crypt.c:2880 msgid "decrypted file will have embedded name" msgstr "расшифрованный файл будет иметь вложенное имя" #: e2p_crypt.c:2885 msgid "strip this from end of decrypted file name" msgstr "удалить это из имени зашифрованного файла" #: e2p_crypt.c:2904 msgid "decrypted file name will be" msgstr "расшифрованный файл будет иметь имя" #: e2p_crypt.c:2948 msgid "decrypted file will have stored owners, permissions and dates" msgstr "у расшифрованного файла будут сохраненные владелец, права и время" #: e2p_crypt.c:2948 msgid "restore properties" msgstr "восстановить свойства" #: e2p_crypt.c:2953 e2p_crypt.c:2957 msgid "compress" msgstr "сжать" #: e2p_crypt.c:2953 e2p_crypt.c:2957 msgid "compress file before encryption" msgstr "сжать файл перед зашифровкой" #: e2p_crypt.c:2965 msgid "store current name, permissions etc in the encrypted file" msgstr "сохранить текущие имя, права и т.д. в зашифрованном файле" #: e2p_crypt.c:2965 msgid "store properties" msgstr "сохранить свойства" #: e2p_crypt.c:2968 msgid "backup an existing file with the same name as the processed file" msgstr "сделать копию файла с таким же именем, как обрабатываемый файл" #: e2p_crypt.c:2970 msgid "do not remove the original file, after processing it" msgstr "не удалять оригинальный файл после обработки" #: e2p_crypt.c:2970 msgid "keep original" msgstr "оставить оригинал" #: e2p_crypt.c:2973 msgid "if file is a symlink, process its target" msgstr "если файл -- символическая ссылка, то обработать её цель" #: e2p_crypt.c:2973 msgid "through links" msgstr "обрабатывать ссылки" #: e2p_crypt.c:2975 msgid "recurse directories" msgstr "вложенные каталоги" #: e2p_crypt.c:3219 #, c-format msgid "You do not have authority to modify %s" msgstr "Вы не авторизованы, чтобы изменить %s" #: e2p_crypt.c:3336 msgid "crypt" msgstr "зашифровать" #: e2p_crypt.c:3339 msgid "_En/decrypt.." msgstr "_За/Расшифровать" #: e2p_crypt.c:3340 msgid "Encrypt or decrypt selected items" msgstr "Зашифровать или расшифровать выбранные элементы" #: e2p_dircmp.c:876 msgid "compare" msgstr "сравнить" #: e2p_dircmp.c:879 msgid "C_ompare" msgstr "_Сравнить" #: e2p_dircmp.c:880 msgid "Select active-pane items which are duplicated in the other pane" msgstr "" "Выбрать элементы в активной панели, копии которых существуют в другой панели" #: e2p_du.c:165 msgid "total size: " msgstr "полный размер: " #: e2p_du.c:181 msgid "kilobytes" msgstr "Килобайт" #: e2p_du.c:194 msgid "Megabytes" msgstr "Мегабайт" #: e2p_du.c:207 msgid "gigabytes" msgstr "Гигабайт" #: e2p_du.c:222 e2p_rename.c:934 e2p_rename.c:1049 msgid "in" msgstr "в" #: e2p_du.c:224 msgid "(one or more are hidden)" msgstr "(один или более скрыты)" #: e2p_du.c:270 msgid "_Disk usage" msgstr "_Занимаемый объём" #: e2p_du.c:271 msgid "Calculate the disk usage of selected item(s)" msgstr "Подсчитать объём, занимаемый выбранными элементами" #: e2p_find.c:316 msgid "all files" msgstr "все файлы" #: e2p_find.c:319 msgid "images" msgstr "изображения" #: e2p_find.c:320 msgid "music" msgstr "аудио файлы" #: e2p_find.c:321 msgid "videos" msgstr "видео файлы" #: e2p_find.c:322 msgid "text files" msgstr "текстовые файлы" #: e2p_find.c:323 msgid "development files" msgstr "исходные коды" #: e2p_find.c:324 msgid "other files" msgstr "другие файлы" #: e2p_find.c:334 msgid "conversations" msgstr "беседы" #: e2p_find.c:336 msgid "applications" msgstr "приложения" #: e2p_find.c:338 msgid "emails" msgstr "электронные письма" #: e2p_find.c:339 msgid "email attachments" msgstr "почтовые вложения" #: e2p_find.c:2623 e2p_rename.c:1460 msgid "Search for items:" msgstr "Искать элементы:" #: e2p_find.c:2626 e2p_rename.c:1461 msgid "any_where" msgstr "_везде" #: e2p_find.c:2629 e2p_rename.c:1464 msgid "in _active directory" msgstr "в _активном каталоге" #: e2p_find.c:2637 e2p_rename.c:1466 msgid "in _other directory" msgstr "в _другом каталоге" #: e2p_find.c:2645 e2p_rename.c:1468 msgid "in _this directory" msgstr "в _этом каталоге" #: e2p_find.c:2669 msgid "Recurse subdirectories" msgstr "Искать во вложенных каталогах" #: e2p_find.c:2712 msgid "name" msgstr "имя файла" #: e2p_find.c:2719 msgid "Find items whose name:" msgstr "Искать файлы, названия которых:" #: e2p_find.c:2724 e2p_find.c:3177 msgid "is" msgstr "точно" #: e2p_find.c:2726 e2p_find.c:3179 msgid "is like" msgstr "похожи на" #: e2p_find.c:2728 e2p_find.c:3181 msgid "matches this regex" msgstr "регулярное выражение" #: e2p_find.c:2730 e2p_find.c:3183 msgid "ignore case" msgstr "не учитывать регистр" #: e2p_find.c:2754 msgid "size" msgstr "размер" #: e2p_find.c:2762 msgid "Find items whose size is:" msgstr "Искать файлы, размер которых:" #: e2p_find.c:2767 msgid "less than:" msgstr "меньше чем:" #: e2p_find.c:2769 msgid "equal to:" msgstr "равен:" #: e2p_find.c:2772 msgid "more than" msgstr "больше чем:" #: e2p_find.c:2800 msgid "mime" msgstr "mime" #: e2p_find.c:2809 msgid "Find files whose mimetype is like this:" msgstr "Искать файлы, тип mime которых:" #: e2p_find.c:2835 msgid "save" msgstr "сохранённые" #: e2p_find.c:2840 msgid "Find items most-recently saved:" msgstr "Искать файлы, сохранённые:" #: e2p_find.c:2845 e2p_find.c:2879 e2p_find.c:2913 msgid "before:" msgstr "до:" #: e2p_find.c:2846 e2p_find.c:2880 e2p_find.c:2914 msgid "on/at:" msgstr "точно:" #: e2p_find.c:2848 e2p_find.c:2882 msgid "after:" msgstr "после:" #: e2p_find.c:2869 msgid "access" msgstr "считанные" #: e2p_find.c:2874 msgid "Find items most-recently read or executed:" msgstr "Искать файлы, считанные или запущенные:" #: e2p_find.c:2908 msgid "Find items whose inode was last changed:" msgstr "Искать файлы, иноды которых изменены:" #: e2p_find.c:2916 msgid "after" msgstr "после:" #: e2p_find.c:2936 msgid "permission" msgstr "права" #: e2p_find.c:2941 msgid "Find items whose permissions:" msgstr "Искать файлы, права которых:" #: e2p_find.c:2945 e2p_find.c:3007 msgid "are" msgstr "точно" #: e2p_find.c:2947 msgid "include" msgstr "включают" #: e2p_find.c:2949 msgid "exclude" msgstr "исключают" #: e2p_find.c:2955 msgid "owner read" msgstr "чтение для владельца" #: e2p_find.c:2957 msgid "group read" msgstr "чтение для группы" #: e2p_find.c:2959 msgid "anyone read" msgstr "чтение для всех" #: e2p_find.c:2963 msgid "owner write" msgstr "запись для владельца" #: e2p_find.c:2965 msgid "group write" msgstr "запись для группы" #: e2p_find.c:2967 msgid "anyone write" msgstr "запись для всех" #: e2p_find.c:2971 msgid "owner execute" msgstr "запуск для владельца" #: e2p_find.c:2973 msgid "group execute" msgstr "запуск для группы" #: e2p_find.c:2975 msgid "anyone execute" msgstr "запуск для всех" #: e2p_find.c:2979 msgid "setuid" msgstr "setuid" #: e2p_find.c:2981 msgid "setgid" msgstr "setgid" #: e2p_find.c:2983 msgid "sticky" msgstr "sticky" #: e2p_find.c:3004 msgid "Find items which" msgstr "Искать файлы, которые" #: e2p_find.c:3009 msgid "are not" msgstr "не являются" #: e2p_find.c:3016 msgid "regular" msgstr "обыкновенный" #: e2p_find.c:3023 e2p_find.c:3046 msgid "block device" msgstr "блочное устройство" #: e2p_find.c:3049 msgid "raw device" msgstr "устройство" #: e2p_find.c:3053 msgid "fifo" msgstr "fifo" #: e2p_find.c:3077 msgid "Find items with:" msgstr "Искать файлы, владелец которых:" #: e2p_find.c:3085 msgid "any user id" msgstr "любой пользователь" #: e2p_find.c:3087 msgid "specific user id" msgstr "указанный пользователь" #: e2p_find.c:3090 msgid "current user's uid" msgstr "текущий пользователь" #: e2p_find.c:3093 msgid "this user id" msgstr "пользователь с этим id" #: e2p_find.c:3100 msgid "unregistered user" msgstr "неизвестный пользователь" #: e2p_find.c:3119 msgid "any group id" msgstr "любая группа" #: e2p_find.c:3121 msgid "specific group id" msgstr "указанная группа" #: e2p_find.c:3124 msgid "current user's gid" msgstr "текущая группа" #: e2p_find.c:3127 msgid "this group id" msgstr "группа с этим id" #: e2p_find.c:3134 msgid "unregistered group" msgstr "неизвестная группа" #: e2p_find.c:3165 msgid "content" msgstr "содержимое" #: e2p_find.c:3173 msgid "Using grep, find files with content that:" msgstr "Используя grep, искать файлы содержащие:" #: e2p_find.c:3220 msgid "Using" msgstr "Используя" #: e2p_find.c:3229 msgid "find files with content that is:" msgstr "искать файлы, содержащие:" #: e2p_find.c:3514 msgid "Day" msgstr "День" #: e2p_find.c:3522 msgid "Month" msgstr "Месяц" #: e2p_find.c:3532 msgid "Year" msgstr "Год" #: e2p_find.c:3543 msgid "Hour" msgstr "Час" #: e2p_find.c:3551 msgid "Minute" msgstr "Минута" #: e2p_find.c:3595 msgid "find files" msgstr "поиск файлов" #: e2p_find.c:3614 msgid "get advice on search options on this page" msgstr "Посмотреть советы по пользованию поиском" #: e2p_find.c:3621 msgid "stop the current search" msgstr "Остановить текущий поиск" #: e2p_find.c:3627 msgid "begin searching" msgstr "Начать поиск" #: e2p_find.c:3630 msgid "Clea_r" msgstr "_Очистить" #: e2p_find.c:3630 msgid "clear all search parameters" msgstr "Сбросить все параметры поиска" #: e2p_find.c:3664 msgid "detfind" msgstr "detfind" #: e2p_find.c:3668 msgid "Find and list items, using detailed criteria" msgstr "Найти и показать элементы, используя указанные параметры" #: e2p_for_each.c:125 msgid "repeat action" msgstr "выполнить действие" #: e2p_for_each.c:126 msgid "Action to run for each selected item:" msgstr "Действие, которое необходимо выполнить для каждого элемента:" #: e2p_for_each.c:180 msgid "foreach" msgstr "foreach" #: e2p_for_each.c:183 msgid "For _each.." msgstr "Для _каждого.." #: e2p_for_each.c:184 msgid "Execute an entered command on each selected item separately" msgstr "" "Выполнить введенную команду для каждого выбранного элемента по отдельности" #: e2p_glob.c:409 msgid "Select items:" msgstr "Выберите элементы:" #: e2p_glob.c:410 msgid "selection filter" msgstr "фильтр выбора" #: e2p_glob.c:416 msgid "Named like" msgstr "С названием" #: e2p_glob.c:441 msgid "_Invert" msgstr "_Обратить" #: e2p_glob.c:448 msgid "Select items that DO NOT match the given mask" msgstr "Будут выбраны элементы, которые НЕ совпадают с данной маской" #: e2p_glob.c:449 msgid "Case _sensitive" msgstr "_Учитывать регистр" #: e2p_glob.c:567 msgid "glob" msgstr "glob" #: e2p_glob.c:570 msgid "_Glob.." msgstr "_Выбор.." #: e2p_glob.c:571 msgid "Select items matching a specified pattern" msgstr "Выбрать элементы совпадающие с указанной маской" #: e2p_gvfs.c:636 msgid "login details" msgstr "параметры входа" #: e2p_gvfs.c:642 msgid "Domain" msgstr "Домен" #: e2p_gvfs.c:769 #, c-format msgid "Cannot unmount %s - %s" msgstr "Не могу размонтировать %s - %s" #: e2p_gvfs.c:799 e2p_gvfs.c:833 #, c-format msgid "Cannot mount %s - %s" msgstr "Не могу смонтировать %s - %s" #: e2p_gvfs.c:1002 e2p_gvfs.c:1164 #, c-format msgid "Cannot open file %s - %s" msgstr "Не могу открыть файл %s - %s" #: e2p_gvfs.c:1087 #, c-format msgid "Error reading file %s - %s" msgstr "Ошибка при чтении файла %s - %s" #: e2p_gvfs.c:1103 e2p_gvfs.c:1204 #, c-format msgid "Cannot close file %s - %s" msgstr "Не могу закрыть файл %s - %s" #: e2p_gvfs.c:1186 #, c-format msgid "Error writing file %s - %s" msgstr "Ошибка записи в файл %s - %s" #: e2p_gvfs.c:1888 msgid "Enable vfs functionality using gvfs library" msgstr "Включить возможности vfs, с помощью библиотеки gvfs" #: e2p_mvbar.c:411 #, c-format msgid "" "moving %s\n" "to %s\n" "this is item %s of %s" msgstr "" "перемещение %s\n" "в %s\n" "это элемент %s из %s" #: e2p_mvbar.c:769 msgid "moving" msgstr "перемещение" #: e2p_mvbar.c:795 msgid "Resume moving after pause" msgstr "Продолжить перемещение после паузы" #: e2p_mvbar.c:801 msgid "Suspend moving, after the current item" msgstr "Приостановить перемещение после текущего элемента" #: e2p_mvbar.c:806 e2p_mvbar.c:810 msgid "Abort the moving" msgstr "Остановить перемещение" #: e2p_mvbar.c:948 msgid "mvbar" msgstr "mvbar" #: e2p_mvbar.c:952 msgid "Move selected item(s), with displayed progress details" msgstr "Переместить выбранные элементы, показывая детали процесса" #: e2p_names_clip.c:106 msgid "copy_name" msgstr "copy_name" #: e2p_names_clip.c:109 msgid "Copy _names" msgstr "Скопировать _названия" #: e2p_names_clip.c:110 msgid "Copy path or name of each selected item to the clipboard" msgstr "Скопировать путь или имя каждого выбранного элемента в буфер обмена" #: e2p_pack.c:51 msgid ".tar.gz" msgstr ".tar.gz" #: e2p_pack.c:52 msgid ".tar.bz2" msgstr ".tar.bz2" #: e2p_pack.c:53 msgid ".tar" msgstr ".tar" #: e2p_pack.c:54 msgid ".zip" msgstr ".zip" #: e2p_pack.c:55 msgid ".7z" msgstr ".7z" #: e2p_pack.c:56 msgid ".rar" msgstr ".rar" #: e2p_pack.c:57 msgid ".arj" msgstr ".arj" #: e2p_pack.c:58 msgid ".zoo" msgstr ".zoo" #: e2p_pack.c:195 msgid "Filename:" msgstr "Имя файла:" #: e2p_pack.c:196 msgid "archive creation" msgstr "создание архива" #: e2p_pack.c:251 msgid "pack" msgstr "pack" #: e2p_pack.c:254 msgid "_Pack.." msgstr "_Упаковать.." #: e2p_pack.c:255 msgid "Build an archive containing the selected item(s)" msgstr "Создать архив, содержащий выделенные элементы" #: e2p_rename.c:630 msgid "No current name pattern is specified" msgstr "Не указан шаблон текущих имен файлов" #: e2p_rename.c:643 msgid "No replacement name pattern is specified" msgstr "Не указан шаблон замены названий файлов" #: e2p_rename.c:651 msgid "Replacement name pattern cannot have wildcard(s)" msgstr "Шаблон замен названий файлов не может содержать сивмолы '?' и '*'" #: e2p_rename.c:757 #, c-format msgid "Error in regular expression %s" msgstr "Ошибка в регулярном выражении %s" #: e2p_rename.c:819 #, c-format msgid "Cannot find anything which matches %s" msgstr "Не могу найти ничего совпадающего с %s" #: e2p_rename.c:934 msgid "Rename" msgstr "Переименовать" #: e2p_rename.c:934 e2p_rename.c:1049 msgid "to" msgstr "на" #: e2p_rename.c:1049 msgid "Renamed" msgstr "Переименован" #: e2p_rename.c:1453 msgid "rename items" msgstr "переименование файлов" #: e2p_rename.c:1480 msgid "R_ecurse subdirectories" msgstr "В_ложенные каталоги" #: e2p_rename.c:1485 msgid "_Selected item(s)" msgstr "_Выбранные элементы" #: e2p_rename.c:1490 msgid "Match _exact/wildcard" msgstr "Искать _точно/шаблон" #: e2p_rename.c:1491 msgid "Match regular e_xpression" msgstr "Искать регулярное _выражение" #: e2p_rename.c:1495 msgid "Current name is like this:" msgstr "Текущее имя совпадает с:" #: e2p_rename.c:1516 msgid "New name is _upper case" msgstr "Новое имя в _верхнем регистре" #: e2p_rename.c:1518 msgid "New name is _lower case" msgstr "Новое имя в _нижнем регистре" #: e2p_rename.c:1520 msgid "_New name is like this:" msgstr "_Новое имя совпадает с:" #: e2p_rename.c:1532 msgid "Con_firm before each rename" msgstr "_Подтверждение перед каждым переименованием" #: e2p_rename.c:1539 msgid "Get advice on rename options" msgstr "Посмотреть советы по параметрам" #: e2p_rename.c:1542 msgid "Stop the current search" msgstr "Остановить поиск" #: e2p_rename.c:1547 msgid "_Rename" msgstr "_Переименовать" #: e2p_rename.c:1548 msgid "Begin renaming" msgstr "Начать переименование" #: e2p_rename.c:1577 msgid "renext" msgstr "renext" #: e2p_rename.c:1581 msgid "Rename item(s), using wildcards or regular-expressions" msgstr "Переименовать элементы, используя шаблоны или регулярные выражения" #: e2p_sort_by_ext.c:86 msgid "sort_by_ext" msgstr "sort_by_ext" #: e2p_sort_by_ext.c:89 msgid "Extension _sort" msgstr "_Сортировка по расширению" #: e2p_sort_by_ext.c:90 msgid "Sort the active file pane by filename extension" msgstr "Сортировать активную файловую панель по расширению имен файлов" #: e2p_thumbs.c:1106 msgid "Rotate _+" msgstr "Повернуть _+" #: e2p_thumbs.c:1107 msgid "Rotate selected images quarter-turn clockwise" msgstr "Повернуть выбранные изображения на черверть круга по часовой стрелке" #: e2p_thumbs.c:1109 msgid "Rotate _-" msgstr "Повернуть _-" #: e2p_thumbs.c:1110 msgid "Rotate selected images quarter-turn anti-clockwise" msgstr "" "Повернуть выбранные изображения на черверть круга против часовой стрелки" #: e2p_thumbs.c:1112 msgid "_Flip" msgstr "_Отразить" #: e2p_thumbs.c:1113 msgid "Flip selected images top-to-bottom" msgstr "Отразить выбранные изображения сверху-вниз" #: e2p_thumbs.c:1123 msgid "_Unselect all" msgstr "_Отменить выбор" #: e2p_thumbs.c:1129 msgid "Replicate _selection" msgstr "_Повторять выбор" #: e2p_thumbs.c:1137 msgid "" "If activated, items selected in this window will also be selected in the " "associated filelist" msgstr "" "Если включено, элементы, выбранные в этом окне, будут также выбраны в " "соответствующем списке файлов" #: e2p_thumbs.c:1140 msgid "_Clamp size" msgstr "_Изменять размер" #: e2p_thumbs.c:1148 msgid "" "If activated, thumbnails will be scaled up or down if needed, into the range " "32 to 128 pixels" msgstr "" "Если включено, миниатюры будут уменьшены или увеличены, если нужно в " "диапазоне от 32 до 128 пикселей" #: e2p_thumbs.c:1337 msgid "Ascending" msgstr "По возрастанию" #: e2p_thumbs.c:1345 msgid "If activated, items are displayed in ascending order" msgstr "Если включено, элементы будут сортироваться по возрастанию" #: e2p_thumbs.c:1477 msgid "pane 1 images" msgstr "панель изображений 1" #: e2p_thumbs.c:1477 msgid "pane 2 images" msgstr "панель изображений 2" #: e2p_thumbs.c:1607 msgid "_Sort" msgstr "_Сортировка" #: e2p_thumbs.c:1696 msgid "thumbs" msgstr "миниатюры" #: e2p_thumbs.c:1699 msgid "_Thumbnail.." msgstr "_Миниатюры.." #: e2p_thumbs.c:1700 msgid "Display thumbnails of image files in the active pane" msgstr "Показывать миниатюры изображений в активной панели" #: e2p_times.c:212 #, c-format msgid "Cannot change times of %s" msgstr "Не могу изменить время %s" #: e2p_times.c:383 #, c-format msgid "Cannot get current times of %s" msgstr "Не могу получить текущее время %s" #: e2p_times.c:442 #, c-format msgid "Cannot interpret date-time %s" msgstr "Не могу разобрать дату/время %s" #: e2p_times.c:534 msgid "" "Changing 'ctime' requires temporary changes to the system clock. That is " "normally unwise, as typically, other things rely on system time. Click 'ok' " "to proceed." msgstr "" "Изменение 'ctime' требует временного изменения системных часов. Это не очень " "правильно, потому что другие программы полагаются на системное время. " "Нажмите 'ОК' для продолжения." #: e2p_times.c:689 msgid "times" msgstr "время" #: e2p_times.c:712 msgid "Current values" msgstr "Текущие значения" #: e2p_times.c:713 msgid "New date" msgstr "Новая дата" #: e2p_times.c:714 msgid "New time" msgstr "Новое время" #: e2p_times.c:717 msgid "Content Modified" msgstr "Изменён" #: e2p_times.c:718 msgid "Inode Changed" msgstr "Создан" #: e2p_times.c:898 #, c-format msgid "You do not have authority to change time(s) for %s" msgstr "У вас нет прав для изменения времени для %s" #: e2p_times.c:1004 msgid "timeset" msgstr "timeset" #: e2p_times.c:1007 msgid "Change _times.." msgstr "Изменить _время.." #: e2p_times.c:1008 msgid "Change any of the time properties of selected items" msgstr "Изменить любые параметры времени у выбранных элементов" #: e2p_unpack.c:327 msgid "What do you want to do with the unpacked item(s) ?" msgstr "Что вы хотите сделать с распакованными элементами ?" #: e2p_unpack.c:331 msgid "Re_pack" msgstr "_Упаковать" #: e2p_unpack.c:333 msgid "_Retain" msgstr "_Оставить" #: e2p_unpack.c:418 msgid "Selected item is not a supported archive" msgstr "Выбранный элемент не является поддерживаемым архивом" #: e2p_unpack.c:432 msgid "Recursive unpack is not supported" msgstr "Распаковка вложенных каталогов не поддерживается" #: e2p_unpack.c:521 msgid "unpack_with_plugin" msgstr "unpack_with_plugin" #: e2p_unpack.c:527 msgid "" "Unpack archive (tar, tar.gz, tar.bz2, zip, 7z, rar, arj, zoo) into a " "temporary directory" msgstr "" "Распаковать архив (tar, tar.gz, tar.bz2, zip, 7z, rar, arj, zoo) во " "временный каталог" #: e2p_upgrade.c:42 #, c-format msgid "" "Configuration arrangements for this version %s of %s are considerably " "different from those of old versions. To reliably ensure access to the " "program's current features, it is best to start with fresh settings.\n" "If you proceed, the superseded configuration files in\n" " %s will have '.save' appended to their names.\n" "Feel free to delete them." msgstr "" "Параметры настройки для версии %s %s отличаются от параметров настройки " "старых версий. Для того чтобы вы могли воспользоваться всеми возможностями " "программы, рекомендуется начать с новыми настройками.\n" "Если вы продолжите, то предыдущие файлы настроек в\n" "%s будут соранены с добавлением '.save' к их именам.\n" "Вы можете спокойно их удалить." #: e2p_upgrade.c:50 #, c-format msgid "" "Several default configuration settings of this version %s of %s are " "different from those of recent versions (see changelog).\n" "If you click OK, those settings will be updated where possible.\n" "Or else you can Cancel, and later, via the configuration dialog, " "manuallychange individual settings, or change all settings to current " "defaults." msgstr "" "Некоторые стандартные параметры для версии %s %s отличаются от предыдущих " "версий (смотрите changelog).\n" "Если вы нажмете 'OK', то они будут обновлены, где возможно.\n" "Или вы можете просто нажать 'Отмена', и потом, используя диалог настройки, " "вручную изменить эти параметры, или сбросить их все к стандартным." #: e2p_upgrade.c:132 msgid "update information" msgstr "обновление" #: e2p_upgrade.c:806 msgid "" msgstr "<список>" #: e2p_view.c:80 msgid "view_with_plugin" msgstr "view_with_plugin" #: e2p_view.c:84 #, c-format msgid "Open the first selected item with the %s text-file viewer" msgstr "Открыть первый выбранный элемент с помощью текстового просмотрщика %s" #~ msgid "_file selection" #~ msgstr "выбор _файла" #~ msgid "_stock icons" #~ msgstr "_стандартные значки" emelfm2-0.4.1/po/de.po0000600000175000017500000047567411014372224013400 0ustar cairocairo# translation of emelfm2.pot to German msgid "" msgstr "" "Project-Id-Version: de\n" "POT-Creation-Date: \n" "PO-Revision-Date: 2008-05-19 23:48+0100\n" "Last-Translator: Ronny Steiner \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: e2_about_dialog.c:107 #: e2_action.c:851 #: e2_alias.c:323 #: e2_command.c:2781 msgid "help" msgstr "Hilfe" #: e2_about_dialog.c:118 #: e2_action.c:826 msgid "about" msgstr "Über" #: e2_about_dialog.c:126 #, c-format msgid "" "An \"orthodox\" file manager for GTK+2\n" "\n" "Copyright © %s\n" "\n" "This program is licensed under the terms of the General Public License and comes with ABSOLUTELY NO WARRANTY\n" "\n" "This binary was compiled on %s\n" "using %s and GTK+%d.%d.%d" msgstr "" "Ein GTK+2 Dateimanager\n" "im 2-Fenster-Design\n" "\n" "Copyright © %s\n" "\n" "This program is licensed under the terms of the General Public License and comes with ABSOLUTELY NO WARRANTY\n" "\n" "This binary was compiled on %s\n" "using %s and GTK+%d.%d.%d" #: e2_about_dialog.c:142 #, c-format msgid "" "The file\n" "%s\n" "gives details." msgstr "" "Die Datei\n" "%s\n" "enthält Details." #: e2_about_dialog.c:145 msgid "contributors" msgstr "Entwickler" #: e2_about_dialog.c:150 msgid "" "This program is based on emelFM, developed by Michael Clark.\n" "\n" "Contributions have been made by many friends." msgstr "" "Dieses Progamm basiert auf emelFM, entwickelt von Michael Clark.\n" "\n" "Viele Freunde trugen der Entwicklung bei." #: e2_about_dialog.c:160 msgid "usage" msgstr "Benutzung" #: e2_about_dialog.c:169 #: e2_option.c:1073 #: e2_output.c:980 msgid "commands" msgstr "Befehle" #: e2_about_dialog.c:179 #: e2_filetype_dialog.c:1193 msgid "Read the file" msgstr "Datei lesen" #: e2_about_dialog.c:179 #: e2_context_menu.c:554 #: e2_filetype_dialog.c:1192 #: e2p_view.c:83 msgid "_View" msgstr "_Anzeigen" #: e2_action.c:798 msgid "bookmark" msgstr "Lesezeichen" #: e2_action.c:799 #: e2_command.c:1858 msgid "command" msgstr "Befehl" #: e2_action.c:800 msgid "configure" msgstr "Konfigurieren" #: e2_action.c:801 #: e2_option__default.c:160 #: e2_option__default.c:329 #: e2_toolbar.c:2794 msgid "dialog" msgstr "Dialog" #: e2_action.c:802 msgid "dirline" msgstr "Verzeichnisbox" #: e2_action.c:803 #: e2p_du.c:219 msgid "file" msgstr "Datei" #: e2_action.c:804 #: e2_task.c:3419 msgid "find" msgstr "Suchen" #: e2_action.c:805 msgid "list" msgstr "Auflistung" #: e2_action.c:806 msgid "option" msgstr "Option" #: e2_action.c:807 #: e2_option.c:1094 msgid "output" msgstr "Ausgabebereich" #: e2_action.c:808 msgid "pane_active" msgstr "aktives_Fenster" #: e2_action.c:809 msgid "pane1" msgstr "Fenster1" #: e2_action.c:810 msgid "pane2" msgstr "Fenster2" #: e2_action.c:811 #: e2_option.c:1099 msgid "panes" msgstr "Fenster" #: e2_action.c:812 #: e2_config_dialog.c:935 msgid "plugin" msgstr "Plugin" #: e2_action.c:813 msgid "toggle" msgstr "umschalten" #: e2_action.c:815 msgid "separator" msgstr "Trennlinie" #: e2_action.c:816 msgid "" msgstr "" #: e2_action.c:817 msgid "" msgstr "" #: e2_action.c:818 msgid "" msgstr "" #: e2_action.c:821 msgid "" msgstr "" #: e2_action.c:822 msgid "" msgstr "" #: e2_action.c:823 msgid "" msgstr "" #: e2_action.c:824 msgid "" msgstr "" #: e2_action.c:825 msgid "" msgstr "" #: e2_action.c:827 msgid "add" msgstr "Hinzufügen" #: e2_action.c:828 msgid "adjust_ratio" msgstr "Bereichsgröße" #: e2_action.c:829 #: e2_bookmark.c:302 #: e2_option_tree.c:667 msgid "children" msgstr "Unterpunkte" #: e2_action.c:830 #: e2_alias.c:321 msgid "clear" msgstr "Bereinigen" #: e2_action.c:831 msgid "clear_history" msgstr "Chronik_löschen" #: e2_action.c:832 msgid "complete" msgstr "komplettieren" #: e2_action.c:833 msgid "application" msgstr "Anwendung" #: e2_action.c:834 #: e2_task.c:1426 msgid "copy" msgstr "Kopieren" #: e2_action.c:835 msgid "copy_as" msgstr "Kopieren_nach" #: e2_action.c:836 msgid "copy_merge" msgstr "Kopieren_mit_mischen" #: e2_action.c:837 msgid "copy_with_time" msgstr "Kopieren_mit_Zeit" #: e2_action.c:838 msgid "default" msgstr "Standard" #: e2_action.c:839 msgid "delete" msgstr "Löschen" #: e2_action.c:840 msgid "edit" msgstr "Bearbeiten" #: e2_action.c:841 msgid "edit_again" msgstr "Bearbeiten_fortsetzen" #: e2_action.c:842 msgid "filetype" msgstr "Dateityp" #: e2_action.c:843 msgid "focus" msgstr "Fokus" #: e2_action.c:844 msgid "focus_toggle" msgstr "aktivieren" #: e2_action.c:845 msgid "fullscreen" msgstr "Vollbild" #: e2_action.c:846 msgid "go_back" msgstr "zurückgehen" #: e2_action.c:847 msgid "go_forward" msgstr "vorwärtsgehen" #: e2_action.c:848 msgid "go_up" msgstr "hoch" #: e2_action.c:849 msgid "goto_bottom" msgstr "ans_Ende_gehen" #: e2_action.c:850 msgid "goto_top" msgstr "an_den_Anfang_gehen" #: e2_action.c:852 #: e2_option.c:1085 msgid "history" msgstr "Chronik" #: e2_action.c:853 #: e2_mkdir_dialog.c:918 msgid "info" msgstr "Info" #: e2_action.c:854 msgid "insert_selection" msgstr "Auswahl_einfügen" #: e2_action.c:855 msgid "invert_selection" msgstr "Auswahl_invertieren" #: e2_action.c:856 msgid "mirror" msgstr "Fenster_spiegeln" #: e2_action.c:857 msgid "mkdir" msgstr "Verzeichnis_erstellen" #: e2_action.c:858 msgid "mountpoints" msgstr "Mountpunkte" #: e2_action.c:859 #: e2_task.c:1748 msgid "move" msgstr "Verschieben" #: e2_action.c:860 msgid "move_as" msgstr "Verschieben_nach" #: e2_action.c:861 #: e2_select_dir_dialog.c:45 msgid "open" msgstr "Öffnen" #: e2_action.c:862 msgid "open_in_other" msgstr "Öffnen_im_anderen" #: e2_action.c:863 msgid "open_with" msgstr "Öffnen_mit" #: e2_action.c:864 #: e2p_find.c:3069 msgid "owners" msgstr "Besitzer" #: e2_action.c:865 msgid "page_down" msgstr "Seite_runter" #: e2_action.c:866 msgid "page_up" msgstr "Seite_hoch" #: e2_action.c:867 msgid "pending" msgstr "Warteschlange" #: e2_action.c:868 #: e2_permissions_dialog.c:318 msgid "permissions" msgstr "Zugriffsrechte" #: e2_action.c:869 msgid "print" msgstr "Druck" #: e2_action.c:870 #: e2_alias.c:322 msgid "quit" msgstr "Beenden" #: e2_action.c:871 msgid "refresh" msgstr "Aktualisieren" #: e2_action.c:872 msgid "refreshresume" msgstr "Aktualisierung_ein" #: e2_action.c:873 msgid "refreshsuspend" msgstr "Aktualisierung_aus" #: e2_action.c:874 #: e2_task.c:2358 msgid "rename" msgstr "Umbenennen" #: e2_action.c:875 msgid "scroll_down" msgstr "runterblättern" #: e2_action.c:876 msgid "scroll_up" msgstr "hochblättern" #: e2_action.c:877 #: e2_option.c:1102 msgid "search" msgstr "Suchen" #: e2_action.c:878 msgid "send" msgstr "zu_Kindprozess_senden" #: e2_action.c:879 msgid "set" msgstr "setzen" #: e2_action.c:880 msgid "show" msgstr "anzeigen" #: e2_action.c:881 msgid "show_hidden" msgstr "versteckte_anzeigen" #: e2_action.c:882 msgid "show_menu" msgstr "Menü_zeigen" #: e2_action.c:883 msgid "sortaccesssed" msgstr "sortieren_nach_Zugriff" #: e2_action.c:884 msgid "sortchanged" msgstr "sortieren_nach_geändert" #: e2_action.c:885 msgid "sortgroup" msgstr "sortieren_nach_Gruppe" #: e2_action.c:886 msgid "sortmodified" msgstr "sortieren_nach_verändert" #: e2_action.c:887 msgid "sortname" msgstr "sortieren_nach_Dateiname" #: e2_action.c:888 msgid "sortpermission" msgstr "sortieren_nach_Rechte" #: e2_action.c:889 msgid "sortsize" msgstr "sortieren_nach_Größe" #: e2_action.c:890 msgid "sortuser" msgstr "sortieren_nach_Eigentümer" #: e2_action.c:891 msgid "switch" msgstr "umschalten" #: e2_action.c:892 #: e2_option__default.c:274 #: e2p_find.c:3020 msgid "symlink" msgstr "Symbolischer_Link" #: e2_action.c:893 msgid "symlink_as" msgstr "Link_als" #: e2_action.c:894 msgid "sync" msgstr "spiegeln" #: e2_action.c:895 msgid "toggle_direction" msgstr "Ausrichtung_ändern" #: e2_action.c:897 msgid "toggle_select_all" msgstr "alles_auswählen" #: e2_action.c:898 msgid "toggle_selected" msgstr "Objekt_auswählen" #: e2_action.c:899 #: e2_bookmark.c:564 msgid "trash" msgstr "Papierkorb" #: e2_action.c:900 msgid "trashempty" msgstr "Papierkorb_leeren" #: e2_action.c:902 msgid "tree" msgstr "Verzeichnisbaum" #: e2_action.c:904 msgid "unpack" msgstr "Entpacken" #: e2_action.c:905 #: e2_option.c:1107 msgid "view" msgstr "Betrachten" #: e2_action.c:906 msgid "view_again" msgstr "Betrachten_fortsetzen" #: e2_action.c:907 msgid "view_at" msgstr "Betrachten_an_Stelle" #: e2_action.c:909 #: e2_bookmark.c:302 #: e2_option_tree.c:667 msgid "child" msgstr "Unterpunkt" #: e2_action.c:910 msgid "ctrl" msgstr "eigene_Befehle" #: e2_action.c:911 msgid "dirs" msgstr "nur_Verzeichnisse" #: e2_action.c:912 msgid "escape" msgstr "nicht_gequotet" #: e2_action.c:913 msgid "expand" msgstr "aufklappen" #: e2_action.c:914 #: e2p_du.c:219 msgid "files" msgstr "Dateien" #: e2_action.c:915 msgid "off" msgstr "aus" #: e2_action.c:916 msgid "on" msgstr "an" #: e2_action.c:917 msgid "quote" msgstr "gequotet" #: e2_action.c:918 msgid "shift" msgstr "Kontext" #: e2_action.c:919 msgid "top" msgstr "am_Anfang" #: e2_action.c:923 msgid "" msgstr "" #: e2_action.c:924 msgid "dummy" msgstr "Dummy" #: e2_action.c:925 msgid "namespace" msgstr "Namensraum" #: e2_action.c:926 msgid "unpack_in_other" msgstr "Entpacken_im_anderen" #: e2_action.c:929 msgid "key" msgstr "Taste" #: e2_action.c:930 msgid "alias" msgstr "Alias" #: e2_alias.c:316 msgid "x" msgstr "X" #: e2_alias.c:317 #: e2_alias.c:318 #: e2_toolbar.c:2893 msgid "Done. Press enter " msgstr "Abgeschlossen. Drücke ENTER." #: e2_alias.c:317 msgid "xx" msgstr "XX" #: e2_alias.c:324 #: e2_output.c:985 msgid "keys" msgstr "Tasten" #: e2_alias.c:325 msgid "e2ps" msgstr "Prozess" #: e2_alias.c:327 msgid "cns" msgstr "" #: e2_alias.c:350 msgid "Match" msgstr "Ausdruck" #: e2_alias.c:353 msgid "Stop" msgstr "Halt" #: e2_alias.c:355 msgid "Replace" msgstr "Ersetzen durch" #: e2_bookmark.c:298 msgid "Are you sure that you want to delete the bookmark" msgstr "Lesezeichen wirklich löschen" #: e2_bookmark.c:302 #: e2p_du.c:222 msgid "and" msgstr "und" #: e2_bookmark.c:307 msgid "confirm bookmark delete" msgstr "Bestätigung" #: e2_bookmark.c:375 msgid "_Add after" msgstr "_Hinzufügen (danach)" #: e2_bookmark.c:376 msgid "Bookmark the current directory after the selected bookmark" msgstr "Fügt ein Lesezeichen des aktuellen Verzeichnisses nach dem markierten Lesezeichen ein." #: e2_bookmark.c:382 msgid "Add as _child" msgstr "_Hinzufügen als Unterpunkt" #: e2_bookmark.c:383 msgid "Bookmark the current directory a a child of the selected bookmark" msgstr "Fügt ein Lesezeichen des aktuellen Verzeichnisses als Unterpunkt zum markierten Lesezeichen hinzu." #: e2_bookmark.c:389 #: e2_context_menu.c:563 #: e2p_unpack.c:335 msgid "_Delete" msgstr "_Löschen" #: e2_bookmark.c:390 msgid "Delete the selected bookmark, and its children if any" msgstr "Löscht das markierte Lesezeichen und dessen Unterpunkte, soweit vorhanden." #: e2_bookmark.c:552 msgid "_home" msgstr "_Home" #: e2_bookmark.c:555 msgid "cdrom" msgstr "CD-ROM" #: e2_bookmark.c:557 msgid "root" msgstr "Stammverzeichnis /" #: e2_bookmark.c:558 msgid "Your home directory" msgstr "Ihr Home-Verzeichnis" #: e2_bookmark.c:558 msgid "home" msgstr "Verzeichnis /home" #: e2_bookmark.c:559 msgid "media" msgstr "Verzeichnis /media" #: e2_bookmark.c:560 msgid "mnt" msgstr "Verzeichnis /mnt" #: e2_bookmark.c:561 msgid "usr" msgstr "Verzeichnis /usr" #: e2_bookmark.c:562 msgid "usr/local" msgstr "Verzeichnis /usr/local" #: e2_bookmark.c:564 msgid "default trash directory" msgstr "Verzeichnis für den Papierkorb" #: e2_bookmark.c:582 #: e2_context_menu.c:613 #: e2_menu.c:995 #: e2_plugins.c:1278 #: e2_toolbar.c:2851 #: e2_toolbar.c:2918 #: e2_toolbar.c:3085 msgid "Label" msgstr "Bezeichnung" #: e2_bookmark.c:584 #: e2_context_menu.c:615 #: e2_menu.c:993 #: e2_plugins.c:1280 #: e2_toolbar.c:2853 #: e2_toolbar.c:2920 #: e2_toolbar.c:3087 msgid "Icon" msgstr "Icon" #: e2_bookmark.c:586 #: e2_menu.c:997 #: e2_plugins.c:1282 #: e2_toolbar.c:2855 #: e2_toolbar.c:2922 #: e2_toolbar.c:3089 msgid "Tooltip" msgstr "Schnellhilfe" #: e2_bookmark.c:588 #: e2_plugins.c:1286 msgid "Path" msgstr "Pfad" #: e2_bookmark.c:596 msgid "open bookmark in other pane on middle-button click" msgstr "Lesezeichen im anderen Fenster beim Drücken der mittleren Maustaste öffnen" #: e2_bookmark.c:597 msgid "Clicking the middle mouse button on a bookmark will open it in the other file pane" msgstr "Wenn aktiviert, wird beim Drücken der mittleren Maustaste auf ein Lesezeichen dieses im anderen Fenster geöffnet." #: e2_bookmark.c:601 msgid "focus file pane after opening a bookmark in it" msgstr "Fenster, in dem das Lesezeichen göffnet wird, aktivieren" #: e2_bookmark.c:602 msgid "After opening a bookmark in the inactive file pane, that pane will become the active one" msgstr "Wenn aktiviert, wird nach dem Öffnen des Lesezeichens im inaktiven Fenster dieses aktiviert." #: e2_bookmark.c:608 msgid "confirm any delete of a selected bookmark" msgstr "Löschen EINES Lesezeichens bestätigen" #: e2_bookmark.c:609 msgid "You will be asked to confirm, before deleting any bookmark" msgstr "Wenn aktiviert, werden Sie immer vor dem Löschen eines Lesezeichens zu einer Bestätigung aufgefordert." #: e2_bookmark.c:613 msgid "confirm any delete of multiple bookmarks" msgstr "Löschen MEHRERER Lesezeichen bestätigen" #: e2_bookmark.c:614 msgid "You will be asked to confirm, before deleting any bookmark that has 'children'" msgstr "Wenn aktiviert, werden Sie immer vor dem Löschen eines Lesezeichens, welches Unterpunkte enthält, zu einer Bestätigung aufgefordert." #: e2_button.c:25 #: e2p_upgrade.c:107 msgid "_OK" msgstr "_Okay" #: e2_button.c:28 #: e2p_upgrade.c:116 msgid "_Cancel" msgstr "_Abbrechen" #: e2_button.c:30 msgid "_Yes" msgstr "_Ja" #: e2_button.c:33 msgid "_No" msgstr "_Nein" #: e2_button.c:36 msgid "Yes to _all" msgstr "Ja, a_lle" #: e2_button.c:38 #: e2p_cpbar.c:731 #: e2p_cpbar.c:735 #: e2p_mvbar.c:805 #: e2p_mvbar.c:809 msgid "_Stop" msgstr "_Anhalten" #: e2_button.c:40 #: e2p_config.c:1161 #: e2p_config.c:1288 msgid "_Apply" msgstr "_Anwenden" #: e2_button.c:43 msgid "_Apply to all" msgstr "Anwenden, a_lle" #: e2_button.c:45 #: e2_tree_dialog.c:1115 #: e2p_thumbs.c:1117 msgid "_Refresh" msgstr "_Aktualisieren" #: e2_button.c:47 msgid "_Close" msgstr "_Schließen" #: e2_button.c:49 msgid "C_reate" msgstr "_Erstellen" #: e2_button.c:51 #: e2_option_tree.c:978 #: e2_permissions_dialog.c:458 #: e2p_acl.c:3728 msgid "_Remove" msgstr "E_ntfernen" #: e2_cache.c:726 #, c-format msgid "" "%sThis file stores runtime configuration data for %s.\n" "%sThe file will be overwritten each time %s is shut down.\n" "\n" msgstr "" "%sDiese Datei speichert Laufzeit-Konfigurationsdaten für %s.\n" "%sSie wird immer beim Beenden von %s überschrieben!\n" "\n" #: e2_cache.c:955 #, c-format msgid "Cannot write cache file %s - %s" msgstr "Fehler beim Schreiben der Cache-Datei: %s - %s!" #: e2_cl_option.c:43 #, c-format msgid "usage: %s [option]\n" msgstr "Anwendung: %s [Optionen]\n" #: e2_cl_option.c:56 msgid "" "Program options:\n" "-1,--one=DIR set 1st pane's start directory to DIR\n" "-2,--two=DIR set 2nd pane's start directory to DIR\n" "-c,--config=DIR set config directory to DIR (default: ~/.config/emelfm2)\n" "-e,--encoding=TYPE set filesystem character encoding to TYPE\n" "-f,--fallback-encoding set fallback encoding (default: ISO-8859-1)\n" "-i,--ignore-problems ignore encoding/locale problems (at your own risk!)\n" "-l,--log-all maximise scope of error logging\n" "-m,--daemon run program as daemon\n" "-r,--run-at-start=CMD run command CMD at session start\n" "-s,--set-option=OPT set one-line gui option using config-file formatted OPT\n" "-t,--trash=DIR set trash directory to DIR (default: ~/.local/share/Trash/files)\n" "\n" "Help options:\n" "-h,--help show this help message\n" "-u,--usage display brief usage messsage\n" "-v,--version display version and build info\n" msgstr "" "Programmparameter:\n" "-1,--one=Verz. Startverzeichnis des 1. Fensters\n" "-2,--two=Verz. Startverzeichnis des 2. Fensters\n" "-c,--config=Verz. Konfigurationsverzeichnis (Standard: ~/.config/emelfm2)\n" "-e,--encoding=TYP Zeichensatz des Dateisystems setzen\n" "-f,--fallback-encoding Fallback-Zeichensatz setzen (Standard: ISO-8859-1)\n" "-i,--ignore-problems Zeichensatz/Locale-Probleme ignorieren (auf eigenes Risiko!)\n" "-l,--log-all alle Fehler protokollieren\n" "-m,--daemon Programm als Dämon starten\n" "-r,--run-at-start=CMD Befehl, der nach Start von emelFM2 ausgeführt wird\n" "-s,--set-option=OPT GUI-Optionen setzen (eine Zeile im Konfigurationsdateiformat)\n" "-t,--trash=Verz. Verzeichnis des Papierkorbes setzen (Standard: ~/.local/share/Trash/files)\n" "\n" "Hilfe Optionen:\n" "-h,--help Diese Hilfe zeigen\n" "-u,--usage Kurze Hilfe zur Benutzung anzeigen\n" "-v,--version Version und Build-Info anzeigen\n" #: e2_cl_option.c:78 msgid "" "-d,--debug=[1-5] set debug level from 1 (low) to 5 (high)\n" "-x,--verbose display time/location info on debug messages\n" msgstr "" "-d,--debug=[1-5] Debug-Stufe von 1 (niedrig) bis 5 (hoch)\n" "-x,--verbose Zeit und Ort bei der Debugausgabe anzeigen\n" #: e2_cl_option.c:91 #, c-format msgid "" "%s v. %s\n" "Licensed under the GPL\n" "Copyright (C) %s\n" "Build date: %s\n" "Build platform: GTK+ %d.%d.%d %s\n" msgstr "" "%s v. %s\n" "Lizensiert unter der GPL\n" "Copyright (C) %s\n" "Build-Datum: %s\n" "Build-Plattform: GTK+ %d.%d.%d %s\n" #: e2_cl_option.c:299 msgid "Startup options must begin with \"-\" or \"--\"\n" msgstr "Fehler! Startoptionen beginnen mit \"-\" oder \"--\"!\n" #: e2_command.c:643 #: e2_command.c:1301 #: e2_command.c:1587 msgid "returned" msgstr "meldete" #: e2_command.c:789 #, c-format msgid "Command '%s' - %s" msgstr "Befehl '%s' - %s" #: e2_command.c:1066 #, c-format msgid "Error while launching '%s'" msgstr "Fehler beim Starten von '%s'!" #: e2_command.c:1092 msgid "Cannot find last child process" msgstr "Fehler! Kann letzten Kindprozess nicht finden!" #: e2_command.c:1100 #, c-format msgid "Cannot find child process with pid %ld" msgstr "Fehler! Kann Kindprozess nicht finden mit der PID %ld" #: e2_command.c:1103 #, c-format msgid "Cannot communicate to process %ld" msgstr "Fehler bei der Kommunikation mit dem Prozess %ld" #: e2_command.c:1118 msgid "Failed writing to child" msgstr "Fehler beim Senden zum Kindprozess" #: e2_command.c:1594 #, c-format msgid "Strange error: could not run '%s'" msgstr "Fehler beim Ausführen von '%s'!" #: e2_command.c:1617 #, c-format msgid "The process with pid %ld is not our child" msgstr "Der Prozess mit der PID %ld ist kein Kindprozess von emelFM2." #: e2_command.c:1629 #: e2_command.c:1640 #, c-format msgid "Failed writing to child: %s" msgstr "Fehler beim Senden zum Kindprozess: %s" #: e2_command.c:1854 msgid "nothing is waiting" msgstr "Die Warteschlange ist leer!" #: e2_command.c:1878 #: e2_command.c:1982 #: e2_command.c:2045 msgid "" msgstr "" #: e2_command.c:1953 msgid "nothing is running" msgstr "Nichts läuft!" #: e2_command.c:1957 msgid " pid || directory || command" msgstr " PID || Verzeichnis || Befehl" #: e2_command.c:2019 msgid "command || directory || result" msgstr "Befehl || Verzeichnis || Ergebnis" #: e2_command.c:2051 #: e2_command.c:2063 msgid "OK" msgstr "Okay" #: e2_command.c:2051 #: e2_command.c:2066 msgid "error" msgstr "Fehler" #: e2_command.c:2321 #, c-format msgid "Cannot run '%s'" msgstr "Ausführen von '%s' nicht möglich!" #: e2_command.c:2439 msgid "Failed to expand macros" msgstr "Makro konnte nicht ausgeführt werden!" #: e2_command.c:2587 #, c-format msgid "Failed parsing command '%s' - %s" msgstr "Fehler beim Interpretieren des Befehls '%s' - %s!" #: e2_command.c:2675 #, c-format msgid "Cannot send \"%s\" to a child process" msgstr "Senden von \"%s\" zu einem Kindprozess nicht möglich!" #: e2_command.c:2870 msgid "x terminal emulator:" msgstr "X-Terminal-Befehl:" #: e2_command.c:2871 msgid "This is the external command/application that will be be run when emelFM2 is asked to open a terminal" msgstr "Das ist der Befehl, welcher aufrufen wird, wenn emelFM2 ein Terminal öffnen soll." #: e2_command.c:2874 msgid "use external file-viewer" msgstr "Externen Datei-Betrachter verwenden" #: e2_command.c:2875 msgid "If activated, the command entered below will be run to view file content, instead of launching the internal viewer" msgstr "Wenn aktiviert, wird das nachstehende Programm zum Betrachten des Dateiinhaltes anstatt des internen Betrachters verwendet." #: e2_command.c:2878 msgid "viewer command:" msgstr "Betrachter-Befehl:" #: e2_command.c:2879 msgid "" "This is a command to run an external application for viewing file content.\n" "The first selected item will be supplied as the first argument" msgstr "" "Das ist der Befehl, welcher den externen Datei-Betrachter aufruft.\n" "Die erste ausgewählte Datei wird als erster Parameter übergeben." #: e2_command.c:2883 msgid "use external file-editor" msgstr "Externen Datei-Editor verwenden" #: e2_command.c:2884 msgid "If activated, the command entered below will be run to edit file content, instead of launching the internal editor" msgstr "Wenn aktiviert, wird das nachstehende Programm zum Bearbeiten des Dateiinhaltes anstatt des internen Editors verwendet." #: e2_command.c:2887 msgid "editor command:" msgstr "Editor-Befehl:" #: e2_command.c:2888 msgid "" "This is a command to run an external application for editing file content.\n" "The first selected item will be supplied as the first argument" msgstr "" "Das ist der Befehl, welcher den externen Datei-Editor aufruft.\n" "Die erste ausgewählte Datei wird als erster Parameter übergeben." #: e2_command.c:2892 msgid "use external encoding converter" msgstr "Externen Zeichensatz-Konverter verwenden" #: e2_command.c:2893 msgid "If activated, the command entered below will be run, instead of using the internal conversion functions, to convert file character encoding when needed" msgstr "Wenn aktiviert, wird das nachstehende Programm zum Umwandeln des Zeichensatzes anstatt des internen Konverters verwendet." #: e2_command.c:2896 #: e2p_upgrade.c:744 #: e2p_upgrade.c:745 msgid "File encoding:" msgstr "Datei-Zeichensatz" #: e2_command.c:2897 msgid "converter command:" msgstr "Konverter-Befehl:" #: e2_command.c:2898 msgid "A command which runs an external application to convert text encoding to UTF-8" msgstr "" "Das ist der Befehl, welcher den externen Zeichensatz-Konverter aufruft,\n" "der den Text in UTF-8 umwandelt." #: e2_command.c:2902 msgid "use aliases" msgstr "Kurzbefehle verwenden" #: e2_command.c:2903 msgid "This is a general switch to turn on/off alias handling for commands" msgstr "Wenn aktiviert, so wird das Verwenden von Kurzbefehlen in der Befehlszeile eingeschaltet." #: e2_command.c:2906 msgid "interpret 'relative' paths" msgstr "Relative Pfade interpretieren" #: e2_command.c:2907 msgid "This enables correct interpretation of paths containing '..' etc. These might be typed in, or attached to a button, say" msgstr "Wenn aktiviert, werden Pfade, welche '..' enthalten, richtig interpretiert." #: e2_command.c:2910 msgid "running commands survive shutdown" msgstr "Laufende Aktionen beim Beenden von emelFM2 nicht abbrechen" #: e2_command.c:2911 msgid "If activated, commands that are still running will not be terminated at end of emelFM2 session" msgstr "Wenn aktiviert, werden laufende Aktionen beim Beenden von emelFM2 nicht abgebrochen." #: e2_command.c:2915 msgid "watch priority" msgstr "Ausgabe-Priorität" #: e2_command.c:2916 msgid "" "The watch priority of commands influences how fast program\n" "output is read from i/o channels. A too-high priority might\n" "decrease gui responsiveness and break automatic scrolling.\n" "(note: negative values mean high priority, positive values mean low priority)" msgstr "" "Die Ausgabe-Priorität beeinflusst, wie schnell Programm-Ausgaben von den I/O-Kanälen gelesen werden.\n" "Eine zu hohe Priorität kann die Reaktionsfähigkeit von emelFM2 verringern.\n" "(Anmerkung: negative Werte=hohe Priorität, positive Werte=geringe Priorität)" #: e2_command.c:2925 msgid "stop after timeout" msgstr "Datei-Aktionen nach einer bestimmten Zeit abbrechen" #: e2_command.c:2926 msgid "If activated, each file operation will be terminated if not finished within the time-interval set below" msgstr "Wenn aktiviert, wird jede Datei-Aktion abgebrochen, wenn sie nicht innerhalb der angegebenen Zeit vollendet wurde." #: e2_command.c:2929 msgid "timeout interval" msgstr "maximale Zeitdauer je Datei-Aktion (in Sekunden)" #: e2_command.c:2930 msgid "The interval (seconds) allowed to complete any file operation" msgstr "Hier stellen Sie die Anzahl der Sekunden ein, in der eine Datei-Aktion abgeschlossen sein muss." #: e2_command.c:2933 msgid "confirm any delete" msgstr "Jedes Löschen bestätigen" #: e2_command.c:2934 msgid "If activated, you will be asked for confirmation before actually deleting anything" msgstr "Wenn aktiviert, werden Sie immer vor dem Löschen zu einer Bestätigung aufgefordert." #: e2_command.c:2936 msgid "confirm any overwrite" msgstr "Jedes Überschreiben bestätigen" #: e2_command.c:2937 msgid "If activated, you will be asked for confirmation before actually overwriting anything" msgstr "Wenn aktiviert, werden Sie immer vor dem Überschreiben zu einer Bestätigung aufgefordert." #: e2_command.c:2939 msgid "relative symlinks" msgstr "Symbolische Links mit relativem Pfad erzeugen" #: e2_command.c:2940 msgid "This gives each created symlink a relative path to its source, like '../..//', instead of a full path referenced to /" msgstr "Wenn aktiviert, werden symbolische Links mit relativen Pfaden (z.B. ../../Verzeichnis/Datei) anstatt des vollen Pfades (z.B. /home/Benutzer/Verzeichnis/Datei) erzeugt." #: e2_command_line.c:577 msgid "mounts" msgstr "mounts" #: e2_command_line.c:579 #: e2p_track.c:256 msgid "all" msgstr "Alles" #: e2_command_line.c:672 #, c-format msgid "Warning - process %s is not active" msgstr "Warnung - Kindprozess %s ist nicht aktiv!" #: e2_command_line.c:988 msgid "show last" msgstr "Letzten Befehl zeigen" #: e2_command_line.c:989 msgid "If activated, the last-entered command will be displayed, instead of an empty line" msgstr "Wenn aktiviert, wird der letzte eingegebene Befehl angezeigt anstatt einer leeren Zeile." #: e2_command_line.c:994 #: e2_command_line.c:1041 msgid "maximum number of history entries" msgstr "Maximale Anzahl der Chronik-Einträge" #: e2_command_line.c:995 msgid "This is the largest number of command-line history entries that will be recorded" msgstr "Das ist die maximale Anzahl der Befehle, die in der Befehlszeilen-Chronik aufgezeichnet werden." #: e2_command_line.c:998 #: e2_command_line.c:1046 msgid "double entries" msgstr "Gleiche Einträge aufzeichnen" #: e2_command_line.c:999 #: e2_command_line.c:1047 msgid "This allows entries to be recorded more than once in the history list, so the last entry is always close to hand" msgstr "Wenn NICHT aktiviert, werden gleiche Befehle NICHT mehrfach aufgezeichnet." #: e2_command_line.c:1002 #: e2_command_line.c:1051 msgid "cyclic list" msgstr "Liste zyklisch durchblättern" #: e2_command_line.c:1003 #: e2_command_line.c:1052 msgid "When scanning the history list, cycle from either end around to the other end, instead of stopping" msgstr "Wenn aktiviert, wird nach Anzeige der letzten Chronikzeile die erste wieder angezeigt und umgekehrt anstatt zu stoppen." #: e2_command_line.c:1007 #: e2_command_line.c:1056 msgid "show as a menu" msgstr "Als Menü anzeigen" #: e2_command_line.c:1008 msgid "If activated, the history entries will be presented as a menu. For most Gtk+2 themes, this will not be as attractive as the list view" msgstr "Wenn aktiviert, wird die Befehlszeilen-Chronik nicht als Liste sondern als Menü angezeigt." #: e2_command_line.c:1015 msgid "append space after unique items" msgstr "Leerzeichen nach eindeutigen Datei-/Verzeichnisnamen anhängen" #: e2_command_line.c:1016 msgid "This appends a 'space' character to the end of a unique successful match of a file" msgstr "Wenn aktiviert, wird bei eindeutigen Datei- und Verzeichnisnamen bei Betätigung der Tabulatortaste ein Leerzeichen angehängt." #: e2_command_line.c:1023 msgid "show last entry" msgstr "Letztes Verzeichnis zeigen" #: e2_command_line.c:1024 msgid "If activated, the last-entered directory will be displayed, instead of an empty line" msgstr "Wenn aktiviert, wird das letzte eingegebene Verzeichnis angezeigt anstatt einer leeren Zeile." #: e2_command_line.c:1028 msgid "show pathname as a tooltip" msgstr "Pfadname als Schnellhilfe zeigen" #: e2_command_line.c:1029 msgid "If activated, the full directory pathname will display as a tooltip. This is useful when the path is too long for the normal display" msgstr "Wenn aktiviert, so wird der komplette Pfadname als Schnellhilfe angezeigt, wenn der Mauszeiger in der Verzeichnisbox steht. Das ist sehr nützlich, wenn der Pfadname länger als die Verzeichnisbox ist." #: e2_command_line.c:1034 msgid " only" msgstr "Vervöllständigung nur mit Tabulatortaste" #: e2_command_line.c:1034 msgid "inserted" msgstr "möglicher Pfad wird sofort vervöllständigt" #: e2_command_line.c:1034 msgid "selected" msgstr "möglicher Pfad wird hervorgehoben" #: e2_command_line.c:1035 msgid "directory path completion" msgstr "Automatische Pfad-Vervollständigung" #: e2_command_line.c:1036 msgid "This determines the mode of completion when keying a directory-path" msgstr "Hier stellen Sie den Modus der automatischen Pfad-Vervöllständigung ein" #: e2_command_line.c:1042 msgid "This is the largest number of directory-line history entries that will be retained" msgstr "Das ist die maximale Anzahl der Verzeichnisse, die in der Verzeichnis-Chronik aufgezeichnet werden." #: e2_command_line.c:1057 msgid "If activated, the directory line history will be presented as a menu. For most for most Gtk+2 themes, this will not be as attractive as the list view" msgstr "Wenn aktiviert, wird die Verzeichnis-Chronik nicht als Liste sondern als Menü angezeigt." #: e2_config_dialog.c:175 #: e2_config_dialog.c:1484 msgid "configuration" msgstr "Konfiguration" #: e2_config_dialog.c:658 msgid "Reverting to default configuration cannot be undone" msgstr "" "Das Zurücksetzen auf die Standard-Einstellungen kann nicht rückgängig gemacht werden!\n" "Einstellungen wirklich zurücksetzen?" #: e2_config_dialog.c:781 #: e2_option_tree_context_menu.c:243 msgid "_Expand" msgstr "A_usklappen" #: e2_config_dialog.c:782 msgid "Expand all rows" msgstr "Ausklappen aller Unterpunkte" #: e2_config_dialog.c:783 #: e2_option_tree_context_menu.c:247 #: e2_tree_dialog.c:1113 msgid "C_ollapse" msgstr "E_inklappen" #: e2_config_dialog.c:784 msgid "Collapse all rows" msgstr "Einklappen aller Unterpunkte" #: e2_config_dialog.c:922 msgid "choose plugin" msgstr "Wähle Plugin" #: e2_config_dialog.c:1001 #, c-format msgid "Choose font: %s" msgstr "Wähle %s" #: e2_config_dialog.c:1041 msgid "abcd efgh ABCD EFGH" msgstr "abcd efgh ABCD EFGH" #: e2_config_dialog.c:1041 msgid "example:" msgstr "Beispiel:" #: e2_config_dialog.c:1136 msgid "Color data are not stored there" msgstr "Die Farben werden nicht in diesem Bereich eingestellt!" #: e2_config_dialog.c:1154 msgid "The current color descriptor is not valid" msgstr "Die aktuelle Farbdefinition ist falsch." #: e2_config_dialog.c:1161 msgid "Set filetype color" msgstr "Farbe des Dateitypes bearbeiten" #: e2_config_dialog.c:1219 #, c-format msgid "Choose color: %s" msgstr "Wähle Farbe für '%s'" #: e2_config_dialog.c:1264 msgid "abCD" msgstr "abCD" #: e2_config_dialog.c:1264 msgid "currently:" msgstr "Momentan eingestellt:" #: e2_config_dialog.c:1517 #: e2_filetype_dialog.c:872 msgid "Categories" msgstr "Kategorien" #: e2_config_dialog.c:1631 #: e2_config_dialog.c:1658 #: e2p_find.c:2903 msgid "change" msgstr "Ändern" #: e2_config_dialog.c:1632 msgid "Click to open a font select dialog" msgstr "Klicken Sie hier, um den Schriften-Auswahl-Dialog zu öffnen." #: e2_config_dialog.c:1659 msgid "Click to open a color selection dialog" msgstr "Klicken Sie hier, um den Farbauswahl-Dialog zu öffnen." #: e2_config_dialog.c:1771 msgid "_Default" msgstr "_Standard" #: e2_config_dialog.c:1772 msgid "Revert all options to their default settings" msgstr "Es werden alle Optionen auf ihre Grundeinstellung zurückgesetzt!" #: e2_config_dialog.c:1776 msgid "_Basic" msgstr "_Basis" #: e2_config_dialog.c:1777 msgid "Display only the basic configuration options" msgstr "Es werden nur die Grundeinstellungen angezeigt." #: e2_config_dialog.c:1781 msgid "Ad_vanced" msgstr "_Erweitert" #: e2_config_dialog.c:1782 msgid "Display all configuration options" msgstr "Es werden alle Konfigurationseinstellungen angezeigt." #: e2_context_menu.c:209 #: e2_filetype.c:119 #: e2_filetype.c:299 #: e2_filetype.c:493 #: e2_output.c:718 #: e2_task.c:3192 #: e2_task_backend.c:1929 msgid "" msgstr "" #: e2_context_menu.c:273 #: e2_filetype.c:120 #: e2_filetype.c:301 #: e2_filetype.c:501 #: e2_output.c:757 #: e2_task.c:3198 msgid "" msgstr "" #: e2_context_menu.c:552 #: e2p_thumbs.c:1102 msgid "Open _with.." msgstr "Öffnen _mit..." #: e2_context_menu.c:556 msgid "_Info" msgstr "_Eigenschaften" #: e2_context_menu.c:558 msgid "_Actions" msgstr "_Aktionen" #: e2_context_menu.c:559 #: e2_dnd.c:726 #: e2_file_info_dialog.c:246 #: e2_filetype_dialog.c:691 #: e2_option_tree_context_menu.c:231 #: e2_toolbar.c:2816 #: e2_tree_dialog.c:1109 #: e2_view_dialog.c:946 #: e2p_cpbar.c:875 msgid "_Copy" msgstr "_Kopieren" #: e2_context_menu.c:560 #: e2_dnd.c:732 #: e2_toolbar.c:2818 #: e2p_mvbar.c:951 msgid "_Move" msgstr "_Verschieben" #: e2_context_menu.c:561 #: e2_dnd.c:738 #: e2_toolbar.c:2820 msgid "_Link" msgstr "_Link" #: e2_context_menu.c:562 #: e2_toolbar.c:2824 msgid "_Trash" msgstr "_Papierkorb" #: e2_context_menu.c:565 #: e2p_rename.c:1580 msgid "_Rename.." msgstr "_Umbenennen..." #: e2_context_menu.c:566 msgid "Change _owners.." msgstr "Ei_gentümer..." #: e2_context_menu.c:568 msgid "Change _permissions.." msgstr "_Zugriffsrechte..." #: e2_context_menu.c:570 msgid "Copy as.." msgstr "Kopieren als..." #: e2_context_menu.c:571 msgid "Move as.." msgstr "Verschieben als..." #: e2_context_menu.c:572 msgid "Link as.." msgstr "Link als..." #: e2_context_menu.c:573 msgid "_Plugins" msgstr "_Plugins" #: e2_context_menu.c:576 msgid "_Edit plugins.." msgstr "_Plugins bearbeiten..." #: e2_context_menu.c:577 msgid "_User commands" msgstr "Eigene _Befehle" #: e2_context_menu.c:578 msgid "Enter file name:" msgstr "Dateiname:" #: e2_context_menu.c:578 msgid "_Make new file.." msgstr "_Neue Datei erstellen..." #: e2_context_menu.c:579 msgid "The files are different" msgstr "Die Dateien sind verschieden!" #: e2_context_menu.c:579 msgid "The files are identical" msgstr "Die Dateien sind gleich!" #: e2_context_menu.c:579 msgid "_Compare files" msgstr "Dateien _vergleichen" #: e2_context_menu.c:580 msgid "Compare _directories" msgstr "Verzeichnisse _vergleichen" #: e2_context_menu.c:582 msgid "_Remove spaces" msgstr "_Leerzeichen entfernen" #: e2_context_menu.c:583 msgid "Enter the piece-size (in kB):" msgstr "Teilgröße eingeben (in kB):" #: e2_context_menu.c:583 msgid "_Split file.." msgstr "Datei _teilen..." #: e2_context_menu.c:584 msgid "Co_ncatenate files.." msgstr "Dateien _zusammenfügen..." #: e2_context_menu.c:584 msgid "Enter the name of the combined file:" msgstr "Name der zusammengefügten Datei eingeben:" #: e2_context_menu.c:585 msgid "_Free space" msgstr "_Freier Speicherplatz" #: e2_context_menu.c:585 msgid "percent free" msgstr "Prozent frei" #: e2_context_menu.c:587 msgid "_Edit user commands.." msgstr "_Eigene Befehle bearbeiten" #: e2_context_menu.c:588 #: e2_toolbar.c:2826 msgid "Ma_ke dir.." msgstr "_Verzeichnis(se) erstellen..." #: e2_context_menu.c:590 msgid "_Bookmarks" msgstr "_Lesezeichen" #: e2_context_menu.c:591 #: e2_toolbar.c:2979 #: e2_toolbar.c:3044 msgid "Add _top" msgstr "_Lesezeichen hinzufügen (Anfang)" #: e2_context_menu.c:593 #: e2_toolbar.c:2983 #: e2_toolbar.c:3048 msgid "Add _bottom" msgstr "_Lesezeichen hinzufügen (Ende)" #: e2_context_menu.c:595 msgid "_Edit bookmarks.." msgstr "_Lesezeichen bearbeiten..." #: e2_context_menu.c:597 msgid "_Edit filetype.." msgstr "_Dateityp bearbeiten..." #: e2_context_menu.c:618 msgid "Shift" msgstr "Umsch" #: e2_context_menu.c:620 msgid "Ctrl" msgstr "Strg" #: e2_context_menu.c:622 #: e2_keybinding.c:1148 #: e2_toolbar.c:2857 #: e2_toolbar.c:2924 #: e2_toolbar.c:3091 msgid "Action" msgstr "Aktion" #: e2_context_menu.c:625 #: e2_keybinding.c:1151 #: e2_toolbar.c:2860 #: e2_toolbar.c:2927 #: e2_toolbar.c:3094 msgid "Argument" msgstr "Argument" #: e2_date_filter_dialog.c:216 msgid "Display only the items:" msgstr "Dateien mit folgender Bedingung anzeigen:" #: e2_date_filter_dialog.c:217 msgid "date filter" msgstr "Filter nach Datum" #: e2_date_filter_dialog.c:225 #: e2p_glob.c:515 msgid "accessed since" msgstr "Zugriff seit dem" #: e2_date_filter_dialog.c:225 #: e2p_glob.c:515 msgid "modified before" msgstr "Verändert vor dem" #: e2_date_filter_dialog.c:225 #: e2p_glob.c:515 msgid "modified since" msgstr "Verändert seit dem" #: e2_date_filter_dialog.c:226 #: e2p_glob.c:516 msgid "accessed before" msgstr "Zugriff vor dem" #: e2_date_filter_dialog.c:226 #: e2p_glob.c:516 msgid "changed before" msgstr "Geändert vor dem" #: e2_date_filter_dialog.c:226 #: e2p_glob.c:516 msgid "changed since" msgstr "Geändert seit dem" #: e2_dialog.c:684 #: e2_tree_dialog.c:1458 msgid "_Hidden" msgstr "_Versteckte Dateien" #: e2_dialog.c:686 msgid "Toggle display of hidden items" msgstr "Anzeige der versteckten Objekte umschalten" #: e2_dialog.c:831 msgid "user input" msgstr "Datei erstellen" #: e2_dialog.c:1001 msgid "older" msgstr "älter" #: e2_dialog.c:1003 msgid "newer" msgstr "neuer" #: e2_dialog.c:1012 msgid "existing" msgstr "vorhanden" #: e2_dialog.c:1027 #, c-format msgid "" "Remove all contents of %s\n" "%s ?" msgstr "" "Den gesamten Inhalt von %s\n" "%s löschen?" #: e2_dialog.c:1032 #, c-format msgid "" "Overwrite %s %s\n" "in %s ?" msgstr "" "Überschreiben des %sen Objektes %s\n" "in %s ?" #: e2_dialog.c:1039 #: e2_dialog.c:1079 #: e2_dialog.c:1108 #: e2_edit_dialog.c:696 #: e2_edit_dialog.c:928 #: e2_task.c:2117 #: e2p_crypt.c:430 #: e2p_rename.c:899 msgid "confirm" msgstr "Bestätigung" #: e2_dialog.c:1106 #, c-format msgid "%s is taking a long time. Continue waiting ?" msgstr "%s benötigte bereits viel Zeit. Weiter warten?" #: e2_dialog.c:1113 msgid "_Quiet" msgstr "_Stille" #: e2_dialog.c:1114 msgid "Don't ask any more" msgstr "Nicht mehr danach fragen" #: e2_dialog.c:1118 #, c-format msgid "Cancel the %s" msgstr "Abbrechen von '%s'" #: e2_dialog.c:1122 msgid "Wait some more" msgstr "Weiter warten" #: e2_dialog.c:1224 msgid "center" msgstr "Bildschirmmitte" #: e2_dialog.c:1224 msgid "mouse" msgstr "Am Mauszeiger" #: e2_dialog.c:1224 #: e2_output.c:2442 #: e2_toolbar.c:2729 #: e2p_acl.c:3513 msgid "none" msgstr "ohne" #: e2_dialog.c:1225 msgid "always center" msgstr "Immer in der Mitte" #: e2_dialog.c:1225 msgid "center on parent" msgstr "In der Mitte des vorherigen Fensters" #: e2_dialog.c:1227 msgid "dialog position" msgstr "Dialog-Fensterposition" #: e2_dialog.c:1228 msgid "This determines the position where dialog windows will pop up" msgstr "Hier wird die Position der Dialog-Fenster festgelegt." #: e2_dnd.c:749 msgid "C_ancel" msgstr "A_bbrechen" #: e2_edit_dialog.c:119 #: e2_edit_dialog.c:344 msgid "file save as" msgstr "Datei speichern als" #: e2_edit_dialog.c:233 #, c-format msgid "Cannot save %s in its original encoding %s. Reverting to UTF-8" msgstr "Fehler beim Speichern von %s im Original-Zeichensatz %s. Konvertiere zurück nach UTF-8." #: e2_edit_dialog.c:240 #, c-format msgid "Encoding conversion failed for %s" msgstr "Fehler bei der Zeichensatz-Konvertierung von %s!" #: e2_edit_dialog.c:312 #, c-format msgid "Original encoding of '%s' is unknown, now saved as UTF-8" msgstr "Der Original-Zeichensatz von '%s' ist unbekannt, wurde als UTF-8 gespeichert!" #: e2_edit_dialog.c:412 msgid "Reverting to saved version cannot be undone" msgstr "Das Zurücksetzen auf die gesicherte Version kann nicht rückgängig gemacht werden!" #: e2_edit_dialog.c:477 #, c-format msgid "Cannot initialize spell checking: %s" msgstr "Fehler beim Initialisieren der Rechtschreibkontrolle: %s!" #: e2_edit_dialog.c:524 msgid "choose language" msgstr "Sprache auswählen" #: e2_edit_dialog.c:525 msgid "Enter the spell-checker language (like en_CA):" msgstr "Sprache der Rechtschreibkontrolle (z.B. de_DE):" #: e2_edit_dialog.c:535 #, c-format msgid "Cannot set speller language to %s (%s)" msgstr "Fehler beim Einstellen der Sprache: %s (%s)" #: e2_edit_dialog.c:696 msgid "Replace this one ?" msgstr "Diesen Text ersetzen?" #: e2_edit_dialog.c:810 msgid "Spelling suggestions" msgstr "Rechtschreibvorschläge" #: e2_edit_dialog.c:817 #: e2_edit_dialog.c:1538 #: e2p_config.c:1100 msgid "_Save" msgstr "_Speichern" #: e2_edit_dialog.c:818 msgid "Save the file" msgstr "Speichert die Datei" #: e2_edit_dialog.c:820 msgid "Save as.." msgstr "S_peichern als..." #: e2_edit_dialog.c:821 msgid "Save the file with a new name" msgstr "Speichert die Datei mit einem neuen Namen" #: e2_edit_dialog.c:823 msgid "Save se_lection.." msgstr "Auswa_hl speichern..." #: e2_edit_dialog.c:824 msgid "Save the selected text" msgstr "Speichert den ausgewählten Text in eine Datei" #: e2_edit_dialog.c:829 #: e2_view_dialog.c:952 msgid "Print file" msgstr "Datei drucken" #: e2_edit_dialog.c:829 #: e2_view_dialog.c:952 msgid "Print selected text" msgstr "Druckt den ausgewählten Text" #: e2_edit_dialog.c:830 #: e2_view_dialog.c:953 msgid "_Print.." msgstr "_Drucken..." #: e2_edit_dialog.c:834 msgid "R_efresh" msgstr "A_ktualisieren" #: e2_edit_dialog.c:835 msgid "Reload the file being edited" msgstr "Die geöffnete Datei neu laden" #: e2_edit_dialog.c:838 #: e2_toolbar.c:2889 #: e2p_find.c:3667 msgid "_Find.." msgstr "S_uchen..." #: e2_edit_dialog.c:839 msgid "Find matching text" msgstr "Text suchen" #: e2_edit_dialog.c:840 msgid "_Replace.." msgstr "_Ersetzen..." #: e2_edit_dialog.c:841 msgid "Find and replace matching text" msgstr "Text suchen und ersetzen" #: e2_edit_dialog.c:843 #: e2_edit_dialog.c:1549 #: e2_output.c:1346 #: e2_view_dialog.c:1579 msgid "_Hide" msgstr "Vers_tecken" #: e2_edit_dialog.c:844 #: e2_view_dialog.c:1587 msgid "Hide the search options bar" msgstr "Such-Parameter verstecken" #: e2_edit_dialog.c:847 #: e2_edit_dialog.c:1550 msgid "_Undo" msgstr "_Rückgängig" #: e2_edit_dialog.c:848 msgid "Undo last change" msgstr "Letzte Änderung rückgängig machen" #: e2_edit_dialog.c:852 #: e2_edit_dialog.c:1552 msgid "Re_do" msgstr "_Wiederholen" #: e2_edit_dialog.c:853 msgid "Reverse last undo" msgstr "Letzte Änderung wiederholen" #: e2_edit_dialog.c:858 msgid "_Check spelling" msgstr "_Rechtschreibprüfung" #: e2_edit_dialog.c:859 msgid "Flag mis-spelt words" msgstr "Falsch geschriebene Worte markieren" #: e2_edit_dialog.c:861 msgid "_Clear spellcheck" msgstr "Rechtschreibkontrolle _zurücksetzen" #: e2_edit_dialog.c:862 msgid "Remove all spell-check flags" msgstr "Alle Markierungen entfernen" #: e2_edit_dialog.c:867 msgid "_Language.." msgstr "_Sprache..." #: e2_edit_dialog.c:868 msgid "Set spell-checker language" msgstr "Stellt die Sprache der Rechtschreibkontrolle ein." #: e2_edit_dialog.c:872 msgid "_Wrap" msgstr "Zei_lenumbruch" #: e2_edit_dialog.c:880 #: e2_view_dialog.c:1593 msgid "If activated, text in the window will be word-wrapped" msgstr "Wenn aktiviert, wird der Text im Fenster mit Zeilenumbruch angezeigt." #: e2_edit_dialog.c:883 msgid "Se_ttings" msgstr "_Einstellungen" #: e2_edit_dialog.c:884 #: e2_view_dialog.c:959 msgid "Open the configuration dialog at the options page" msgstr "Öffnet die Konfigurationsseite des Betrachters/Editors" #: e2_edit_dialog.c:928 msgid "Save modified file ?" msgstr "Geänderte Datei speichern?" #: e2_edit_dialog.c:1416 msgid "editing file" msgstr "Datei bearbeiten" #: e2_edit_dialog.c:1491 msgid "Replacements" msgstr "Ersetzungstext" #: e2_edit_dialog.c:1495 msgid "If activated, the next match will be sought after each replacement" msgstr "Wenn aktiviert, wird nach jedem Ersetzen zum nächsten Vorkommen gesprungen." #: e2_edit_dialog.c:1495 msgid "r_epeat" msgstr "_wiederholen" #: e2_edit_dialog.c:1498 msgid "If activated, all matches will be replaced at once" msgstr "Wenn aktiviert, werden alle Vorkommen des Suchtextes auf einmal ersetzt." #: e2_edit_dialog.c:1498 msgid "_all" msgstr "_alle" #: e2_edit_dialog.c:1501 msgid "If activated, confirmation will be sought when \"replacing all\"" msgstr "" "Wenn aktiviert, werden Sie vor jeder Erstetzung zu einer Bestätigung aufgefordert.\n" "(Nur, wenn \"alle\" aktiviert ist.)" #: e2_edit_dialog.c:1501 msgid "co_nfirm" msgstr "_bestätigen" #: e2_edit_dialog.c:1509 #: e2_view_dialog.c:1571 msgid "not found" msgstr "nicht gefunden" #: e2_edit_dialog.c:1516 msgid "_Replace" msgstr "_Ersetzen" #: e2_edit_dialog.c:1524 msgid "Replace this match" msgstr "Diesen Text ersetzen" #: e2_edit_dialog.c:1527 #: e2_view_dialog.c:1596 #: e2p_find.c:3627 #: e2p_track.c:421 msgid "_Find" msgstr "_Suchen" #: e2_edit_dialog.c:1535 #: e2_view_dialog.c:1605 msgid "Find the next match" msgstr "Finde das nächste Vorkommen des Suchtextes" #: e2_edit_dialog.c:1705 msgid "backup when saving" msgstr "Sicherheitskopie beim Speichern erstellen" #: e2_edit_dialog.c:1706 msgid "When saving an edited file, an existing file with the same name will be renamed" msgstr "Wenn beim Speichern einer Datei eine gleichnamige bereits existiert, so wird diese umbenannt." #: e2_file_info_dialog.c:207 #: e2_file_info_dialog.c:210 #: e2_file_info_dialog.c:446 #: e2_file_info_dialog.c:478 #: e2_file_info_dialog.c:499 msgid "Type:" msgstr "Objekttyp:" #: e2_file_info_dialog.c:215 msgid "Item:" msgstr "Objekt:" #: e2_file_info_dialog.c:217 #: e2_file_info_dialog.c:645 msgid "Size:" msgstr "Größe:" #: e2_file_info_dialog.c:218 #: e2_file_info_dialog.c:672 msgid "User:" msgstr "Eigentümer:" #: e2_file_info_dialog.c:219 #: e2_file_info_dialog.c:680 msgid "Group:" msgstr "Gruppe:" #: e2_file_info_dialog.c:220 #: e2_file_info_dialog.c:692 msgid "Permissions:" msgstr "Zugriffsrechte:" #: e2_file_info_dialog.c:221 #: e2_file_info_dialog.c:703 msgid "Accessed:" msgstr "Zugriff:" #: e2_file_info_dialog.c:222 #: e2_file_info_dialog.c:717 msgid "Modified:" msgstr "Verändert: " #: e2_file_info_dialog.c:223 #: e2_file_info_dialog.c:731 msgid "Changed:" msgstr "Geändert:" #: e2_file_info_dialog.c:247 msgid "Copy displayed data" msgstr "Kopiert die angezeigten Daten in die Zwischenablage" #: e2_file_info_dialog.c:344 msgid "file info" msgstr "Eigenschaften" #: e2_file_info_dialog.c:365 msgid "Virtual file" msgstr "Virtuelle Datei" #: e2_file_info_dialog.c:381 #: e2_file_info_dialog.c:497 #: e2_utf8.c:339 #: e2_utf8.c:369 #: e2_utf8.c:397 #: e2_utf8.c:427 msgid "Unknown" msgstr "Unbekannt" #: e2_file_info_dialog.c:464 #: e2p_acl.c:3609 #: e2p_crypt.c:2798 msgid "Directory" msgstr "Verzeichnis" #: e2_file_info_dialog.c:487 msgid "Symbolic Link" msgstr "Symbolischer Link" #: e2_file_info_dialog.c:489 msgid "Character Device" msgstr "Zeichenorientiertes Gerät" #: e2_file_info_dialog.c:491 msgid "Block Device" msgstr "Blockorientiertes Gerät" #: e2_file_info_dialog.c:493 msgid "FIFO Pipe" msgstr "FIFO-Pipe" #: e2_file_info_dialog.c:495 msgid "Socket" msgstr "Sockel" #: e2_file_info_dialog.c:523 #, c-format msgid "to %s" msgstr "nach %s" #: e2_file_info_dialog.c:571 msgid "(which is missing)" msgstr "(welches nicht vorhanden ist)" #: e2_file_info_dialog.c:573 msgid "(which is itself)" msgstr "(welches es selbst ist)" #: e2_file_info_dialog.c:588 #, c-format msgid "and ultimately to %s" msgstr "und letztendlich nach %s" #: e2_file_info_dialog.c:602 msgid "(Cannot resolve the link)" msgstr "(Link kann nicht aufgelöst werden)" #: e2_file_info_dialog.c:623 msgid "Encoding:" msgstr "Kodierung:" #: e2_file_info_dialog.c:623 #: e2_file_info_dialog.c:627 #: e2_file_info_dialog.c:633 msgid "Mime:" msgstr "Mime-Typ:" #: e2_file_info_dialog.c:666 #: e2_size_filter_dialog.c:192 #: e2p_du.c:170 #: e2p_find.c:2781 #: e2p_glob.c:501 msgid "bytes" msgstr "Byte" #: e2_file_info_dialog.c:711 msgid "Item was last opened, run, read etc" msgstr "geöffnet, gestartet, gelesen usw." #: e2_file_info_dialog.c:725 msgid "Content of the item was last altered" msgstr "Änderung des Inhaltes" #: e2_file_info_dialog.c:739 msgid "Property of the item (name, permission, owner etc) was last altered" msgstr "Änderung der Eigenschaften (Name, Rechte, Eigentümer usw.)" #: e2_filelist.c:261 msgid "Something is wrong with the auto-refresh mechanism" msgstr "Etwas mit dem Auto-Refresh stimmt nicht!" #: e2_filelist.c:773 #: e2_toolbar.c:1621 msgid "k" msgstr "k" #: e2_filelist.c:778 #: e2_toolbar.c:1626 msgid "M" msgstr "M" #: e2_filetype.c:121 #: e2_filetype.c:307 #: e2_filetype.c:502 msgid "" msgstr "" #: e2_filetype.c:491 #: e2p_du.c:220 msgid "directories" msgstr "Verzeichnisse" #: e2_filetype.c:495 #: e2_filetype.c:602 #: e2_filetype.c:616 #: e2_filetype.c:631 #: e2_filetype.c:645 #: e2_filetype.c:680 #: e2_filetype_dialog.c:1201 #: e2_output.c:1397 msgid "_Open" msgstr "_Öffnen" #: e2_filetype.c:496 #: e2_filetype.c:603 #: e2_filetype.c:617 #: e2_filetype.c:632 #: e2_filetype.c:646 #: e2_filetype.c:681 msgid "O_pen in other" msgstr "Ö_ffnen im anderen Fenster" #: e2_filetype.c:497 msgid "_Mount" msgstr "_Mounten" #: e2_filetype.c:498 msgid "_Unmount" msgstr "_Unmounten" #: e2_filetype.c:499 msgid "executables" msgstr "Ausführbare Dateien" #: e2_filetype.c:505 #: e2_filetype_dialog.c:1198 #: e2p_upgrade.c:742 #: e2p_upgrade.c:743 msgid "_Run" msgstr "Aus_führen" #: e2_filetype.c:508 #: e2_filetype.c:691 msgid "Edit _with.." msgstr "_Bearbeiten mit..." #: e2_filetype.c:508 #: e2_filetype.c:691 #: e2p_upgrade.c:746 #: e2p_upgrade.c:747 msgid "Editor command:" msgstr "Editor-Befehl:" #: e2_filetype.c:509 #: e2_filetype.c:543 #: e2_filetype.c:694 #: e2_filetype_dialog.c:1188 #: e2_output.c:1394 msgid "_Edit" msgstr "_Bearbeiten" #: e2_filetype.c:510 #: e2p_find.c:318 #: e2p_track.c:111 msgid "office documents" msgstr "Office-Dokumente" #: e2_filetype.c:516 #: e2_filetype.c:553 msgid "_Openoffice" msgstr "_OpenOffice" #: e2_filetype.c:517 msgid "HTML documents" msgstr "HTML-Dateien" #: e2_filetype.c:522 msgid "_Firefox" msgstr "_Firefox-Browser" #: e2_filetype.c:523 msgid "_Mozilla" msgstr "_Mozilla-Browser" #: e2_filetype.c:524 msgid "_Lynx" msgstr "_Lynx-Browser" #: e2_filetype.c:525 msgid "_Opera" msgstr "_Opera-Browser" #: e2_filetype.c:526 msgid "PDF documents" msgstr "PDF-Dateien" #: e2_filetype.c:532 msgid "postscript documents" msgstr "Postscript-Dateien" #: e2_filetype.c:537 msgid "text documents" msgstr "Text-Dateien" #: e2_filetype.c:542 msgid "View" msgstr "Betrachten" #: e2_filetype.c:547 msgid "spreadsheets" msgstr "Tabellen" #: e2_filetype.c:554 msgid "audio files" msgstr "Sound-Dateien" #: e2_filetype.c:565 msgid "image files" msgstr "Bilder" #: e2_filetype.c:585 msgid "video files" msgstr "Videos" #: e2_filetype.c:595 msgid "plain tarballs" msgstr "Tar-Archive" #: e2_filetype.c:599 #: e2_filetype.c:605 #: e2_filetype.c:619 #: e2_filetype.c:634 #: e2_filetype.c:648 msgid "Unpack" msgstr "Entpacken (temporär)" #: e2_filetype.c:600 msgid "Unpack in other pane" msgstr "Entpacken im anderen Fenster" #: e2_filetype.c:607 #: e2_filetype.c:621 #: e2_filetype.c:636 #: e2_filetype.c:650 msgid "_List contents" msgstr "_Inhalt anzeigen" #: e2_filetype.c:608 msgid "gzip tarballs" msgstr "Gzip-Archive" #: e2_filetype.c:613 #: e2_filetype.c:628 #: e2p_unpack.c:526 msgid "_Unpack" msgstr "_Entpacken" #: e2_filetype.c:614 #: e2_filetype.c:629 msgid "Unpack in _other pane" msgstr "_Entpacken im anderen Fenster" #: e2_filetype.c:622 msgid "bzip2 tarballs" msgstr "Bzip2-Archive" #: e2_filetype.c:637 msgid "zip archives" msgstr "Zip-Archive" #: e2_filetype.c:641 msgid "_Unzip" msgstr "_Entpacken" #: e2_filetype.c:643 msgid "Unzip in _other pane" msgstr "_Entpacken im anderen Fenster" #: e2_filetype.c:673 msgid "RPM packages" msgstr "RPM-Pakete" #: e2_filetype.c:677 msgid "In_formation" msgstr "In_formation" #: e2_filetype.c:678 msgid "_Install" msgstr "_Installieren" #: e2_filetype.c:683 msgid "source code files" msgstr "Quellcode-Dateien" #: e2_filetype.c:695 msgid "object files" msgstr "Objekt-Dateien" #: e2_filetype.c:701 msgid "_View symbols" msgstr "_Symbole anzeigen" #: e2_filetype.c:727 #: e2_keybinding.c:1142 msgid "Category" msgstr "Kategorie" #: e2_filetype.c:732 #: e2_menu.c:999 msgid "Command" msgstr "Befehl" #: e2_filetype.c:739 msgid "case-insensitive filetypes" msgstr "Groß-/Kleinbuchstaben des Dateitypes unterscheiden" #: e2_filetype.c:740 msgid "This causes text-case to always be ignored when matching a file-extension" msgstr "Wenn aktiviert, wird Groß- und Kleinschreibung des Dateitypes unterschieden." #: e2_filetype_dialog.c:443 msgid "new" msgstr "neu" #: e2_filetype_dialog.c:689 #: e2_option_tree_context_menu.c:227 msgid "Cu_t" msgstr "_Ausschneiden" #: e2_filetype_dialog.c:690 msgid "Cut selected row(s)" msgstr "Markierte Zeile(n) ausschneiden" #: e2_filetype_dialog.c:692 msgid "Copy selected row(s)" msgstr "Markierte Zeile(n) kopieren" #: e2_filetype_dialog.c:697 #: e2_option_tree_context_menu.c:235 msgid "_Paste" msgstr "_Einfügen" #: e2_filetype_dialog.c:698 #: e2_option_tree_context_menu.c:236 msgid "Paste previously copied or cut row(s) after current row" msgstr "Fügt die Zwischenablage hinter der aktuellen Zeile ein." #: e2_filetype_dialog.c:702 #: e2_option_tree_context_menu.c:256 #: e2_permissions_dialog.c:454 #: e2p_acl.c:3725 msgid "_Add" msgstr "_Hinzufügen" #: e2_filetype_dialog.c:703 #: e2_option_tree_context_menu.c:257 msgid "Add a row after the current one" msgstr "Fügt eine neue Zeile nach der aktuellen ein" #: e2_filetype_dialog.c:705 #: e2_filetype_dialog.c:1002 #: e2_option_tree.c:969 #: e2_option_tree_context_menu.c:264 #: e2_toolbar.c:386 #: e2_toolbar.c:2998 #: e2_toolbar.c:3059 #: e2_toolbar.c:3065 msgid "_Up" msgstr "_Hoch" #: e2_filetype_dialog.c:706 #: e2_option_tree_context_menu.c:265 msgid "Move selected row up" msgstr "Bewegt die aktuelle Zeile nach oben." #: e2_filetype_dialog.c:707 #: e2_filetype_dialog.c:1008 #: e2_option_tree.c:972 #: e2_option_tree_context_menu.c:267 #: e2_toolbar.c:388 msgid "_Down" msgstr "_Runter" #: e2_filetype_dialog.c:708 #: e2_option_tree_context_menu.c:268 msgid "Move selected row down" msgstr "Bewegt die aktuelle Zeile nach unten." #: e2_filetype_dialog.c:834 msgid "edit filetypes" msgstr "Dateityp bearbeiten" #: e2_filetype_dialog.c:903 msgid "Extensions" msgstr "Dateierweiterungen" #: e2_filetype_dialog.c:928 msgid "Labels" msgstr "Bezeichnungen" #: e2_filetype_dialog.c:937 msgid "Commands" msgstr "Befehle" #: e2_filetype_dialog.c:1006 #: e2_option_tree.c:970 msgid "Move the selected row one place up" msgstr "Bewegt die aktuelle Zeile nach oben." #: e2_filetype_dialog.c:1012 #: e2_option_tree.c:973 msgid "Move the selected row one place down" msgstr "Bewegt die aktuelle Zeile nach unten." #: e2_filetype_dialog.c:1014 #: e2_option_tree.c:980 #: e2p_acl.c:3795 msgid "Add a_fter" msgstr "_Neu" #: e2_filetype_dialog.c:1018 msgid "Add a row after the currently selected one" msgstr "Es wird eine neue Zeile nach der aktuellen Zeile eingefügt." #: e2_filetype_dialog.c:1157 msgid "ambiguous filetype" msgstr "mehrdeutiger Dateityp" #: e2_filetype_dialog.c:1157 msgid "unrecognised filetype" msgstr "Nicht zugeordneter Dateityp" #: e2_filetype_dialog.c:1162 #, c-format msgid "" "What would you like to do with\n" "%s\n" "in %s ?" msgstr "" "Was möchten Sie mit\n" "'%s' tun\n" "in %s ?" #: e2_filetype_dialog.c:1173 msgid "_Add.." msgstr "_Hinzufügen" #: e2_filetype_dialog.c:1174 msgid "Create a new filetype for this extension, or add it to an existing filetype" msgstr "Eine neue Zuordung für diesen Dateityp erstellen oder zu einer bestehenden Zuordnung hinzufügen" #: e2_filetype_dialog.c:1189 msgid "Edit the file" msgstr "Datei bearbeiten" #: e2_filetype_dialog.c:1199 msgid "Execute the item" msgstr "Objekt ausführen" #: e2_filetype_dialog.c:1202 msgid "Open with the default application" msgstr "Mit der Standard-Anwendung öffnen" #: e2_filetype_dialog.c:1206 msgid "_Open.." msgstr "Öffnen _mit..." #: e2_filetype_dialog.c:1207 msgid "Enter a command with which to open the file" msgstr "Einen Befehl eingeben, mit dem die Datei geöffnet werden soll" #: e2_fileview.c:155 #: e2_ownership_dialog.c:217 #: e2_permissions_dialog.c:327 #: e2_plugins.c:1284 #: e2p_acl.c:3467 #: e2p_crypt.c:2805 #: e2p_times.c:697 msgid "Filename" msgstr "Dateiname" #: e2_fileview.c:156 msgid "Size" msgstr "Größe" #: e2_fileview.c:157 #: e2_permissions_dialog.c:386 #: e2p_acl.c:3573 msgid "Permissions" msgstr "Rechte" #: e2_fileview.c:158 msgid "Owner" msgstr "Eigentümer" #: e2_fileview.c:159 #: e2_permissions_dialog.c:355 #: e2_permissions_dialog.c:410 #: e2p_acl.c:229 #: e2p_acl.c:2093 #: e2p_acl.c:3495 msgid "Group" msgstr "Gruppe" #: e2_fileview.c:160 msgid "Modified" msgstr "Verändert" #: e2_fileview.c:161 #: e2p_times.c:716 msgid "Accessed" msgstr "Zugriff" #: e2_fileview.c:162 msgid "Changed" msgstr "Geändert" #: e2_fs.c:711 #, c-format msgid "'%s' is not a directory" msgstr "'%s' ist kein Verzeichnis!" #: e2_fs.c:720 #, c-format msgid "Cannot access directory '%s' - No permission" msgstr "Zugriff auf Verzeichnis '%s' nicht möglich!" #: e2_fs.c:725 #, c-format msgid "Directory '%s' does not exist" msgstr "Das Verzeichnis '%s' existiert nicht!" #: e2_fs.c:1293 #: e2_fs.c:1378 msgid "Reading directory data" msgstr "Verzeichnisdaten lesen" #: e2_fs.c:1294 msgid "directory read" msgstr "Verzeichnis lesen" #: e2_fs.c:2082 #, c-format msgid "Cannot open '%s' for writing - %s" msgstr "Fehler beim Öffnen von '%s' zum Schreiben -%s!" #: e2_fs.c:2127 #, c-format msgid "Error writing file '%s' - %s" msgstr "Fehler beim Schreiben der Datei '%s' - %s!" #: e2_fs.c:2243 #: e2_fs.c:2289 #: e2_fs.c:2514 #: e2_view_dialog.c:491 #: e2p_config.c:652 #: e2p_crypt.c:1720 #: e2p_dircmp.c:440 #, c-format msgid "Error reading file %s" msgstr "Fehler beim Lesen der Datei '%s'!" #: e2_fs.c:2272 #: e2_fs.c:2465 #: e2_fs_walk.c:311 #: e2_fs_walk.c:471 #: e2_fs_walk.c:700 #: e2_output.c:825 #: e2_task_backend.c:1261 #: e2p_acl.c:1808 #: e2p_acl.c:1882 #, c-format msgid "Cannot get information about %s" msgstr "Kann keine Informationen erhalten über %s" #: e2_fs.c:2279 #, c-format msgid "Cannot open file %s" msgstr "Fehler beim Öffnen der Datei %s" #: e2_fs.c:2350 #: e2_fs.c:2454 #, c-format msgid "Cannot create file %s" msgstr "Erstellen der Datei '%s' nicht möglich!" #: e2_fs.c:2362 #: e2_fs.c:2529 #: e2p_crypt.c:1752 #, c-format msgid "Error writing file %s" msgstr "Fehler beim Schreiben der Datei '%s'!" #: e2_fs.c:2440 #: e2p_crypt.c:867 #: e2p_crypt.c:1088 #: e2p_dircmp.c:408 #, c-format msgid "Cannot open '%s' for reading" msgstr "Fehler beim Öffnen von '%s' zum Lesen!" #: e2_fs.c:2589 #, c-format msgid "Cannot open pipe for command '%s'" msgstr "Fehler beim Erzeugen der Pipe für den Befehl '%s'!" #: e2_fs.c:2612 #, c-format msgid "Command %s failed: not enough memory" msgstr "Kommando %s konnte nicht ausgeführt werden: nicht genug Speicher" #: e2_fs.c:2805 msgid "-rwxrwxrwx" msgstr "-rwxrwxrwx" #: e2_fs.c:2810 msgid "ldbcfs" msgstr "" #: e2_fs.c:2815 msgid "TtSs" msgstr "" #: e2_fs_walk.c:176 #, c-format msgid "Cannot change anything in %s" msgstr "Keine Änderungen in '%s' möglich!" #: e2_fs_walk.c:176 #: e2_fs_walk.c:652 #: e2_task_backend.c:160 #: e2p_find.c:1766 #: e2p_find.c:2337 #: e2p_times.c:359 #, c-format msgid "Cannot change permissions of %s" msgstr "Ändern der Zugriffsrechte von '%s' nicht möglich!" #: e2_fs_walk.c:333 #: e2_fs_walk.c:341 #, c-format msgid "Directory %s not opened" msgstr "Fehler! Verzeichnis %s ist nicht geöffnet!" #: e2_fs_walk.c:386 #, c-format msgid "Cannot open directory %s" msgstr "Fehler beim Öffnen des Verzeichnisses %s!" #: e2_keybinding.c:840 #, c-format msgid "Cannot find a key binding named %s" msgstr "Tastenkürzel %s nicht gefunden!" #: e2_keybinding.c:850 #: e2_option.c:1089 msgid "key bindings" msgstr "Tastenkürzel" #: e2_keybinding.c:1062 msgid "Now press one of h,m,d\\n" msgstr "Drücken Sie jetzt h (Home-Verzeichnis), m (Mount-Punkte) oder d (Download-Verzeichnis)\\n" #: e2_keybinding.c:1144 msgid "Key" msgstr "Taste(n)" #: e2_keybinding.c:1146 msgid "Continue" msgstr "Weiter" #: e2_keybinding.c:1160 msgid "chained keybindings timeout (ms)" msgstr "Timeout für verkettete Tastenkürzel (ms)" #: e2_keybinding.c:1161 msgid "This sets the time limit (in milliseconds) for accepting 'chained' keybindings" msgstr "Dieser Parameter setzt das Zeitlimit für das Akzeptieren von verketteten Tastenkürzel." #: e2_main.c:231 #, c-format msgid "Your current locale is '%s'.\n" msgstr "Die aktuelle Spracheinstellung ist '%s'.\n" #: e2_main.c:233 #, c-format msgid "" "You have set the environment variable G_BROKEN_FILENAMES, which\n" "causes GTK+ to convert filename encoding, from the one specified\n" "by the system locale, to UTF-8.\n" "However, you have not set a system locale. Please do so, by setting\n" "the environment variable LANG or LC_CTYPE!\n" msgstr "" #: e2_main.c:241 #, c-format msgid "" "(Note: There is a command line option -i/--ignore-problems, but use it\n" "at your own risk!)\n" msgstr "" "(Hinweis: Es gibt eine Kommandozeilen-Option -i/--ignore-problems, aber benutzen Sie diese auf\n" "eigene Gefahr!)\n" #: e2_main.c:246 #, c-format msgid "" "%s will ignore locale problems when reading filenames because\n" "--ignore-problems/-i has been set. This might result in segfaults and all\n" "kind of problems. You really should set a system locale with the\n" "LANG or LC_CTYPE environment variable.\n" msgstr "" #: e2_main.c:342 msgid "emelFM2" msgstr "emelFM2" #: e2_main.c:558 #, c-format msgid "%u process(es) are running" msgstr "%u Kindprozesse laufen" #: e2_menu.c:745 msgid "no children" msgstr "keine Kindprozesse" #: e2_menu.c:766 msgid "_Name filter" msgstr "Filter nach _Name" #: e2_menu.c:768 msgid "_Size filter" msgstr "Filter nach _Größe" #: e2_menu.c:770 msgid "_Date filter" msgstr "Filter nach _Datum" #: e2_menu.c:773 msgid "_Directories too" msgstr "Auch _Verzeichnisse filtern" #: e2_menu.c:780 msgid "_Remove all filters" msgstr "Alle _Filter entfernen" #: e2_menu.c:990 #: e2_plugins.c:1276 msgid "Menu" msgstr "Menü" #: e2_mkdir_dialog.c:58 msgid "new directory" msgstr "neues Verzeichnis" #: e2_mkdir_dialog.c:332 msgid "yes" msgstr "ja" #: e2_mkdir_dialog.c:339 msgid "no" msgstr "nein" #: e2_mkdir_dialog.c:411 #, c-format msgid "cannot write to '%s' - %s" msgstr "Schreiben in '%s' nicht möglich: %s" #: e2_mkdir_dialog.c:419 #, c-format msgid "cannot write to parent directory - %s" msgstr "Schreiben ins Elternverzeichnis nicht möglich - %s!" #: e2_mkdir_dialog.c:427 #, c-format msgid "only '%s' exists - %s" msgstr "Es existiert nur '%s' - %s!" #: e2_mkdir_dialog.c:456 msgid "the directory already exists." msgstr "Das Verzeichnis existiert bereits!" #: e2_mkdir_dialog.c:458 msgid "something is in the way." msgstr "Irgendetwas stimmt nicht!" #: e2_mkdir_dialog.c:468 msgid "directories will be created" msgstr "Verzeichnisse werden erstellt" #: e2_mkdir_dialog.c:656 #: e2_mkdir_dialog.c:701 #: e2_task_backend.c:925 #, c-format msgid "Cannot create directory %s" msgstr "Erstellen des Verzeichnisses '%s' nicht möglich!" #: e2_mkdir_dialog.c:902 msgid "What is the new directory's name?" msgstr "Name des neuen Verzeichnisses:" #: e2_mkdir_dialog.c:902 msgid "create directory" msgstr "Verzeichnis(se) erstellen" #: e2_mkdir_dialog.c:927 msgid "parent directory:" msgstr "Erstellen im Verzeichnis:" #: e2_mkdir_dialog.c:933 msgid "creation possible:" msgstr "Erstellen möglich:" #: e2_mkdir_dialog.c:1003 msgid "open info frame" msgstr "Infobereich anzeigen" #: e2_mkdir_dialog.c:1004 msgid "This causes make-directory dialogs to start with extra information displayed" msgstr "Wenn aktiviert, wird der Info-Bereich im Dialog zum Erstellen von Verzeichnissen immer aufgeklappt." #: e2_mkdir_dialog.c:1007 msgid "follow active-pane directory" msgstr "Folge dem Verzeichnis im aktiven Fenster" #: e2_mkdir_dialog.c:1008 msgid "This makes the parent directory for new directories the same as the one in the active pane, even if the latter changes" msgstr "Diese Option legt fest, dass stets das Verzeichnis des aktiven Fensters beim Erstellen neuer Verzeichnisse als übergeordnetes Verzeichnis verwendet wird." #: e2_mkdir_dialog.c:1012 msgid "suggest directory name" msgstr "Neuen Verzeichnisname vorschlagen" #: e2_mkdir_dialog.c:1013 msgid "This presents a suggested name for each new directory, based on the last-created directory with an increasing number appended" msgstr "Wenn aktiviert, so schlägt emelFM2 einen neuen Name für das zu erstellende Verzeichnis basierend auf das zuletzt erstellte vor." #: e2_mkdir_dialog.c:1017 msgid "show last directory name" msgstr "Letztes Verzeichnis anzeigen" #: e2_mkdir_dialog.c:1018 msgid "This causes the name of the last-created directory to be shown in the entry field, after opening the dialog or when creating another directory" msgstr "Wenn aktiviert, so wird der Name des zuletzt erstellten Verzeichnisses im Dialogfenster angezeigt." #: e2_mkdir_dialog.c:1021 msgid "replicate changes" msgstr "Änderungen im Dialogfenster übernehmen" #: e2_mkdir_dialog.c:1022 msgid "This causes option-changes to be replicated in other mkdir dialogs. Otherwise such changes will be confined to the current dialog" msgstr "Wenn aktiviert, werden die im Dialogfenster vorgenommenen Änderungen der Optionen generell übernommen." #: e2_name_filter_dialog.c:57 #: e2p_glob.c:301 msgid "Invalid filename pattern" msgstr "Ungültiges Muster für Dateinamen!" #: e2_name_filter_dialog.c:161 msgid "Display only the items named like:" msgstr "Nur Dateien mit folgender Maske anzeigen:" #: e2_name_filter_dialog.c:162 msgid "name filter" msgstr "Filter nach Name" #: e2_name_filter_dialog.c:175 #: e2p_glob.c:438 msgid "example: *.c,*.h" msgstr "Beispiel: *.c,*.h (mehrere Masken durch Komma trennen)" #: e2_name_filter_dialog.c:178 msgid "Invert" msgstr "Invertieren" #: e2_name_filter_dialog.c:186 msgid "Show files that DO NOT match the given mask" msgstr "Objekte anzeigen, die NICHT der Maske entsprechen" #: e2_name_filter_dialog.c:187 msgid "Case sensitive" msgstr "Groß-/Kleinbuchstaben unterscheiden" #: e2_option.c:181 #, c-format msgid "Cannot create trash directory %s" msgstr "Das Verzeichnis für den Papierkorb '%s' konnte nicht erstellt werden." #: e2_option.c:346 msgid "Configuration data re-loaded" msgstr "Konfiguration wurde neu geladen." #: e2_option.c:611 #, c-format msgid "" "# This is the %s configuration data file.\n" "# It will be overwritten each time the program is run!\n" "\n" "# If you're inclined to edit the file between program sessions, note this:\n" "# for tree options, you have to use \\| to escape | and you have to use \\< to escape <,\n" "# if that is the first non-space character on a line.\n" "\n" msgstr "" "# Das ist die Konfigurationsdatei von %s.\n" "# Sie wird immer beim Beenden des Programmes überschrieben!\n" "\n" #: e2_option.c:704 #, c-format msgid "Cannot write config file %s - %s" msgstr "Fehler beim Schreiben der Konfigurationsdatei %s - %s!" #: e2_option.c:1067 msgid "aliases" msgstr "Kurzbefehle" #: e2_option.c:1068 msgid "bookmarks" msgstr "Lesezeichen" #: e2_option.c:1069 msgid "colors" msgstr "Farben" #: e2_option.c:1070 msgid "columns" msgstr "Spalten" #: e2_option.c:1071 msgid "command bar" msgstr "Kommandoleiste" #: e2_option.c:1072 msgid "command line" msgstr "Befehlszeile" #: e2_option.c:1074 msgid "confirmation" msgstr "Bestätigungen" #: e2_option.c:1075 msgid "context menu" msgstr "Kontextmenü" #: e2_option.c:1076 msgid "custom menus" msgstr "Eigenes Menü" #: e2_option.c:1077 msgid "delays" msgstr "Verzögerungen" #: e2_option.c:1078 msgid "dialogs" msgstr "Dialogfenster" #: e2_option.c:1079 msgid "directory lines" msgstr "Verzeichnisbox" #: e2_option.c:1080 msgid "extensions" msgstr "Dateierweiterungen" #: e2_option.c:1081 msgid "file actions" msgstr "Datei-Aktionen" #: e2_option.c:1082 msgid "filetypes" msgstr "Dateitypen" #: e2_option.c:1083 msgid "fonts" msgstr "Schriften" #: e2_option.c:1084 msgid "general" msgstr "emelFM2" #: e2_option.c:1086 #: e2p_config.c:1266 #: e2p_config.c:1301 #: e2p_config.c:1323 msgid "icons" msgstr "Icons" #: e2_option.c:1087 msgid "interface" msgstr "Oberfläche" #: e2_option.c:1088 msgid "item types" msgstr "Objekttypen" #: e2_option.c:1090 msgid "make directory" msgstr "Verzeichnis(se) erstellen" #: e2_option.c:1091 msgid "menus" msgstr "Menüs" #: e2_option.c:1092 msgid "miscellaneous" msgstr "Allgemeines" #: e2_option.c:1093 msgid "options" msgstr "Optionen" #: e2_option.c:1095 msgid "pane 1" msgstr "Fenster 1" #: e2_option.c:1096 msgid "pane1 bar" msgstr "Fensterleiste 1" #: e2_option.c:1097 msgid "pane 2" msgstr "Fenster 2" #: e2_option.c:1098 msgid "pane2 bar" msgstr "Fensterleiste 2" #: e2_option.c:1100 msgid "plugins" msgstr "Plugins" #: e2_option.c:1101 msgid "position" msgstr "Position" #: e2_option.c:1103 msgid "startup" msgstr "Start" #: e2_option.c:1104 msgid "style" msgstr "Stil" #: e2_option.c:1105 msgid "tab completion" msgstr "Automatische Vervollständigung (Tabulator)" #: e2_option.c:1106 msgid "task bar" msgstr "Werkzeugleiste" #: e2_option__default.c:70 msgid "show all options in config dialogs" msgstr "Alle Optionen in den Konfigurationsdialogen zeigen." #: e2_option__default.c:74 msgid "reload config on external change" msgstr "Konfiguration erneut laden bei externer Änderung" #: e2_option__default.c:75 msgid "This enables automatic reloading of the configuration data for this program, if that data is changed by another program instance" msgstr "Wenn aktiviert, erfolgt ein automatisches Laden der Konfigurationseinstellungen von emelFM2, wenn diese durch eine andere Programm-Instanz verändert wurde." #: e2_option__default.c:85 msgid "document containing usage advice" msgstr "Dokument, welches Hinweise zur Benutzung enthält" #: e2_option__default.c:86 msgid "This document is opened from the help dialog usage page" msgstr "Dieses Dokument wird bei der generellen Hilfe von emelFM2 angezeigt." #: e2_option__default.c:92 msgid "document containing configuration advice" msgstr "Dokument, welches Hinweise zur Konfiguration enthält" #: e2_option__default.c:93 msgid "This document is opened from the help dialog configuration page" msgstr "Dieses Dokument wird bei der Konfiguration von emelFM2 angezeigt." #: e2_option__default.c:98 msgid "warn about running processes when shutting down" msgstr "Vor dem Beenden warnen, wenn noch Kindprozesse laufen" #: e2_option__default.c:99 msgid "This enables a reminder about incomplete commands and actions" msgstr "Wenn aktiviert, wird vor dem Beenden von emelFM2 eine Warnung über nicht abgeschlossene Befehle und Aktionen ausgegeben." #: e2_option__default.c:115 msgid "opacity" msgstr "Transparenz" #: e2_option__default.c:116 msgid "Window translucence, 30 (faint) to 100 (opaque)" msgstr "Fenstertransparenz, 30 (durchsichtig) bis 100 (undurchsichtig)" #: e2_option__default.c:120 msgid "'go up' on middle-button click" msgstr "Mit der mittleren Maustaste eine Verzeichnisebene zurück springen" #: e2_option__default.c:121 msgid "This is a faster alternative to double clicking '..' or clicking the 'go up' button" msgstr "Das ist eine etwas schnellere Alternative, um eine Verzeichnisebene höher zu springen." #: e2_option__default.c:123 msgid "match windows (TM) right-click behaviour" msgstr "Auswahl auch mit der rechten Maustaste" #: e2_option__default.c:124 msgid "If activated, clicking the right mouse button will also select the row where the mouse cursor is" msgstr "Wenn aktiviert, so wird das an der Mauszeigerposition befindliche Objekt beim Klick auf die rechte Maustaste ausgewählt." #: e2_option__default.c:127 msgid "advanced windows (TM) right-click behaviour" msgstr "Aktuelle Auswahl beim rechten Mausklick auf einen freien Bereich nicht löschen" #: e2_option__default.c:128 msgid "If activated, clicking on a free area will not clear the current selection" msgstr "Wenn aktiviert, so wird beim Klicken mit der rechten Maustaste auf einen freien Bereich im Fenster die aktuelle Auswahl nicht entfernt." #: e2_option__default.c:131 msgid "show icons in dialog buttons" msgstr "Icons in den Dialog-Buttons zeigen" #: e2_option__default.c:132 msgid "If activated, dialog buttons will show an icon as well as a label" msgstr "Wenn aktiviert, werden Icons in den Dialogfenstern angezeigt." #: e2_option__default.c:137 msgid "use icons directory" msgstr "Eigenes Icon-Verzeichnis benutzen" #: e2_option__default.c:138 msgid "If activated, icon files in the directory shown below will be used for toolbars etc" msgstr "Wenn aktiviert, so werden die Icons im nachstehenden Verzeichnis verwendet." #: e2_option__default.c:142 msgid "icons directory" msgstr "Icon-Verzeichnis:" #: e2_option__default.c:143 msgid "The directory from which icon files will be retrieved" msgstr "Hier wird das Verzeichnis mit den eigenen Icons eingetragen." #: e2_option__default.c:155 msgid "show icons in menus" msgstr "Icons in den Menüs zeigen" #: e2_option__default.c:156 msgid "Some people think that icons help you recognize items more quickly" msgstr "Wenn aktiviert, werden Icons in den Menüs angezeigt." #: e2_option__default.c:160 #: e2_option__default.c:329 #: e2_toolbar.c:2794 msgid "button" msgstr "Buttons" #: e2_option__default.c:160 #: e2_option__default.c:329 #: e2_toolbar.c:2794 msgid "dnd" msgstr "Drag & Drop" #: e2_option__default.c:160 #: e2_option__default.c:329 #: e2_toolbar.c:2794 msgid "menu" msgstr "Menü" #: e2_option__default.c:160 #: e2_option__default.c:329 #: e2_toolbar.c:2794 msgid "toolbar large" msgstr "Werkzeugleiste (groß)" #: e2_option__default.c:160 #: e2_option__default.c:329 #: e2_toolbar.c:2794 msgid "toolbar small" msgstr "Werkzeugleiste (klein)" #: e2_option__default.c:161 msgid "menu icon size" msgstr "Größe der Menü-Icons" #: e2_option__default.c:162 msgid "This sets the icon size for ALL menus" msgstr "Hier wird die Größe der Icons für alle Menüs eingestellt." #: e2_option__default.c:167 msgid "menu popup delay (ms)" msgstr "Verzögerung vor dem Aufklappen eines Menüs (ms)" #: e2_option__default.c:168 msgid "The delay (in milliseconds) after the mouse pointer arrives at a menu item, before any submenu will pop up" msgstr "Dieser Wert entspricht der Verzögerung, bevor ein Untermenü automatisch geöffnet wird, wenn der Mauszeiger darauf zeigt." #: e2_option__default.c:171 msgid "menu popdown delay (ms)" msgstr "Verzögerung vor dem Zuklappen eines Menüs (ms)" #: e2_option__default.c:172 msgid "The delay (in milliseconds) after the mouse pointer leaves a menu item, before popping down an activated submenu" msgstr "Dieser Wert entspricht der Verzögerung, bevor ein Untermenü automatisch geschlossen wird, wenn der Mauszeiger es verlässt." #: e2_option__default.c:181 msgid "auto refresh" msgstr "Automatisch aktualisieren" #: e2_option__default.c:182 msgid "This enables automatic checking for changes to the content of displayed directories. Otherwise you need to refresh manually" msgstr "Wenn aktiviert, wird der Inhalt der Fenster automatisch aktualisiert." #: e2_option__default.c:185 msgid "focus the pane after completing a directory entry" msgstr "Fenster nach Eingabe des Pfades aktivieren" #: e2_option__default.c:186 msgid "If activated, after return/enter is pressed in a directory line, the pane containing that dir line will become the active one" msgstr "Wenn aktiviert, wird nach Eingabe des Pfades in der Verzeichnisbox und drücken der Enter-Taste das jeweilige Fenster aktiv." #: e2_option__default.c:190 msgid "select first item in newly-opened directories" msgstr "erstes Objekt im neu geöffneten Verzeichnis wählen" #: e2_option__default.c:191 msgid "If activated, when a directory is first displayed in a session, the first item there will be selected" msgstr "Wenn aktiviert, wird immer das erste Objekt des geöffneten Verzeichnisses gewählt." #: e2_option__default.c:196 msgid "use horizontal panes" msgstr "Horizontale Fensterteilung" #: e2_option__default.c:197 msgid "Horizontal (vertical-stacked) panes present more columns at once" msgstr "" "Wenn aktiviert, so werden die Fenster untereinander angeordnet.\n" "(Das hat den Vorteil, dass mehr Spalten angezeigt werden können.)" #: e2_option__default.c:205 msgid "bottom-left" msgstr "unten und links" #: e2_option__default.c:205 msgid "bottom-right" msgstr "unten und rechts" #: e2_option__default.c:205 msgid "top-left" msgstr "oben und links" #: e2_option__default.c:205 msgid "top-right" msgstr "oben und rechts" #: e2_option__default.c:206 msgid "scrollbar position" msgstr "Bildlaufleisten-Position" #: e2_option__default.c:207 msgid "The default (bottom-right) should be ok for most users" msgstr "" "Hier stellen Sie die Position der horizontalen und vertikalen Bildlaüfleisten ein.\n" "(Standard: unten und rechts)" #: e2_option__default.c:210 msgid "bold name header" msgstr "Überschriften in Fettschrift" #: e2_option__default.c:210 msgid "colored headers" msgstr "Einfärben der Spaltenköpfe" #: e2_option__default.c:210 msgid "sensitivity" msgstr "Nicht aktives Fenster grau" #: e2_option__default.c:211 msgid "type of indicator for active pane" msgstr "Art der Hervorhebung des aktiven Fensters" #: e2_option__default.c:212 msgid "This determines how the active pane is indicated on-screen. Some GTK themes do not allow column-header re-coloring" msgstr "" "Hier stellen Sie ein, wie das aktive Fenster zu erkennen ist.\n" "(Manche GTK-Themen erlauben kein Einfärben der Spaltenköpfe.)" #: e2_option__default.c:216 msgid "banded background" msgstr "Gestreiften Hintergrund verwenden" #: e2_option__default.c:217 msgid "If activated, lines in file lists will alternate background color" msgstr "Wenn aktiviert, so wird jede zweite Zeile im Fenster durch einen anderen Hintergrund hervorgehoben." #: e2_option__default.c:221 #: e2_output.c:2457 #: e2_view_dialog.c:1862 msgid "use custom font" msgstr "Eigene Schriftart verwenden" #: e2_option__default.c:222 #: e2_output.c:2458 #: e2_view_dialog.c:1863 msgid "If activated, the font specified below will be used, instead of the theme default" msgstr "Wenn aktiviert, wird die nachstehende Schriftart anstatt der Schriftart des GTK-Themas benutzt." #: e2_option__default.c:225 msgid "custom font" msgstr "Schriftart für Fenster" #: e2_option__default.c:226 msgid "This is the font used for flle pane text" msgstr "Hier wird die Schriftart zu Darstellung der Dateiliste in den Fenstern festgelegt." #: e2_option__default.c:229 msgid "Default: May 20 09:11" msgstr "Grundeinstellung: May 20 09:11" #: e2_option__default.c:230 msgid "American: 05/20/04 09:11" msgstr "Amerikanisch: 05/20/04 09:11" #: e2_option__default.c:230 msgid "Standard: 20/05/04 09:11" msgstr "Standard: 20/05/04 09:11" #: e2_option__default.c:231 msgid "LC_TIME locale specified" msgstr "Länderspezifisch laut LC_TIME" #: e2_option__default.c:232 msgid "date format" msgstr "Datumsformat" #: e2_option__default.c:233 msgid "This determines the format of all dates displayed in the panes" msgstr "Hier stellen Sie die Darstellungsform des Datums in den Fenstern ein." #: e2_option__default.c:237 msgid "condensed" msgstr "verkürzt" #: e2_option__default.c:237 msgid "exact" msgstr "exakt" #: e2_option__default.c:238 msgid "size format" msgstr "Format der Größenangaben" #: e2_option__default.c:239 msgid "Displayed item-sizes can be 'condensed' to show kB, MB where appropriate" msgstr "Die Dateigröße wird bei 'verkürzt' in kB und MB angezeigt, sofern sinnvoll." #: e2_option__default.c:243 msgid "show parent directory entry '..' in file lists" msgstr "Übergeordnetes Verzeichnis durch '..' in den Fenstern anzeigen" #: e2_option__default.c:244 msgid "This slows status-line updates" msgstr "Wenn aktiviert, wird das übergeordnete Verzeichnis durch '..' dargestellt." #: e2_option__default.c:246 msgid "filename sort is case sensitive" msgstr "Groß-/Kleinschreibung bei der Sortierung der Dateinamen berücksichtigen" #: e2_option__default.c:247 msgid "This places all the capitalised file/directory names ahead of the others, if your LANG_C suports that" msgstr "Wenn aktiviert, werden alle Dateien/Verzeichnisse, die mit Großbuchstaben beginnen, vor den anderen Einträgen angezeigt." #: e2_option__default.c:250 msgid "both panes are like pane 1" msgstr "Beide Fenster wie Fenster 1" #: e2_option__default.c:252 #: e2_option__default.c:258 #, c-format msgid "This makes pane %d options (other than toolbar placement and content) apply to pane %d" msgstr "Wenn aktiviert, werden die Einstellungen des %d. Fensters auch vom %d. Fenster übernommen." #: e2_option__default.c:256 msgid "both panes are like pane 2" msgstr "Beide Fenster wie Fenster 2" #: e2_option__default.c:268 msgid "executable" msgstr "Ausführbare Datei" #: e2_option__default.c:269 msgid "Executable file names are listed in this color" msgstr "Ausführbare Dateien werden in dieser Farbe ausgelistet." #: e2_option__default.c:271 #: e2p_du.c:220 #: e2p_find.c:3018 msgid "directory" msgstr "Verzeichnis" #: e2_option__default.c:272 msgid "Directory names are listed in this color" msgstr "Verzeichnisse werden in dieser Farbe ausgelistet." #: e2_option__default.c:275 msgid "Symbolic link names are listed in this color" msgstr "Symbolische Links werden in dieser Farbe ausgelistet." #: e2_option__default.c:277 msgid "device" msgstr "Gerät" #: e2_option__default.c:278 msgid "Device names are listed in this color" msgstr "Gerätenamen werden in dieser Farbe ausgelistet." #: e2_option__default.c:280 #: e2p_find.c:3051 msgid "socket" msgstr "Socket" #: e2_option__default.c:281 msgid "Sockets are listed in this color" msgstr "Socket-Namen werden in dieser Farbe ausgelistet." #: e2_option__default.c:286 msgid "custom background color" msgstr "Eigene Hintergrund-Farbe" #: e2_option__default.c:287 msgid "If enabled, the color specified below will be used for background" msgstr "Wenn aktiviert, wird die nachstehende Farbe für den Hintergrund benutzt." #: e2_option__default.c:290 msgid "background color" msgstr "Hintergrund-Farbe" #: e2_option__default.c:291 msgid "Background color used instead of theme color" msgstr "Hintergrund-Farbe anstatt Themen-Farbe verwenden" #: e2_option__default.c:295 msgid "active pane header color" msgstr "Farbe des aktiven Fensters" #: e2_option__default.c:296 msgid "This color is used for the background of column headers in the active pane" msgstr "Der Hintergrund der Spaltenköpfe des aktiven Fensters wird in dieser Farbe dargestellt." #: e2_option__default.c:298 msgid "highlight color" msgstr "Hintergrundfarbe markierter Objekte" #: e2_option__default.c:299 msgid "This color is used for the background of highlighed item-names and other text" msgstr "Der Hintergrund markierter Objekte wird in dieser Farbe dargestellt." #: e2_option__default.c:320 #: e2_toolbar.c:2784 msgid "icon above label" msgstr "Icon über Name" #: e2_option__default.c:320 #: e2_toolbar.c:2784 msgid "icon beside label" msgstr "Icon neben Name" #: e2_option__default.c:320 #: e2_toolbar.c:2784 msgid "icon only" msgstr "Nur Icon" #: e2_option__default.c:320 #: e2_toolbar.c:2784 msgid "label only" msgstr "Nur Name" #: e2_option__default.c:320 #: e2_option__default.c:329 #: e2_toolbar.c:2784 #: e2_toolbar.c:2794 msgid "theme" msgstr "GTK-Thema" #: e2_option__default.c:321 #: e2_toolbar.c:2785 #, c-format msgid "'%s' uses the Gtk default, '%s' leaves most space for other things" msgstr "'%s' benutzt die GTK-Einstellung, '%s' nimmt am wenigsten Platz für die anderen Bereiche weg." #: e2_option__default.c:323 msgid "toolbars button style" msgstr "Stil der Werkzeug-Buttons" #: e2_option__default.c:330 #: e2_toolbar.c:2795 #, c-format msgid "'%s' uses the Gtk default, '%s' is smallest, '%s' is largest" msgstr "'%s' benutzt die Einstellung des GTK-Themas, '%s' ist am kleinsten, '%s' ist am größten." #: e2_option__default.c:332 msgid "toolbars icon size" msgstr "Größe der Werkzeug-Icons" #: e2_option_tree.c:163 #: e2_option_tree.c:408 msgid "Press key" msgstr "Drücke Taste" #: e2_option_tree.c:621 #, c-format msgid "select icon for %s" msgstr "Wähle Icon für %s" #: e2_option_tree.c:666 #, c-format msgid "Are you sure that you want to delete this row and %d %s?" msgstr "Wirklich diese Zeile und %d %s löschen?" #: e2_option_tree.c:670 msgid "confirm row delete" msgstr "Bestätigung" #: e2_option_tree.c:956 #: e2_toolbar.c:2899 #: e2p_acl.c:3791 #: e2p_find.c:3613 #: e2p_rename.c:1538 #: e2p_track.c:403 msgid "_Help" msgstr "_Hilfe" #: e2_option_tree.c:957 msgid "Get help on this option" msgstr "Zeigt eine kurze Hilfe zu dieser Einstellung an." #: e2_option_tree.c:960 #: e2p_track.c:392 msgid "_Select" msgstr "_Wählen" #: e2_option_tree.c:961 msgid "Show plugin-selection dialog" msgstr "Plugin-Auswahl-Dialog zeigen" #: e2_option_tree.c:964 msgid "Co_lor" msgstr "_Farbe" #: e2_option_tree.c:965 msgid "Show color-selection dialog" msgstr "Klicken Sie hier, um den Farbauswahl-Dialog zu öffnen." #: e2_option_tree.c:979 msgid "Remove the selected row" msgstr "Entfernt die aktuelle Zeile." #: e2_option_tree.c:981 msgid "Add a row after the currently selected row in the tree" msgstr "Fügt eine neue Zeile nach der aktuellen Zeile hinzu." #: e2_option_tree.c:984 msgid "Add ch_ild" msgstr "Neuer _Unterpunkt" #: e2_option_tree.c:985 msgid "Add a child row to the currently selected one" msgstr "Fügt einen neuen Unterpunkt zur aktuellen Zeile hinzu." #: e2_option_tree_context_menu.c:228 msgid "Cut selected row and any descendant(s)" msgstr "Schneidet die aktuelle Zeile und eventuelle Unterpunkte aus und kopiert sie in die Zwischenablage." #: e2_option_tree_context_menu.c:232 msgid "Copy selected row and any descendant(s)" msgstr "Kopiert die aktuelle Zeile und eventuelle Unterpunkte in die Zwischenablage." #: e2_option_tree_context_menu.c:244 msgid "Expand all rows on this page" msgstr "Ausklappen aller Unterpunkte" #: e2_option_tree_context_menu.c:248 msgid "Collapse all rows on this page" msgstr "Einklappen aller Unterpunkte" #: e2_option_tree_context_menu.c:260 msgid "Add c_hild" msgstr "Neuer _Unterpunkt" #: e2_option_tree_context_menu.c:261 msgid "Add a child to the selected row" msgstr "Erzeugt einen Unterpunkt zur aktuellen Zeile" #: e2_output.c:548 msgid "output tabs" msgstr "Tabs des Ausgabebereiches" #: e2_output.c:1070 #, c-format msgid "Cannot find any output from process %s" msgstr "Keine Meldungen des Prozesses %s gefunden!" #: e2_output.c:1347 msgid "Do not show the output pane" msgstr "Ausgabebereich verstecken" #: e2_output.c:1351 msgid "_Toggle full" msgstr "_Ausgabe maxi-/minimieren" #: e2_output.c:1354 msgid "Toggle output pane size to/from the full window size" msgstr "Umschalten des Ausgabebereiches auf maximale und minimale Größe" #: e2_output.c:1358 msgid "_New tab" msgstr "Tab _hinzufügen" #: e2_output.c:1359 msgid "Add another tab for the output pane" msgstr "Weiteren Tab für den Ausgabebereich hinzufügen" #: e2_output.c:1363 msgid "_Remove tab" msgstr "Tab _entfernen" #: e2_output.c:1364 msgid "Close this this tab" msgstr "Diesen Tab schließen" #: e2_output.c:1373 msgid "_Attach" msgstr "_Anwenden" #: e2_output.c:1374 msgid "Move this tab back to output pane" msgstr "Tab zurück zum Ausgabebereich verschieben" #: e2_output.c:1381 #: e2_output.c:1385 msgid "_Clear" msgstr "_Löschen" #: e2_output.c:1382 #: e2_output.c:1386 msgid "Clear this tab" msgstr "Entfernt den Inhalt dieses Tabs" #: e2_output.c:1395 msgid "Edit the tab contents" msgstr "Bearbeitet den Inhalt dieses Tabs" #: e2_output.c:1417 msgid "Co_mmand output" msgstr "Kindprozess-_Meldungen" #: e2_output.c:1418 msgid "Show output from a completed command" msgstr "Zeige die Ausgabe eines beendeten Kindprozesses" #: e2_output.c:1431 #: e2_view_dialog.c:958 msgid "_Settings" msgstr "_Einstellungen" #: e2_output.c:1442 msgid "_Other" msgstr "_Andere" #: e2_output.c:1443 msgid "Open the configuration dialog at the output options page" msgstr "Öffnet die Konfigurationsseite des Ausgabebereiches" #: e2_output.c:1772 msgid "-- end-of-output --" msgstr "-- Ende-der-Ausgabe --" #: e2_output.c:2415 msgid "show output pane when a new message appears" msgstr "Ausgabebereich bei einer neuen Nachricht anzeigen" #: e2_output.c:2416 msgid "This will ensure you don't miss any messages" msgstr "" "Wenn aktiviert, wird der Ausgabebereich bei einer neuen Nachricht geöffnet.\n" "Damit können Sie keine Nachricht verpassen." #: e2_output.c:2419 msgid "show output pane if the command line is focused" msgstr "Ausgabebereich beim Aktivieren der Befehlszeile zeigen" #: e2_output.c:2420 msgid "This causes the output pane to be opened when you are about enter a command" msgstr "Wenn aktiviert, wird der Ausgabebereich bei Eingabe eines Befehles geöffnet." #: e2_output.c:2424 msgid "hide output pane if the command line is unfocused" msgstr "Ausgabebereich beim Verlassen der Befehlszeile verbergen" #: e2_output.c:2425 msgid "This causes the output pane to be closed when you move focus away from the command line" msgstr "Wenn aktiviert, wird der Ausgabebereich bei Verlassen der Befehlszeile geschlossen." #: e2_output.c:2428 msgid "show commands" msgstr "Befehle anzeigen" #: e2_output.c:2429 msgid "This echoes the commands that are run and their exit value" msgstr "Wenn aktiviert, werden sowohl die Befehle als auch deren Rückgabewerte im Ausgabebereich angezeigt." #: e2_output.c:2431 msgid "scroll to new output" msgstr "Springe automatisch zu neuen Nachrichten" #: e2_output.c:2432 msgid "This will automatically scroll the output pane content, to show new message(s)" msgstr "Wenn aktiviert, wird im Ausgabebereich automatisch zur neuesten Nachricht gesprungen." #: e2_output.c:2434 msgid "only scroll when really following" msgstr "Springe nicht zu neuen Nachrichten, wenn manuell eingriffen wird" #: e2_output.c:2435 msgid "This stops automatic pane scrolling to new output if you've manually scrolled away" msgstr "Wenn aktiviert, wird im Ausgabebereich nicht mehr automatisch zur neuesten Nachricht gesprungen, sobald Sie manuell darin blättern." #: e2_output.c:2438 msgid "only scroll when new output is at the end" msgstr "Springe nur zu neuen Nachrichten, wenn diese am Ende des Ausgabebereiches sind" #: e2_output.c:2439 msgid "This stops pane scrolling if a different process has displayed text after the current insert position" msgstr "Wenn aktiviert, wird im Ausgabebereich nicht mehr automatisch zur neuesten Nachricht gesprungen, sobald ein anderer Prozess danach eine Nachricht einfügt." #: e2_output.c:2442 msgid "everywhere" msgstr "überall" #: e2_output.c:2442 msgid "words" msgstr "Wort" #: e2_output.c:2444 msgid "line wrap mode" msgstr "Zeilenumbruch" #: e2_output.c:2445 msgid "If mode is 'none', a horizontal scrollbar will be available. Mode 'words' will only break the line between words" msgstr "" "Wenn 'ohne' eingestellt ist, so wird eine horizontale Laufleiste eingeblendet.\n" "Wenn 'Wort' eingestellt ist, so erfolgt nur zwischen Wörtern ein Umbruch.\n" "Wenn 'überall' eingestellt ist, so erfolgt auch mitten im Wort ein Umbruch." #: e2_output.c:2448 msgid "left margin (pixels)" msgstr "Linker Rand (Pixel)" #: e2_output.c:2449 msgid "This is the left margin between the output-pane edge and the text in it" msgstr "Hier stellen Sie die Spanne zwischen dem linken Rand des Ausgabebereiches und dem Text darin ein." #: e2_output.c:2452 msgid "right margin (pixels)" msgstr "Rechter Rand (Pixel)" #: e2_output.c:2453 msgid "This is the right margin between the output-pane edge and the text in it" msgstr "Hier stellen Sie die Spanne zwischen dem rechten Rand des Ausgabebereiches und dem Text darin ein." #: e2_output.c:2461 msgid "custom output font" msgstr "Schriftart des Ausgabebereiches" #: e2_output.c:2462 msgid "This is the font used for text in the output pane" msgstr "Hier wird die Schriftart zu Darstellung im Ausgabebereich festgelegt." #: e2_output.c:2467 msgid "positive color" msgstr "Positive Mitteilungen" #: e2_output.c:2468 msgid "This color is used for messages about successful operations or other 'positive events'" msgstr "Mitteilungen erfolgreicher Operationen oder anderer positiven Ereignisse werden in dieser Farbe dargestellt." #: e2_output.c:2471 msgid "negative color" msgstr "Negative Mitteilungen" #: e2_output.c:2472 msgid "This color is used for messages about unsuccessful operations or other 'negative events'" msgstr "Mitteilungen fehlgeschlagener Operationen oder anderer negativen Ereignisse werden in dieser Farbe dargestellt." #: e2_output.c:2475 msgid "unimportant color" msgstr "Unwichtige Mitteilungen" #: e2_output.c:2476 msgid "This color is used for messages of minor importance or other miscellaneous events" msgstr "Mitteilungen von geringer Bedeutung und mancher Ereignisse werden in dieser Farbe dargestellt." #: e2_ownership_dialog.c:167 msgid "You are not authorised to make any change." msgstr "Sie sind nicht berechtigt, Änderungen vorzunehmen!" #: e2_ownership_dialog.c:209 msgid "ownership" msgstr "Eigentum" #: e2_ownership_dialog.c:217 #: e2_permissions_dialog.c:327 #: e2p_acl.c:3467 #: e2p_times.c:697 msgid "Directory name" msgstr "Verzeichnisname" #: e2_ownership_dialog.c:233 msgid "Permissions: " msgstr "Zugriffsrechte:" #: e2_ownership_dialog.c:245 msgid "User: " msgstr "Eigentümer:" #: e2_ownership_dialog.c:251 msgid "Group: " msgstr "Gruppe:" #: e2_ownership_dialog.c:375 msgid "Recurse" msgstr "rekursiv (Unterverzeichnisse einschließen)" #: e2_pane.c:883 msgid "No trash directory is available" msgstr "Papierkorb-Verzeichnis nicht vorhanden." #: e2_pane.c:985 msgid "_Edit places" msgstr "_Orte bearbeiten..." #: e2_pane.c:1584 #, c-format msgid "show %s column" msgstr "%s anzeigen" #: e2_pane.c:1585 #, c-format msgid "This causes the the named column to be displayed in pane %d (duh ...)" msgstr "Wenn aktiviert, wird die genannte Spalte in Fenster %d angezeigt." #: e2_pane.c:1615 #, c-format msgid "At startup, show a specific directory in pane %d" msgstr "Beim Start von emelFM2 ein bestimmtes Verzeichnis im Fester %d zeigen" #: e2_pane.c:1617 msgid "This causes the directory named below, instead of the last-opened directory, to be shown at session start" msgstr "Wenn aktiviert, wird beim Start von emelFM2 ein bestimmtes Verzeichnis anstatt des zuletzt geöffneten angezeigt." #: e2_pane.c:1622 #, c-format msgid "pane %d startup directory:" msgstr "Fenster %d Start-Verzeichnis:" #: e2_pane.c:1624 msgid "This is the directory to show in this pane, at session start" msgstr "Dieses Verzeichnis wird beim Starten von emelFM2 im Fenster angezeigt." #: e2_password_dialog.c:113 #: e2p_crypt.c:2922 msgid "Enter password:" msgstr "Passwort eingeben:" #: e2_password_dialog.c:191 #: e2p_crypt.c:2934 msgid "Confirm password:" msgstr "Passwort bestätigen:" #: e2_password_dialog.c:249 msgid "Entered passwords are different" msgstr "Die eingegebenen Passwörter sind unterschiedlich!" #: e2_permissions_dialog.c:337 #: e2_permissions_dialog.c:400 #: e2p_acl.c:229 #: e2p_acl.c:2090 #: e2p_acl.c:3477 msgid "User" msgstr "Eigentümer" #: e2_permissions_dialog.c:379 msgid "currently" msgstr "Momentan eingestellt:" #: e2_permissions_dialog.c:395 #: e2p_acl.c:222 msgid "Read" msgstr "Lesen" #: e2_permissions_dialog.c:396 #: e2p_acl.c:222 msgid "Write" msgstr "Schreiben" #: e2_permissions_dialog.c:397 #: e2p_acl.c:222 msgid "Exec" msgstr "Ausführen" #: e2_permissions_dialog.c:398 msgid "Special" msgstr "Spezial" #: e2_permissions_dialog.c:407 msgid "Set UID" msgstr "Set User ID" #: e2_permissions_dialog.c:417 msgid "Set GID" msgstr "Set Group ID" #: e2_permissions_dialog.c:420 #: e2p_acl.c:229 #: e2p_acl.c:2099 msgid "Other" msgstr "Andere" #: e2_permissions_dialog.c:427 msgid "Sticky" msgstr "Sticky-Bit" #: e2_permissions_dialog.c:440 #: e2p_acl.c:3704 msgid "Action:" msgstr "Aktion" #: e2_permissions_dialog.c:450 #: e2p_acl.c:3722 #: e2p_times.c:751 #: e2p_times.c:753 #: e2p_times.c:755 msgid "_Set" msgstr "_Setzen" #: e2_permissions_dialog.c:466 #: e2p_acl.c:3746 #: e2p_times.c:757 msgid "R_ecurse:" msgstr "_Rekursiv" #: e2_permissions_dialog.c:475 #: e2p_acl.c:3754 msgid "If activated, changes will be applied to selected items and also their descendents, if such items match your choice of \"directories\" and/or \"others\" (anything not a directory)" msgstr "Wenn aktiviert, werden die Änderungen auch in den darunter liegenden Verzeichnissen vorgenommen (Verzeichnisse und/oder andere Objekte, je nach Wahl)" #: e2_permissions_dialog.c:478 #: e2p_acl.c:3757 msgid "d_irectories" msgstr "_Verzeichnisse" #: e2_permissions_dialog.c:481 #: e2p_acl.c:3760 msgid "o_thers" msgstr "_Andere" #: e2_plugins.c:299 msgid "Cannot unload plugin" msgstr "Fehler beim Entfernen (aus dem Speicher) des Plugins!" #: e2_plugins.c:738 msgid "Plugin not loaded" msgstr "Plugin nicht geladen!" #: e2_plugins.c:1274 msgid "Loaded" msgstr "Laden" #: e2_select_image_dialog.c:611 msgid "Choose icons directory" msgstr "Icon-Verzeichnis auswählen" #: e2_select_image_dialog.c:631 msgid "_other" msgstr "_Andere" #: e2_select_image_dialog.c:635 msgid "_stock" msgstr "_Vorrat" #: e2_select_image_dialog.c:650 msgid "Remove the current icon" msgstr "Zugewiesenes Icon entfernen" #: e2_size_filter_dialog.c:145 msgid "Display only the items which are:" msgstr "Nur Dateien folgender Größe anzeigen:" #: e2_size_filter_dialog.c:146 msgid "size filter" msgstr "Filter nach Größe" #: e2_size_filter_dialog.c:153 #: e2p_glob.c:460 msgid "bigger than" msgstr "größer als" #: e2_size_filter_dialog.c:153 #: e2p_glob.c:460 msgid "equal to" msgstr "gleich" #: e2_size_filter_dialog.c:153 #: e2p_glob.c:460 msgid "smaller than" msgstr "kleiner als" #: e2_size_filter_dialog.c:192 #: e2p_find.c:2785 #: e2p_glob.c:501 msgid "Mbytes" msgstr "MB" #: e2_size_filter_dialog.c:192 #: e2p_find.c:2783 #: e2p_glob.c:501 msgid "kbytes" msgstr "kB" #: e2_task.c:516 #, c-format msgid "%s added to tasks queue" msgstr "%s zur Warteschlange hinzugefügt." #: e2_task.c:784 #, c-format msgid "%s operation incomplete - time limit exceeded" msgstr "Aktion %s nicht vollendet - Zeitlimit überschritten!" #: e2_task.c:868 #, c-format msgid "The current operation (%s)" msgstr "Die aktuelle Aktion (%s)" #: e2_task.c:870 msgid "operation" msgstr "Aktion" #: e2_task.c:1039 msgid "File operation in progress" msgstr "Datei-Aktion in Arbeit" #: e2_task.c:1184 msgid "Operation not permitted - Circular directories" msgstr "Operation nicht erlaubt - Zirkuläre Verzeichnis-Struktur!" #: e2_task.c:1416 #: e2_task.c:1742 #: e2_task.c:1985 #: e2_task.c:2352 #: e2p_clone.c:78 msgid "Enter new name for" msgstr "Neuer Name für" #: e2_task.c:1991 msgid "link" msgstr "Symbolischer Link" #: e2_task.c:2113 #, c-format msgid "Are you sure you want to delete %s?" msgstr "%s wirklich löschen?" #: e2_task.c:2444 #: e2p_rename.c:1017 #: e2p_rename.c:1019 #, c-format msgid "Cannot rename %s" msgstr "Umbenennen von '%s' nicht möglich!" #: e2_task.c:2582 #: e2p_acl.c:4165 #, c-format msgid "You do not have authority to change permission(s) of %s" msgstr "Sie sind nicht berechtigt, die Zugriffsrechte zu ändern für %s" #: e2_task.c:2740 #, c-format msgid "You do not have authority to change owner(s) of %s" msgstr "Sie sind nicht berechtigt, den/die Eigentümer zu ändern für %s" #: e2_task.c:3248 msgid "open with" msgstr "Öffnen mit" #: e2_task.c:3249 #: e2_toolbar.c:2893 msgid "Enter command:" msgstr "Befehl eingeben:" #: e2_task.c:3420 msgid "Enter a name or partial name to find:" msgstr "Name oder einen Teil davon zur Suche eingeben:" #: e2_task_backend.c:330 #, c-format msgid "Cannot change ownership of %s" msgstr "Ändern des Eigentümers von '%s' nicht möglich!" #: e2_task_backend.c:490 #: e2_task_backend.c:506 #: e2_task_backend.c:1613 #, c-format msgid "Cannot delete %s" msgstr "Löschen von '%s' nicht möglich!" #: e2_task_backend.c:663 #: e2_task_backend.c:1578 #, c-format msgid "Cannot delete existing %s" msgstr "Löschen von '%s' nicht möglich!" #: e2_task_backend.c:722 #, c-format msgid "Cannot create special file %s" msgstr "Erstellen der speziellen Datei '%s' nicht möglich!" #: e2_task_backend.c:737 #, c-format msgid "Cannot create FIFO %s" msgstr "Erstellen des FIFO '%s' nicht möglich!" #: e2_task_backend.c:874 #, c-format msgid "Cannot create new link to %s" msgstr "Erstellen des neuen Links nach '%s' nicht möglich!" #: e2_task_backend.c:885 #, c-format msgid "Cannot get target info for link %s" msgstr "Keine Ziel-Informationen des Links '%s' gefunden!" #: e2_task_backend.c:1357 #, c-format msgid "Cannot copy all of %s" msgstr "Das Kopieren aller Objekte in %s ist nicht möglich!" #: e2_task_backend.c:1378 msgid "incomplete" msgstr "teilweise" #: e2_task_backend.c:1463 #, c-format msgid "Cannot move %s to %s" msgstr "Verschieben von '%s' nach '%s' nicht möglich!" #: e2_task_backend.c:1518 #, c-format msgid "Cannot create link to %s" msgstr "Erstellen des Links nach '%s' nicht möglich!" #: e2_task_backend.c:1566 #, c-format msgid "Cannot rename %s to %s" msgstr "Umbenennen von '%s' nach '%s' nicht möglich!" #: e2_task_backend.c:1648 #: e2_task_backend.c:1764 #: e2p_acl.c:2826 #: e2p_crypt.c:1773 #: e2p_times.c:319 #, c-format msgid "Cannot get current data for %s" msgstr "Fehler beim Erhalten aktueller Daten von %s!" #: e2_task_backend.c:1699 #: e2p_acl.c:1870 #, c-format msgid "Cannot change permission(s) of all of %s" msgstr "Das Ändern der Zugriffsrechte aller Objekte in %s ist nicht möglich!" #: e2_task_backend.c:1718 #, c-format msgid "Cannot get current permissions of %s" msgstr "Fehler beim Erhalten aktueller Zugriffsrechte von %s!" #: e2_task_backend.c:1834 #, c-format msgid "Cannot change ownership of all of %s" msgstr "Das Ändern der Eigentümer aller Objekte in %s ist nicht möglich!" #: e2_toolbar.c:357 msgid "Show _bar" msgstr "_Leiste zeigen" #: e2_toolbar.c:359 msgid "Show _tooltips" msgstr "_Schnellhilfe zeigen" #: e2_toolbar.c:361 msgid "Space _handling" msgstr "_Platzbehandlung" #: e2_toolbar.c:364 msgid "_none" msgstr "_ohne" #: e2_toolbar.c:366 msgid "_scrollbars" msgstr "_Bildlaufleiste" #: e2_toolbar.c:368 msgid "_rest button" msgstr "_Rest-Button" #: e2_toolbar.c:370 msgid "_Placement" msgstr "_Platzierung" #: e2_toolbar.c:371 msgid "_horizontal" msgstr "_Horizontal" #: e2_toolbar.c:373 msgid "_Container" msgstr "_Bereich" #: e2_toolbar.c:376 msgid "_main window" msgstr "_Hauptfenster" #: e2_toolbar.c:378 msgid "_both panes" msgstr "_Beide Fenster" #: e2_toolbar.c:380 msgid "file-pane _1" msgstr "_Fenster 1" #: e2_toolbar.c:382 msgid "flle-pane _2" msgstr "_Fenster 2" #: e2_toolbar.c:392 msgid "_Left" msgstr "_Links" #: e2_toolbar.c:394 msgid "_Right" msgstr "_Rechts" #: e2_toolbar.c:397 msgid "_Reset order" msgstr "_Reihenfolge zurücksetzen" #: e2_toolbar.c:399 msgid "Buttons _relief" msgstr "_Button-Relief" #: e2_toolbar.c:401 msgid "Buttons _same size" msgstr "_Gleiche Button-Größe" #: e2_toolbar.c:403 msgid "_Button style" msgstr "_Button-Stil" #: e2_toolbar.c:407 #: e2_toolbar.c:421 msgid "_theme" msgstr "_GTK-Thema" #: e2_toolbar.c:409 msgid "icon _only" msgstr "_Nur Icon" #: e2_toolbar.c:411 msgid "_label only" msgstr "_Nur Name" #: e2_toolbar.c:413 msgid "icon _above label" msgstr "_Icon über Name" #: e2_toolbar.c:415 msgid "icon _beside label" msgstr "_Icon neben Name" #: e2_toolbar.c:417 msgid "_Icon size" msgstr "_Icon-Größe" #: e2_toolbar.c:423 msgid "_menu" msgstr "_Menü" #: e2_toolbar.c:425 msgid "toolbar _small" msgstr "_Leiste (klein)" #: e2_toolbar.c:427 msgid "toolbar _large" msgstr "_Leiste (groß)" #: e2_toolbar.c:429 msgid "_button" msgstr "_Button" #: e2_toolbar.c:431 msgid "drag '_n' drop" msgstr "_Drag & Drop" #: e2_toolbar.c:433 msgid "_dialog" msgstr "_Dialog" #: e2_toolbar.c:436 msgid "Bar i_tems" msgstr "Leisten-Einträge..." #: e2_toolbar.c:719 msgid "_Rest" msgstr "_Rest" #: e2_toolbar.c:720 msgid "Show a menu of hidden items" msgstr "Zeige das Menü der nicht sichtbaren Werkzeuge" #: e2_toolbar.c:1603 msgid "not" msgstr "nicht" #: e2_toolbar.c:1634 msgid "accessed" msgstr "Zugriff" #: e2_toolbar.c:1634 msgid "changed" msgstr "geändert" #: e2_toolbar.c:1634 msgid "modified" msgstr "verändert" #: e2_toolbar.c:2712 msgid "show the " msgstr "Anzeigen der " #: e2_toolbar.c:2713 #, c-format msgid "This determines whether the %s is displayed or hidden" msgstr "Wenn aktiviert, wird die %s angezeigt." #: e2_toolbar.c:2720 #, c-format msgid "show tooltips for %s buttons" msgstr "Schnellhilfe für die Buttons der %s anzeigen" #: e2_toolbar.c:2721 #, c-format msgid "If deactivated, tooltips will not be displayed for %s buttons" msgstr "Wenn aktiviert, wird für jeden Button der %s eine kurze Hilfe angezeigt, wenn der Mauszeiger darauf steht." #: e2_toolbar.c:2727 #, c-format msgid "This determines the method for accessing %s elements that are hidden due to lack of screen-space" msgstr "Hier legen Sie die Methode fest, wie Sie Zugang zu den Elementen der %s erhalten, wenn diese auf Grund eines Platzmangels versteckt sind." #: e2_toolbar.c:2729 msgid "use rest button" msgstr "Rest-Button" #: e2_toolbar.c:2729 msgid "use scrollbars" msgstr "Bildlaufleiste" #: e2_toolbar.c:2730 msgid "space handling" msgstr "Behandlung der verfügbaren Breite" #: e2_toolbar.c:2737 msgid " container" msgstr "im Bereich" #: e2_toolbar.c:2738 #, c-format msgid "This determines the 'box' into which the %s is placed" msgstr "Hier legen Sie den Bereich fest, in welchem sich die %s befinden soll." #: e2_toolbar.c:2740 msgid "both panes" msgstr "Beide Fenster" #: e2_toolbar.c:2740 msgid "file-pane 1" msgstr "Fenster 1" #: e2_toolbar.c:2740 msgid "file-pane 2" msgstr "Fenster 2" #: e2_toolbar.c:2740 msgid "main window" msgstr "Hauptfenster" #: e2_toolbar.c:2748 msgid " horizontal" msgstr " horizontal" #: e2_toolbar.c:2749 #, c-format msgid "This determines whether the %s is displayed horizontally or vertically" msgstr "Wenn NICHT aktiviert, so wird die %s VERTIKAL angezeigt." #: e2_toolbar.c:2756 msgid " priority" msgstr " an Position" #: e2_toolbar.c:2757 msgid "This determines the order of toolbars (0 = left or top)" msgstr "" "Hier beeinfussen Sie, in welcher Reihenfolge die einzelnen Leisten angezeigt werden. Beachten Sie dabei auch die Einstellungen der anderen Leisten.\n" "(0=ganz links oder ganz oben)" #: e2_toolbar.c:2766 msgid " buttons have relief" msgstr ": Buttons immer mit Relief anzeigen" #: e2_toolbar.c:2768 msgid "Buttons with relief show their outline continually, not just when 'moused'" msgstr "Wenn aktiviert, werden die Buttons immer mit Umrandung angezeigt auch, wenn sich der Mauszeiger nicht darüber befindet." #: e2_toolbar.c:2775 msgid " buttons are the same size" msgstr ": Buttons mit gleicher Größe anzeigen" #: e2_toolbar.c:2777 msgid "Equal-width buttons look good on a vertical bar, bad on a horizontal bar" msgstr "Wenn aktiviert, werden alle Buttons mit der gleichen Breite angezeigt." #: e2_toolbar.c:2787 msgid "button style" msgstr "Button-Stil" #: e2_toolbar.c:2797 msgid "icon size" msgstr "Icon-Größe" #: e2_toolbar.c:2817 msgid "Copy item(s) selected in the active pane to the other one" msgstr "Ausgewählte(s) Objekt(e) in das andere Fenster kopieren" #: e2_toolbar.c:2819 msgid "Move item(s) selected in the active pane to the other one" msgstr "Ausgewählte(s) Objekt(e) in das andere Fenster verschieben" #: e2_toolbar.c:2821 msgid "Symlink item(s) selected in the active pane to the other one" msgstr "Symbolischen Link des/der ausgewählten Objekte(s) im anderen Fenster erzeugen" #: e2_toolbar.c:2822 msgid "Re_name.." msgstr "_Umbenennen" #: e2_toolbar.c:2823 msgid "Rename item(s) selected in the active pane" msgstr "Ausgewählte(s) Objekt(e) umbenennen" #: e2_toolbar.c:2825 msgid "Move item(s) selected in the active pane to a trashbin" msgstr "Ausgewählte(s) Objekt(e) in den Papierkorb verschieben" #: e2_toolbar.c:2826 msgid "Create new directory(ies)" msgstr "Neue(s) Verzeichnis(se) erstellen" #: e2_toolbar.c:2832 msgid "Re_fresh" msgstr "A_ktualisieren" #: e2_toolbar.c:2832 msgid "Update pane contents" msgstr "Fensterinhalt aktualisieren" #: e2_toolbar.c:2833 msgid "_Switch" msgstr "_Wechseln" #: e2_toolbar.c:2834 msgid "Toggle the active pane" msgstr "Aktives Fenster wechseln" #: e2_toolbar.c:2880 msgid "Full" msgstr "Voll" #: e2_toolbar.c:2880 msgid "Maximize output pane" msgstr "Ausgabebereich maximieren" #: e2_toolbar.c:2881 msgid "Shrink" msgstr "Klein" #: e2_toolbar.c:2881 msgid "Un-maximize output pane" msgstr "Ausgabebereich minimieren" #: e2_toolbar.c:2882 msgid "Hide" msgstr "Verstecken" #: e2_toolbar.c:2882 msgid "Hide output pane" msgstr "Ausgabebereich verstecken" #: e2_toolbar.c:2883 msgid "Show" msgstr "Zeigen" #: e2_toolbar.c:2883 msgid "Show output pane" msgstr "Ausgabebereich anzeigen" #: e2_toolbar.c:2884 msgid "Clear" msgstr "Löschen" #: e2_toolbar.c:2884 msgid "Clear output pane" msgstr "Ausgabebereich löschen" #: e2_toolbar.c:2886 msgid "Clear command line" msgstr "Befehlszeile löschen" #: e2_toolbar.c:2886 msgid "cl" msgstr "Del" #: e2_toolbar.c:2887 msgid "Child processes" msgstr "Kindprozesse auflisten" #: e2_toolbar.c:2887 msgid "ps" msgstr "List" #: e2_toolbar.c:2888 msgid "Calculate disk usage of selected item(s)" msgstr "Berechne Speicherplatz der gewählten Objekte" #: e2_toolbar.c:2888 #: e2p_du.c:267 msgid "du" msgstr "Speicherplatz" #: e2_toolbar.c:2889 msgid "Find item in active pane, by name" msgstr "Suche Objekt im aktiven Fenster beim Name" #: e2_toolbar.c:2890 msgid "Open terminal at the active directory" msgstr "Öffne Terminal im aktiven Verzeichnis" #: e2_toolbar.c:2890 msgid "_X" msgstr "_Terminal" #: e2_toolbar.c:2893 msgid "Run something as root" msgstr "Einen Befehl als Root ausführen" #: e2_toolbar.c:2893 msgid "su.." msgstr "su.." #: e2_toolbar.c:2895 msgid "Mount or unmount a device" msgstr "Laufwerk mounten oder unmounten" #: e2_toolbar.c:2895 msgid "mts.." msgstr "mnt.." #: e2_toolbar.c:2898 msgid "View/change configuration settings for this program" msgstr "Anzeigen/Ändern der Einstellungen von emelFM2" #: e2_toolbar.c:2898 msgid "_Settings.." msgstr "_Setup.." #: e2_toolbar.c:2899 msgid "Get information about this program" msgstr "Informationen über emelFM2" #: e2_toolbar.c:2901 msgid "Close this program" msgstr "emelFM2 beenden" #: e2_toolbar.c:2901 msgid "_Quit" msgstr "_Ende" #: e2_toolbar.c:2950 #: e2_toolbar.c:3015 msgid "Hide other pane" msgstr "Anderes Fenster verstecken" #: e2_toolbar.c:2950 #: e2_toolbar.c:2952 #: e2_toolbar.c:2955 #: e2_toolbar.c:3015 #: e2_toolbar.c:3017 #: e2_toolbar.c:3020 msgid "_Panes" msgstr "_Fenster" #: e2_toolbar.c:2953 #: e2_toolbar.c:2956 #: e2_toolbar.c:3018 #: e2_toolbar.c:3021 msgid "Show other pane" msgstr "Anderes Fenster zeigen" #: e2_toolbar.c:2958 #: e2_toolbar.c:3023 msgid "Show _hidden" msgstr "_Zeige" #: e2_toolbar.c:2959 #: e2_toolbar.c:3024 msgid "Display hidden items in this directory" msgstr "Versteckte Dateien anzeigen" #: e2_toolbar.c:2960 #: e2_toolbar.c:3025 msgid "Hide _hidden" msgstr "_Verberge" #: e2_toolbar.c:2961 #: e2_toolbar.c:3026 msgid "Do not display hidden items in this directory" msgstr "Versteckte Dateien verbergen" #: e2_toolbar.c:2962 #: e2_toolbar.c:2964 #: e2_toolbar.c:3027 #: e2_toolbar.c:3029 msgid "Fil_ters" msgstr "_Filter" #: e2_toolbar.c:2963 #: e2_toolbar.c:3028 msgid "Set rules for the items to be displayed" msgstr "Anzeigefilter" #: e2_toolbar.c:2965 #: e2_toolbar.c:3030 msgid "Set/remove rules for the items to be displayed" msgstr "Anzeigefilter bearbeiten/entfernen" #: e2_toolbar.c:2967 #: e2_toolbar.c:3032 msgid "_VFS" msgstr "_VFS" #: e2_toolbar.c:2968 #: e2_toolbar.c:3033 msgid "Show a virtual directory in this pane" msgstr "Ein Virtuelles Verzeichnis in diesem Fenster zeigen" #: e2_toolbar.c:2969 #: e2_toolbar.c:3034 msgid "_LocalFS" msgstr "_LocalFS" #: e2_toolbar.c:2977 #: e2_toolbar.c:3042 msgid "_Marks" msgstr "_Lesez." #: e2_toolbar.c:2978 #: e2_toolbar.c:3043 msgid "Bookmarks" msgstr "Lesezeichen" #: e2_toolbar.c:2980 #: e2_toolbar.c:3045 msgid "Add the current directory to the top of the bookmarks list" msgstr "Fügt das aktuelle Verzeichnis an die erste Stelle der Lesezeichen hinzu." #: e2_toolbar.c:2984 #: e2_toolbar.c:3049 msgid "Add the current directory to the bottom of the bookmarks list" msgstr "Fügt das aktuelle Verzeichnis an die letzte Stelle der Lesezeichen hinzu." #: e2_toolbar.c:2987 #: e2_toolbar.c:3052 msgid "_Edit bookmarks" msgstr "_Lesezeichen bearbeiten" #: e2_toolbar.c:2988 #: e2_toolbar.c:3053 msgid "Open the bookmarks configuration dialog" msgstr "Öffnet die Konfigurationsseite der Lesezeichen" #: e2_toolbar.c:2991 #: e2_toolbar.c:2994 #: e2_toolbar.c:3056 #: e2_toolbar.c:3062 msgid "Mi_rror" msgstr "_Spiegeln" #: e2_toolbar.c:2992 #: e2_toolbar.c:2995 #: e2_toolbar.c:3057 #: e2_toolbar.c:3063 msgid "Go to directory shown in other pane" msgstr "Ins gleiche Verzeichnis wechseln wie im anderen Fenster" #: e2_toolbar.c:2997 #: e2_toolbar.c:3058 #: e2_toolbar.c:3066 msgid "Go to previous directory in history" msgstr "Gehe ins vorherige Verzeichnis" #: e2_toolbar.c:2997 #: e2_toolbar.c:3058 #: e2_toolbar.c:3066 msgid "_Back" msgstr "_Zurück" #: e2_toolbar.c:2998 #: e2_toolbar.c:3059 #: e2_toolbar.c:3065 msgid "Go up to parent directory" msgstr "Gehe ins Stammverzeichnis" #: e2_toolbar.c:2999 #: e2_toolbar.c:3060 #: e2_toolbar.c:3064 msgid "Go to next directory in history" msgstr "Gehe ins nächste Verzeichnis" #: e2_toolbar.c:2999 #: e2_toolbar.c:3060 #: e2_toolbar.c:3064 msgid "_Forward" msgstr "_Vor" #: e2_toolbar.c:3079 msgid "bar" msgstr "Fensterleiste" #: e2_tree_dialog.c:1109 msgid "Copy selected path" msgstr "Kopiert den ausgewählten Pfad in die Zwischenablage" #: e2_tree_dialog.c:1113 msgid "Collapse all paths" msgstr "Einklappen aller Pfade" #: e2_tree_dialog.c:1118 msgid "_Strict hiding" msgstr "_Striktes Verstecken" #: e2_tree_dialog.c:1127 msgid "If active, hidden ancestors of visible directories will not be displayed" msgstr "Wenn aktiviert, werden auch sichtbare Verzeichnisse nicht angezeigt, deren Elternverzeichnis versteckt ist." #: e2_tree_dialog.c:1388 msgid "pane 1 navigator" msgstr "Verzeichnisbaum - Fenster 1" #: e2_tree_dialog.c:1388 msgid "pane 2 navigator" msgstr "Verzeichnisbaum - Fenster 2" #: e2_tree_dialog.c:1460 msgid "Toggle display of hidden directories" msgstr "Anzeige der versteckten Verzeichnisse umschalten" #: e2_utils.c:109 msgid "Not enough memory! Things may not work as expected" msgstr "Nicht genug Speicher! Fehlfunktionen sind dadurch zu erwarten!" #: e2_utils.c:159 msgid "Cannot read USAGE help document" msgstr "Fehler beim Lesen der USAGE Hilfedatei!" #: e2_utils.c:845 msgid "No item selected" msgstr "Kein Objekt gewählt!" #: e2_utils.c:888 msgid "No item selected in other pane" msgstr "Kein Objekt im anderen Fenster gewählt!" #: e2_utils.c:978 msgid "No matching '}' found in action text" msgstr "" #: e2_utils.c:1070 msgid "No matching '$' found in action text" msgstr "" #: e2_utils.c:1312 #, c-format msgid "Cannot access %s, going to %s instead" msgstr "Fehler beim Zugriff auf %s, gehe deshalb zu %s!" #: e2_view_dialog.c:401 #, c-format msgid "Cannot read '%s'" msgstr "Fehler beim Lesen von '%s'!" #: e2_view_dialog.c:417 #, c-format msgid "'%s' is a directory" msgstr "'%s' ist ein Verzeichnis!" #: e2_view_dialog.c:468 #, c-format msgid "Encoding conversion command '%s' failed" msgstr "Fehler beim Ausführen des Befehles '%s' zur Zeichensatz-Konvertierung!" #: e2_view_dialog.c:508 #, c-format msgid "Conversion from %s encoding failed: \"%s\"" msgstr "Fehler bei der Konverierung von %s: \"%s\"" #: e2_view_dialog.c:947 msgid "Copy selected text" msgstr "Kopiert den ausgewählten Text in die Zwischenablage" #: e2_view_dialog.c:1004 msgid "Finds" msgstr "Suchtext" #: e2_view_dialog.c:1018 msgid "If activated, text case does matter when searching" msgstr "Wenn aktiviert, wird beim Suchen zwischen Groß- und Kleinschreibung unterschieden." #: e2_view_dialog.c:1018 msgid "_match case" msgstr "_Groß-/Kleinbuchstaben unterscheiden" #: e2_view_dialog.c:1023 msgid "If activated, matches must be surrounded by word-separator characters" msgstr "Wenn aktiviert, müssen die Treffer durch eine Wort-Trennzeichen begrenzt sein (z.B. Leerzeichen)." #: e2_view_dialog.c:1023 msgid "wh_ole words" msgstr "_ganze Wörter" #: e2_view_dialog.c:1027 msgid "If activated, searching proceeds toward document start" msgstr "Wenn aktiviert, erfolgt die Suche rückwärts." #: e2_view_dialog.c:1027 msgid "_backward" msgstr "_rückwärts" #: e2_view_dialog.c:1030 msgid "If activated, searching cycles from either end to the other" msgstr "Wenn aktiviert, erfolgt sie Suche in einer Endlosschleife." #: e2_view_dialog.c:1030 msgid "_loop" msgstr "_zyklisch" #: e2_view_dialog.c:1518 msgid "displaying file" msgstr "Interner Dateibetrachter" #: e2_view_dialog.c:1592 msgid "_wrap" msgstr "_Zeilenumbruch" #: e2_view_dialog.c:1605 msgid "Show the search options bar" msgstr "Such-Parameter anzeigen" #: e2_view_dialog.c:1848 msgid "wrap text" msgstr "Zeilenumbruch" #: e2_view_dialog.c:1849 msgid "This causes the view window to open with text-wrapping enabled" msgstr "Wenn aktiviert, wird das Betrachter-/Editor-Fenster mit aktiviertem Zeilenumbruch geöffnet." #: e2_view_dialog.c:1853 msgid "window width" msgstr "Fensterbreite" #: e2_view_dialog.c:1854 msgid "The view window will default to showing this many characters per line (but the the displayed buttons may make it wider than this)" msgstr "Hier stellen Sie die Breite des Betrachter-Fensters ein (Anzahl der Zeichen, Minimum ist abhängig von den Buttons)." #: e2_view_dialog.c:1858 msgid "window height" msgstr "Fensterhöhe" #: e2_view_dialog.c:1859 msgid "The view window will default to showing this many lines of text" msgstr "Hier stellen Sie die Höhe des Betrachter-Fensters ein (Anzahl der Zeilen)." #: e2_view_dialog.c:1866 msgid "custom font for viewing files" msgstr "Font für den internen Betrachter/Editor" #: e2_view_dialog.c:1867 msgid "This is the font used for text in each view dialog" msgstr "Hier wird die Schriftart zu Darstellung im internen Datei-Betrachter und Editor festgelegt." #: e2_view_dialog.c:1870 msgid "case sensitive searches" msgstr "Groß-/Kleinbuchstaben unterscheiden" #: e2_view_dialog.c:1871 msgid "This causes the view window search-bar to first open with case-sensitive searching enabled" msgstr "Wenn aktiviert, so wird in der Suchleiste des Betrachter-Fensters die Gross-/Kleinschreibung aktiviert." #: e2_view_dialog.c:1875 msgid "show last search string" msgstr "Letzten Suchtext anzeigen" #: e2_view_dialog.c:1876 msgid "This shows the last search-string in the entry field, when the view window search-bar is displayed" msgstr "Wenn aktiviert, wird der zuletzt gesuchte Text im Eingabefeld der Suchleiste angezeigt." #: e2_view_dialog.c:1880 msgid "keep search history" msgstr "Such-Chronik speichern" #: e2_view_dialog.c:1881 msgid "This causes search strings to be remembered between sessions" msgstr "Wenn aktiviert, merkt sich emelFM2 die letzten Suchstrings." #: e2_window.c:1148 #, c-format msgid "displayed & %d concealed " msgstr "angezeigten & %d versteckten " #: e2_window.c:1151 #, c-format msgid "%s%d selected item(s) of %d %sin %s" msgstr "%s%d ausgewählte(s) Objekt(e) von %d %sin %s" #: e2p_acl.c:222 #, fuzzy msgid "Whole" msgstr "Ganze" #: e2p_acl.c:229 #: e2p_acl.c:2096 msgid "Mask" msgstr "Maske" #: e2p_acl.c:1102 #: e2p_acl.c:1135 #: e2p_acl.c:3374 #: e2p_acl.c:3545 #, fuzzy msgid "Directory ACL" msgstr "Dateiverzeichnis ACL" #: e2p_acl.c:1102 #: e2p_acl.c:1135 #: e2p_acl.c:3364 #: e2p_acl.c:3515 #, fuzzy msgid "General ACL" msgstr "Allgemeine ACL" #: e2p_acl.c:1107 #, c-format msgid "Cannot apply %s '%s' for %s" msgstr "Fehler beim Zuweisen: %s '%s' für %s!" #: e2p_acl.c:1140 #, c-format msgid "Cannot apply %s '%s' for %s - Invalid" msgstr "Fehler beim Zuweisen: %s '%s' für %s - ungültig" #: e2p_acl.c:3351 msgid "No directory-changes have been selected" msgstr "" #: e2p_acl.c:3361 #, c-format msgid "The specified %s is likely to ba a problem" msgstr "" #: e2p_acl.c:3461 msgid "extended permissions" msgstr "Erweiterte Zugriffsrechte" #: e2p_acl.c:3514 msgid "unable to display" msgstr "" #: e2p_acl.c:3620 #, fuzzy msgid "General" msgstr "Allgemeine" #: e2p_acl.c:3663 msgid "Data:" msgstr "Daten:" #: e2p_acl.c:3672 #, fuzzy msgid "S_hown" msgstr "Zeigen" #: e2p_acl.c:3680 #, fuzzy msgid "Changes will be based only on the data shown above" msgstr "Ändert die Zeit-Eigenschaften eines oder mehrerer Objekte" #: e2p_acl.c:3681 #, fuzzy msgid "_Varied" msgstr "_Anzeigen" #: e2p_acl.c:3690 #, fuzzy msgid "Changes will be based on the standard permissions of the affected item as modified by the data shown above" msgstr "Ändert die Zeit-Eigenschaften eines oder mehrerer Objekte" #: e2p_acl.c:3692 #, fuzzy msgid "S_ystem" msgstr "_Einfügen" #: e2p_acl.c:3701 #, fuzzy msgid "Changes will be based only on the standard (non-ACL) permissions of the affected item" msgstr "Ändert die Zeit-Eigenschaften eines oder mehrerer Objekte" #: e2p_acl.c:3713 #, fuzzy msgid "_Nuke" msgstr "_Weiter" #: e2p_acl.c:3721 msgid "Clear as much of the item's ACL as possible" msgstr "" #: e2p_acl.c:3732 #, fuzzy msgid "_Whole" msgstr "_Ganze" #: e2p_acl.c:3740 msgid "Conveniently sets all allowed 'whole' values. For those entries, the action will apply to the whole of the entry, Otherwise, the action affects only the permissions of that entry" msgstr "" #: e2p_acl.c:3764 #, fuzzy msgid "dirs-_general" msgstr "emelFM2" #: e2p_acl.c:3772 msgid "if activated, specified changes to the \"general\" ACL will be applied to any affected directory" msgstr "" #: e2p_acl.c:3774 #, fuzzy msgid "dirs-_default" msgstr "Standard" #: e2p_acl.c:3782 msgid "if activated, specified changes to the \"directories-only\" ACL will be applied to any affected directory" msgstr "" #: e2p_acl.c:3801 msgid "Insert a row in the ACL" msgstr "Neue Zeile in den ACLs einfügen" #: e2p_acl.c:3805 msgid "De_lete" msgstr "_Löschen" #: e2p_acl.c:3811 msgid "Delete the selected row from the ACL" msgstr "Gewählte Zeile aus den ACLs entfernen" #: e2p_acl.c:4303 #, fuzzy msgid "acl" msgstr "Del" #: e2p_acl.c:4307 msgid "_Access" msgstr "_Zugriffsrechte (ACL)" #: e2p_acl.c:4310 #, fuzzy msgid "_Access.." msgstr "_Zugriff..." #: e2p_acl.c:4311 #: e2p_acl.c:4319 msgid "Change extended permissions of selected items" msgstr "Ändert die erweiterten Zugriffsrechte der gewählten Objekte (ACLs)" #: e2p_acl.c:4317 msgid "Change _ACLs.." msgstr "_ACLs ändern..." #: e2p_acl.c:4321 msgid "_Replicate" msgstr "_Replizieren" #: e2p_acl.c:4323 msgid "Recursively apply ACLs of selected items to matching items in the other pane" msgstr "" #: e2p_acl.c:4363 #, fuzzy msgid "copy_acl" msgstr "Kopieren_acl" #: e2p_clone.c:82 #: e2p_clone.c:160 msgid "clone" msgstr "Kopie" #: e2p_clone.c:163 msgid "C_lone.." msgstr "_Kopie..." #: e2p_clone.c:164 msgid "Copy selected item(s), each with new name, to the current directory" msgstr "Erstellt eine Kopie der ausgewählten Datei(en) im selben Verzeichnis." #: e2p_config.c:371 #, c-format msgid "Bad configuration data for %s, not installed" msgstr "Fehlerhafte Konfigurationsdaten für %s, nicht installiert!" #: e2p_config.c:504 #, c-format msgid "Incompatible format - %s" msgstr "Fehler: Nicht kompatibles Format - %s" #: e2p_config.c:677 msgid "select configuration data file" msgstr "Auswahl der Konfigurationsdatei" #: e2p_config.c:751 msgid "save configuration data file" msgstr "Konfigurationsdatei speichern" #: e2p_config.c:810 #: e2p_config.c:878 msgid "select icons directory" msgstr "Auswahl des Icons-Verzeichnisses" #: e2p_config.c:1035 msgid "Save configuration data in" msgstr "Konfigurationsdaten exportieren nach:" #: e2p_config.c:1058 #: e2p_crypt.c:2968 msgid "backup" msgstr "Sicherheitskopie" #: e2p_config.c:1097 #: e2p_config.c:1157 #: e2p_config.c:1284 #: e2p_config.c:1315 msgid "Se_lect" msgstr "_Auswahl" #: e2p_config.c:1098 msgid "Select the file in which to store the config data" msgstr "Klicken Sie hier, um das Verzeichnis und den Dateinamen der Konfigurationsdatei auszuwählen." #: e2p_config.c:1101 msgid "Save current config data in the specified file" msgstr "Klicken Sie hier, um die aktuellen Konfigurationsdaten in die angegebenen Datei zu exportieren." #: e2p_config.c:1107 msgid "export" msgstr "Export" #: e2p_config.c:1126 msgid "Get configuration data from" msgstr "Konfigurationsdaten importieren von:" #: e2p_config.c:1158 msgid "Select the config file from which to get the data" msgstr "Klicken Sie hier, um die zu importierende Konfigurationsdatei auszuwählen." #: e2p_config.c:1162 msgid "Import config data in accord with choices below" msgstr "Klicken Sie hier, um die Konfigurationsdaten entsprechend der unten gewählten Angaben zu importieren." #: e2p_config.c:1171 msgid "_all options" msgstr "_alle Optionen" #: e2p_config.c:1173 msgid "all '_non-group' options" msgstr "alle '_nicht-Gruppen'-Optionen" #: e2p_config.c:1177 msgid "all 'g_roup' options" msgstr "alle '_Gruppen'-Optionen" #: e2p_config.c:1179 msgid "_specific group option(s)" msgstr "_spezielle Gruppen-Option(en)" #: e2p_config.c:1180 msgid "_groups" msgstr "_Gruppen" #: e2p_config.c:1235 msgid "import" msgstr "Import" #: e2p_config.c:1254 msgid "Use icons in" msgstr "Die Icons im folgenden Verzeichnis benutzen:" #: e2p_config.c:1285 msgid "Select the directory where the icons are" msgstr "Klicken Sie hier, um das Verzeichnis zu wählen, in welchem sich die emelFM2-Icons befinden." #: e2p_config.c:1289 msgid "Apply the chosen icon directory" msgstr "Klicken Sie hier, um die Icons im ausgewählten Verzeichnis anzuwenden." #: e2p_config.c:1297 msgid "Copy current icons to" msgstr "Aktuelle Icons kopieren nach:" #: e2p_config.c:1316 msgid "Select the directory where the icons will be saved" msgstr "Klicken Sie hier, um das Verzeichnis zu wählen, in welches die aktuellen Icons kopiert werden sollen." #: e2p_config.c:1319 msgid "C_opy" msgstr "_Kopieren" #: e2p_config.c:1320 msgid "Copy the icons to the chosen directory" msgstr "Klicken Sie hier, um die aktuellen Icons in das angegebene Verzeichnis zu kopieren." #: e2p_config.c:1341 msgid "manage configuration data" msgstr "Konfigurationsdaten" #: e2p_config.c:1376 msgid "manage" msgstr "handhaben" #: e2p_config.c:1378 msgid "_Configure.." msgstr "_Konfiguration..." #: e2p_config.c:1379 msgid "Export or import configuration data" msgstr "Exportieren/Importieren der Konfigurationsdaten" #: e2p_cpbar.c:402 #, c-format msgid "" "copying %s\n" "to %s\n" "this is item %s of %s" msgstr "" "Kopiere %s\n" "nach %s\n" "\n" "Objekt %s von %s" #: e2p_cpbar.c:413 #: e2p_mvbar.c:422 #, c-format msgid "%.2f MB of %.2f MB (%.0f\\%%)" msgstr "%.2f MB von %.2f MB (%.0f\\%%)" #: e2p_cpbar.c:673 #: e2p_mvbar.c:715 #, c-format msgid "Cannot put anything in %s" msgstr "Es können keine Objekte in '%s' abgelegt werden!" #: e2p_cpbar.c:695 msgid "copying" msgstr "Kopieren" #: e2p_cpbar.c:720 #: e2p_mvbar.c:794 msgid "_Resume" msgstr "_Weiter" #: e2p_cpbar.c:721 msgid "Resume copying after pause" msgstr "Kopieren nach der Unterbrechung weiterführen" #: e2p_cpbar.c:726 #: e2p_mvbar.c:800 msgid "_Pause" msgstr "_Pause" #: e2p_cpbar.c:727 msgid "Suspend copying, after the current item" msgstr "Unterbricht das Kopieren nach dem nächsten Objekt" #: e2p_cpbar.c:732 #: e2p_cpbar.c:736 msgid "Abort the copying" msgstr "Das Kopieren abbrechen." #: e2p_cpbar.c:871 msgid "cpbar" msgstr "Kopieren_mit_Anzeige" #: e2p_cpbar.c:872 msgid "cpbar_with_time" msgstr "Kopieren_mit_Zeit+Anzeige" #: e2p_cpbar.c:886 msgid "Copy selected item(s), with displayed progress details" msgstr "Die ausgewählten Objekte werden mit Fortschrittsanzeige in das andere Fenster kopiert." #: e2p_cpbar.c:888 msgid "Copy with _times" msgstr "Kopieren mit _Zeit" #: e2p_cpbar.c:890 msgid "Copy selected item(s), with preserved time-properties and displayed progress details" msgstr "Die ausgewählten Objekte werden mit Fortschrittsanzeige und Beibehaltung der Zeiten in das andere Fenster kopiert." #: e2p_crypt.c:515 #, c-format msgid "No LZO compression-library for file %s" msgstr "LZO-Bibliothek nicht gefunden für Datei %s" #: e2p_crypt.c:550 #, c-format msgid "No ZLIB compression-library for file %s" msgstr "ZLIB-Bibliothek nicht gefunden für Datei %s" #: e2p_crypt.c:585 #, c-format msgid "No BZIP compression-library for file %s" msgstr "BZIP-Bibliothek nicht gefunden für Datei %s" #: e2p_crypt.c:590 #, c-format msgid "Unknown compression-library for file %s" msgstr "Unbekannte Kompressions-Bibliothek für Datei %s" #: e2p_crypt.c:913 #: e2p_crypt.c:1811 #, c-format msgid "Cannot open '%s' for writing" msgstr "Fehler beim Öffnen von '%s' zum Schreiben!" #: e2p_crypt.c:1131 #, c-format msgid "Wrong password for %s" msgstr "Falsches Passwort für %s" #: e2p_crypt.c:1247 #, c-format msgid "Error decompressing file %s" msgstr "Fehler beim Entpacken der Datei %s" #: e2p_crypt.c:1960 #, c-format msgid "" "%s does not end with \"%s\".\n" "Process this file anyway?" msgstr "" "%s endet nicht mit \"%s\".\n" "Trotzdem weitermachen?" #: e2p_crypt.c:2303 #, c-format msgid "Cannot process all of %s" msgstr "Fehler! Kann nicht alles verarbeiten von %s" #: e2p_crypt.c:2785 msgid "en/decrypt file" msgstr "Datei ver-/entschlüsseln" #: e2p_crypt.c:2801 msgid "Symbolic link" msgstr "Symbolischer Link" #: e2p_crypt.c:2817 #, c-format msgid " to %s" msgstr "nach %s" #: e2p_crypt.c:2825 msgid "encrypt" msgstr "Verschlüsseln" #: e2p_crypt.c:2828 msgid "decrypt" msgstr "Entschlüsseln" #: e2p_crypt.c:2832 msgid "encrypted file will have same name" msgstr "Die verschlüsselte Datei wird den gleichen Namen besitzen." #: e2p_crypt.c:2837 msgid "append this to encrypted file name" msgstr "" #: e2p_crypt.c:2857 msgid "encrypted file name will be" msgstr "Der verschlüsselte Dateiname wird sein:" #: e2p_crypt.c:2876 msgid "decrypted file will have same name" msgstr "Die entschlüsselte Datei wird den gleichen Namen besitzen." #: e2p_crypt.c:2880 msgid "decrypted file will have embedded name" msgstr "" #: e2p_crypt.c:2885 msgid "strip this from end of decrypted file name" msgstr "" #: e2p_crypt.c:2904 msgid "decrypted file name will be" msgstr "Der entschlüsselte Dateiname wird sein:" #: e2p_crypt.c:2948 msgid "decrypted file will have stored owners, permissions and dates" msgstr "Die entschlüsselte Datei wird die gespeicherten Eigentümer, Zugriffsrechte und Zeiten besitzen." #: e2p_crypt.c:2948 msgid "restore properties" msgstr "Eigenschaften wiederherstellen" #: e2p_crypt.c:2953 #: e2p_crypt.c:2957 msgid "compress" msgstr "Packen" #: e2p_crypt.c:2953 #: e2p_crypt.c:2957 msgid "compress file before encryption" msgstr "Datei vor dem Verschlüsseln packen" #: e2p_crypt.c:2965 msgid "store current name, permissions etc in the encrypted file" msgstr "Aktuellen Name, Zugriffsrechte usw. in der verschlüsselten Datei speichern." #: e2p_crypt.c:2965 msgid "store properties" msgstr "Eigenschaften speichern" #: e2p_crypt.c:2968 msgid "backup an existing file with the same name as the processed file" msgstr "Wenn beim Speichern einer Datei eine gleichnamige bereits existiert, so wird diese gesichert." #: e2p_crypt.c:2970 msgid "do not remove the original file, after processing it" msgstr "Originaldatei nach der Verarbeitung nicht entfernen." #: e2p_crypt.c:2970 msgid "keep original" msgstr "Original erhalten" #: e2p_crypt.c:2973 msgid "if file is a symlink, process its target" msgstr "Wenn die Datei ein symbolischer Link ist, dann das Ziel des Links verarbeiten." #: e2p_crypt.c:2973 msgid "through links" msgstr "Links folgen" #: e2p_crypt.c:2975 msgid "recurse directories" msgstr "Rekursiv (Unterverzeichnisse einschliessen)" #: e2p_crypt.c:3219 #, c-format msgid "You do not have authority to modify %s" msgstr "Sie sind nicht berechtigt, '%s' zu ändern!" #: e2p_crypt.c:3336 #, fuzzy msgid "crypt" msgstr "Schlüssel" #: e2p_crypt.c:3339 msgid "_En/decrypt.." msgstr "_Ver-/Entschlüsseln" #: e2p_crypt.c:3340 msgid "Encrypt or decrypt selected items" msgstr "Ver- oder Entschlüsselt die gewählten Objakte." #: e2p_dircmp.c:876 msgid "compare" msgstr "vergleichen" #: e2p_dircmp.c:879 msgid "C_ompare" msgstr "_Vergleichen" #: e2p_dircmp.c:880 msgid "Select active-pane items which are duplicated in the other pane" msgstr "Wählt alle Objekte des aktiven Fensters, die auch im anderen Fenster vorkommen" #: e2p_du.c:165 msgid "total size: " msgstr "Gesamtgröße:" #: e2p_du.c:181 msgid "kilobytes" msgstr "Kilobyte" #: e2p_du.c:194 msgid "Megabytes" msgstr "Megabyte" #: e2p_du.c:207 msgid "gigabytes" msgstr "Gigabyte" #: e2p_du.c:222 #: e2p_rename.c:934 #: e2p_rename.c:1049 msgid "in" msgstr "in" #: e2p_du.c:224 msgid "(one or more are hidden)" msgstr "(eins oder mehrere sind versteckt)" #: e2p_du.c:270 msgid "_Disk usage" msgstr "_Speicherplatz" #: e2p_du.c:271 msgid "Calculate the disk usage of selected item(s)" msgstr "Berechnet Speicherplatz der gewählten Objekte" #: e2p_find.c:316 msgid "all files" msgstr "alle Dateien" #: e2p_find.c:319 #: e2p_track.c:112 msgid "images" msgstr "Bilder" #: e2p_find.c:320 #: e2p_track.c:113 msgid "music" msgstr "Sounddateien" #: e2p_find.c:321 #: e2p_track.c:114 msgid "videos" msgstr "Videodateien" #: e2p_find.c:322 #: e2p_track.c:115 msgid "text files" msgstr "Textdateien" #: e2p_find.c:323 #: e2p_track.c:116 msgid "development files" msgstr "Entwicklerdateien" #: e2p_find.c:324 #: e2p_track.c:117 msgid "other files" msgstr "Andere Dateien" #: e2p_find.c:334 #: e2p_track.c:127 #, fuzzy msgid "conversations" msgstr "Gespräche" #: e2p_find.c:336 #: e2p_track.c:129 msgid "applications" msgstr "Anwendungen" #: e2p_find.c:338 #: e2p_track.c:131 msgid "emails" msgstr "E-Mails" #: e2p_find.c:339 #: e2p_track.c:132 msgid "email attachments" msgstr "E-Mail-Anhänge" #: e2p_find.c:2623 #: e2p_rename.c:1460 msgid "Search for items:" msgstr "Objekte suchen:" #: e2p_find.c:2626 #: e2p_rename.c:1461 msgid "any_where" msgstr "über_all" #: e2p_find.c:2629 #: e2p_rename.c:1464 msgid "in _active directory" msgstr "im aktuellen _Verzeichnis" #: e2p_find.c:2637 #: e2p_rename.c:1466 msgid "in _other directory" msgstr "im Verzeichnis des _nicht aktiven Fensters" #: e2p_find.c:2645 #: e2p_rename.c:1468 msgid "in _this directory" msgstr "im _folgenden Verzeichnis:" #: e2p_find.c:2669 msgid "Recurse subdirectories" msgstr "Rekursiv (Unterverzeichnisse einschliessen)" #: e2p_find.c:2712 msgid "name" msgstr "Name" #: e2p_find.c:2719 msgid "Find items whose name:" msgstr "Finde Dateien, deren Name..." #: e2p_find.c:2724 #: e2p_find.c:3177 msgid "is" msgstr "so ist" #: e2p_find.c:2726 #: e2p_find.c:3179 msgid "is like" msgstr "so ähnlich ist" #: e2p_find.c:2728 #: e2p_find.c:3181 msgid "matches this regex" msgstr "dem regulären Ausdruck entspricht" #: e2p_find.c:2730 #: e2p_find.c:3183 msgid "ignore case" msgstr "Groß-/Kleinschreibung ignorieren" #: e2p_find.c:2754 msgid "size" msgstr "Größe" #: e2p_find.c:2762 msgid "Find items whose size is:" msgstr "Finde Dateien, deren Größe folgendermaßen ist..." #: e2p_find.c:2767 msgid "less than:" msgstr "kleiner als" #: e2p_find.c:2769 msgid "equal to:" msgstr "gleich" #: e2p_find.c:2772 msgid "more than" msgstr "größer als" #: e2p_find.c:2800 msgid "mime" msgstr "Mime" #: e2p_find.c:2809 msgid "Find files whose mimetype is like this:" msgstr "Finde Dateien, deren Mime-Typ wie folgt ist:" #: e2p_find.c:2835 msgid "save" msgstr "Gesichert" #: e2p_find.c:2840 msgid "Find items most-recently saved:" msgstr "Finde Dateien, die gesichert wurden..." #: e2p_find.c:2845 #: e2p_find.c:2879 #: e2p_find.c:2913 msgid "before:" msgstr "vor dem" #: e2p_find.c:2846 #: e2p_find.c:2880 #: e2p_find.c:2914 msgid "on/at:" msgstr "am" #: e2p_find.c:2848 #: e2p_find.c:2882 msgid "after:" msgstr "nach dem" #: e2p_find.c:2869 msgid "access" msgstr "Zugriff" #: e2p_find.c:2874 msgid "Find items most-recently read or executed:" msgstr "Finde Dateien, die gelesen oder ausgeführt wurden..." #: e2p_find.c:2908 msgid "Find items whose inode was last changed:" msgstr "Finde Dateien, deren Inhalt geändert wurde..." #: e2p_find.c:2916 msgid "after" msgstr "nach dem" #: e2p_find.c:2936 msgid "permission" msgstr "Recht" #: e2p_find.c:2941 msgid "Find items whose permissions:" msgstr "Finde Dateien, deren Zugriffsrechte..." #: e2p_find.c:2945 #: e2p_find.c:3007 msgid "are" msgstr "sind" #: e2p_find.c:2947 msgid "include" msgstr "schließen ein" #: e2p_find.c:2949 msgid "exclude" msgstr "schließen aus" #: e2p_find.c:2955 msgid "owner read" msgstr "Lesen-Eigentümer" #: e2p_find.c:2957 msgid "group read" msgstr "Lesen-Gruppe" #: e2p_find.c:2959 msgid "anyone read" msgstr "Lesen-Andere" #: e2p_find.c:2963 msgid "owner write" msgstr "Schreiben-Eigentümer" #: e2p_find.c:2965 msgid "group write" msgstr "Schreiben-Gruppe" #: e2p_find.c:2967 msgid "anyone write" msgstr "Schreiben-Andere" #: e2p_find.c:2971 msgid "owner execute" msgstr "Ausführen-Eigentümer" #: e2p_find.c:2973 msgid "group execute" msgstr "Ausführen-Gruppe" #: e2p_find.c:2975 msgid "anyone execute" msgstr "Ausführen-Andere" #: e2p_find.c:2979 msgid "setuid" msgstr "Set User ID" #: e2p_find.c:2981 msgid "setgid" msgstr "Set Group ID" #: e2p_find.c:2983 msgid "sticky" msgstr "Sticky-Bit" #: e2p_find.c:2999 msgid "type" msgstr "Typ" #: e2p_find.c:3004 msgid "Find items which" msgstr "Finde Objekte, welche" #: e2p_find.c:3009 msgid "are not" msgstr "sind nicht" #: e2p_find.c:3016 msgid "regular" msgstr "Reguläre Datei" #: e2p_find.c:3023 #: e2p_find.c:3046 msgid "block device" msgstr "Block-Device" #: e2p_find.c:3049 msgid "raw device" msgstr "Raw-Device" #: e2p_find.c:3053 msgid "fifo" msgstr "Fifo" #: e2p_find.c:3077 msgid "Find items with:" msgstr "Finde Objekte, mit:" #: e2p_find.c:3085 msgid "any user id" msgstr "jeder Benutzer-ID" #: e2p_find.c:3087 msgid "specific user id" msgstr "spezieller Benutzer-ID" #: e2p_find.c:3090 msgid "current user's uid" msgstr "aktueller Benutzer-ID" #: e2p_find.c:3093 msgid "this user id" msgstr "dieser Benutzer-ID" #: e2p_find.c:3100 msgid "unregistered user" msgstr "nicht registrierter Benutzer" #: e2p_find.c:3119 msgid "any group id" msgstr "jeder Gruppen-ID" #: e2p_find.c:3121 msgid "specific group id" msgstr "spezieller Gruppen-ID" #: e2p_find.c:3124 msgid "current user's gid" msgstr "aktueller Gruppen-ID" #: e2p_find.c:3127 msgid "this group id" msgstr "dieser Gruppen-ID" #: e2p_find.c:3134 msgid "unregistered group" msgstr "nicht registrierte Gruppe" #: e2p_find.c:3165 msgid "content" msgstr "Inhalt" #: e2p_find.c:3173 msgid "Using grep, find files with content that:" msgstr "Verwende grep, finde Dateien deren Inhalt:" #: e2p_find.c:3220 msgid "Using" msgstr "Verwende" #: e2p_find.c:3229 msgid "find files with content that is:" msgstr "Finde Dateien, deren Inhalt:" #: e2p_find.c:3514 msgid "Day" msgstr "Tag" #: e2p_find.c:3522 msgid "Month" msgstr "Monat" #: e2p_find.c:3532 msgid "Year" msgstr "Jahr" #: e2p_find.c:3543 msgid "Hour" msgstr "Stunde" #: e2p_find.c:3551 msgid "Minute" msgstr "Minute" #: e2p_find.c:3595 msgid "find files" msgstr "Suchen" #: e2p_find.c:3614 msgid "get advice on search options on this page" msgstr "Hilfe zum Suchen (wird im Ausgabebereich angezeigt)" #: e2p_find.c:3621 msgid "stop the current search" msgstr "Suchvorgang anhalten" #: e2p_find.c:3627 msgid "begin searching" msgstr "Suche starten (Ergebnisse im Ausgabebereich)" #: e2p_find.c:3630 msgid "Clea_r" msgstr "_Löschen" #: e2p_find.c:3630 msgid "clear all search parameters" msgstr "Löscht alle Suchparameter" #: e2p_find.c:3664 msgid "detfind" msgstr "Suchen" #: e2p_find.c:3668 msgid "Find and list items, using detailed criteria" msgstr "Dateien nach den verschiedensten Kriterien suchen." #: e2p_for_each.c:125 msgid "repeat action" msgstr "Befehl für Auswahl" #: e2p_for_each.c:126 msgid "Action to run for each selected item:" msgstr "" "Befehl, der mit jedem einzelnen ausge-\n" "wählten Objekt ausgeführt werden soll:" #: e2p_for_each.c:180 msgid "foreach" msgstr "Befehl_für_Auswahl" #: e2p_for_each.c:183 msgid "For _each.." msgstr "_Befehl für Auswahl..." #: e2p_for_each.c:184 msgid "Execute an entered command on each selected item separately" msgstr "Führt einen einzugegebenden Befehl mit jedem ausgewählte Objekt einzeln aus" #: e2p_glob.c:409 msgid "Select items:" msgstr "Ausgewählte Objekte:" #: e2p_glob.c:410 msgid "selection filter" msgstr "Mehrfachauswahl-Filter" #: e2p_glob.c:416 msgid "Named like" msgstr "Maske(n) für Name:" #: e2p_glob.c:441 msgid "_Invert" msgstr "_Invertieren" #: e2p_glob.c:448 msgid "Select items that DO NOT match the given mask" msgstr "Objekte auswählen, die NICHT der Maske entsprechen." #: e2p_glob.c:449 msgid "Case _sensitive" msgstr "Groß-/Kleinbuchstaben unterscheiden" #: e2p_glob.c:567 msgid "glob" msgstr "Mehrfach_Filter" #: e2p_glob.c:570 msgid "_Glob.." msgstr "_Mehrfachauswahl..." #: e2p_glob.c:571 msgid "Select items matching a specified pattern" msgstr "Wählt Dateien entsprechend einer Maske aus" #: e2p_mvbar.c:411 #, c-format msgid "" "moving %s\n" "to %s\n" "this is item %s of %s" msgstr "" "Verschiebe %s\n" "nach %s\n" "Objekt %s von %s" #: e2p_mvbar.c:769 msgid "moving" msgstr "Verschieben" #: e2p_mvbar.c:795 msgid "Resume moving after pause" msgstr "Verschieben nach der Unterbrechung weiterführen" #: e2p_mvbar.c:801 msgid "Suspend moving, after the current item" msgstr "Unterbricht das Verschieben nach dem nächsten Objekt" #: e2p_mvbar.c:806 #: e2p_mvbar.c:810 msgid "Abort the moving" msgstr "Das Verschieben abbrechen." #: e2p_mvbar.c:948 msgid "mvbar" msgstr "Verschieben_mit_Anzeige" #: e2p_mvbar.c:952 msgid "Move selected item(s), with displayed progress details" msgstr "Die ausgewählten Objekte werden mit Fortschrittsanzeige in das andere Fenster verschoben." #: e2p_names_clip.c:106 msgid "copy_name" msgstr "Namen_kopieren" #: e2p_names_clip.c:109 msgid "Copy _names" msgstr "_Namen kopieren" #: e2p_names_clip.c:110 msgid "Copy path or name of each selected item to the clipboard" msgstr "Kopiert den/die Name(n) des/der ausgewählten Objekte(s) in die Zwischenablage." #: e2p_pack.c:51 msgid ".tar.gz" msgstr ".tar.gz" #: e2p_pack.c:52 msgid ".tar.bz2" msgstr ".tar.bz2" #: e2p_pack.c:53 msgid ".tar" msgstr ".tar" #: e2p_pack.c:54 msgid ".zip" msgstr ".zip" #: e2p_pack.c:55 msgid ".7z" msgstr ".7z" #: e2p_pack.c:56 msgid ".rar" msgstr ".rar" #: e2p_pack.c:57 msgid ".arj" msgstr ".arj" #: e2p_pack.c:58 msgid ".zoo" msgstr ".zoo" #: e2p_pack.c:195 msgid "Filename:" msgstr "Dateiname:" #: e2p_pack.c:196 msgid "archive creation" msgstr "Archiv erzeugen" #: e2p_pack.c:251 msgid "pack" msgstr "Archiv" #: e2p_pack.c:254 msgid "_Pack.." msgstr "_Archiv..." #: e2p_pack.c:255 msgid "Build an archive containing the selected item(s)" msgstr "Erzeugt ein Archiv mit den/dem ausgewählten Objekt(en)." #: e2p_rename.c:630 msgid "No current name pattern is specified" msgstr "Fehler beim Umbenennen: momentaner Name wurde nicht angegeben!" #: e2p_rename.c:643 msgid "No replacement name pattern is specified" msgstr "Fehler beim Umbenennen: neuer Name wurde nicht angegeben!" #: e2p_rename.c:651 msgid "Replacement name pattern cannot have wildcard(s)" msgstr "" #: e2p_rename.c:757 #, c-format msgid "Error in regular expression %s" msgstr "Fehler im regulären Ausdruck %s" #: e2p_rename.c:819 #, c-format msgid "Cannot find anything which matches %s" msgstr "Kein Objekt '%s' gefunden!" #: e2p_rename.c:934 msgid "Rename" msgstr "Umbenennen von:" #: e2p_rename.c:934 #: e2p_rename.c:1049 msgid "to" msgstr "nach" #: e2p_rename.c:1049 msgid "Renamed" msgstr "Umbenannt" #: e2p_rename.c:1453 msgid "rename items" msgstr "Objekte umbenennen" #: e2p_rename.c:1480 msgid "R_ecurse subdirectories" msgstr "_rekursiv (Unterverzeichnisse einschliessen)" #: e2p_rename.c:1485 msgid "_Selected item(s)" msgstr "_ausgewählte Objekte" #: e2p_rename.c:1490 msgid "Match _exact/wildcard" msgstr "exakte Übereinstimmung/Platzhalter verwenden" #: e2p_rename.c:1491 msgid "Match regular e_xpression" msgstr "_regulären Ausdruck verwenden" #: e2p_rename.c:1495 msgid "Current name is like this:" msgstr "Momentaner Name :" #: e2p_rename.c:1516 msgid "New name is _upper case" msgstr "Name komplett in _Großbuchstaben " #: e2p_rename.c:1518 msgid "New name is _lower case" msgstr "Name komplett in _Kleinbuchstaben" #: e2p_rename.c:1520 msgid "_New name is like this:" msgstr "_Neuer Name ist wie folgt:" #: e2p_rename.c:1532 msgid "Con_firm before each rename" msgstr "vor dem Umbenennen eine _Bestätigung anfordern" #: e2p_rename.c:1539 msgid "Get advice on rename options" msgstr "Hilfe zum Umbennen (wird im Ausgabebereich angezeigt)" #: e2p_rename.c:1542 msgid "Stop the current search" msgstr "Suchvorgang anhalten" #: e2p_rename.c:1547 msgid "_Rename" msgstr "_Umbenennen" #: e2p_rename.c:1548 msgid "Begin renaming" msgstr "Startet das Umbenennen" #: e2p_rename.c:1577 msgid "renext" msgstr "Umbenennen" #: e2p_rename.c:1581 msgid "Rename item(s), using wildcards or regular-expressions" msgstr "Dateien/Verzeichnisse mit Hilfe regulärer Ausdrücke umbenennen" #: e2p_sort_by_ext.c:86 msgid "sort_by_ext" msgstr "nach_Erweiterung_sortieren" #: e2p_sort_by_ext.c:89 msgid "Extension _sort" msgstr "_Sortieren nach Erweiterung" #: e2p_sort_by_ext.c:90 msgid "Sort the active file pane by filename extension" msgstr "Sortiert das aktive Fenster nach der Dateierweiterung" #: e2p_thumbs.c:1106 msgid "Rotate _+" msgstr "_Rechtsdrehung" #: e2p_thumbs.c:1107 msgid "Rotate selected images quarter-turn clockwise" msgstr "Dreht das Vorschaubild um 90° im Uhrzeigersinn." #: e2p_thumbs.c:1109 msgid "Rotate _-" msgstr "_Linksdrehung" #: e2p_thumbs.c:1110 msgid "Rotate selected images quarter-turn anti-clockwise" msgstr "Dreht das Vorschaubild um 90° entgegen dem Uhrzeigersinn." #: e2p_thumbs.c:1112 msgid "_Flip" msgstr "_Vertikal spiegeln" #: e2p_thumbs.c:1113 msgid "Flip selected images top-to-bottom" msgstr "Spiegelt das Vorschaubild vertikal." #: e2p_thumbs.c:1123 msgid "_Unselect all" msgstr "_Nichts auswählen" #: e2p_thumbs.c:1129 msgid "Replicate _selection" msgstr "_Auswahl synchronisieren" #: e2p_thumbs.c:1137 msgid "If activated, items selected in this window will also be selected in the associated filelist" msgstr "Wenn aktiviert, werden die ausgewählten Objekte der Vorschau auch im zugehörigen Dateifenster ausgewählt." #: e2p_thumbs.c:1140 msgid "_Clamp size" msgstr "_Größe anpassen" #: e2p_thumbs.c:1148 msgid "If activated, thumbnails will be scaled up or down if needed, into the range 32 to 128 pixels" msgstr "Wenn aktiviert, werden die Vorschaubilder passend skaliert in einem Bereich von 32bis 128 Pixel." #: e2p_thumbs.c:1337 msgid "Ascending" msgstr "_Aufsteigend" #: e2p_thumbs.c:1345 msgid "If activated, items are displayed in ascending order" msgstr "Wenn aktiviert, erfolgt die Sortierung in aufsteigender Reihenfolge." #: e2p_thumbs.c:1477 msgid "pane 1 images" msgstr "Vorschau des Dateifensters 1" #: e2p_thumbs.c:1477 msgid "pane 2 images" msgstr "Vorschau des Dateifensters 2" #: e2p_thumbs.c:1607 msgid "_Sort" msgstr "_Sortieren" #: e2p_thumbs.c:1696 msgid "thumbs" msgstr "Vorschaubilder" #: e2p_thumbs.c:1699 msgid "_Thumbnail.." msgstr "_Vorschau..." #: e2p_thumbs.c:1700 msgid "Display thumbnails of image files in the active pane" msgstr "Zeigt eine Vorschau der Bilddateien im aktuellen Fenster" #: e2p_times.c:212 #, c-format msgid "Cannot change times of %s" msgstr "Fehler! Kann Zeiten von '%s' nicht ändern!" #: e2p_times.c:383 #, c-format msgid "Cannot get current times of %s" msgstr "Fehler beim Erhalten aktueller Zeiten von %s!" #: e2p_times.c:442 #, c-format msgid "Cannot interpret date-time %s" msgstr "Fehler beim Interpretieren der Zeitangabe %s" #: e2p_times.c:534 msgid "Changing 'ctime' requires temporary changes to the system clock. That is normally unwise, as typically, other things rely on system time. Click 'ok' to proceed." msgstr "" "Das Ändern der \"Geändert\"-Zeit erfordert eine temporäre Änderung der Systemzeit. Das ist normalerweise unklug bezogen auf andere Prozesse, die der Systemzeit vertrauen!\n" "Trotzdem ausführen?" #: e2p_times.c:689 msgid "times" msgstr "Zeit-Eigenschaften" #: e2p_times.c:712 msgid "Current values" msgstr "Aktuelle Werte" #: e2p_times.c:713 msgid "New date" msgstr "Neues Datum" #: e2p_times.c:714 msgid "New time" msgstr "Neue Zeit" #: e2p_times.c:717 msgid "Content Modified" msgstr "Verändert" #: e2p_times.c:718 msgid "Inode Changed" msgstr "Geändert" #: e2p_times.c:898 #, c-format msgid "You do not have authority to change time(s) for %s" msgstr "Sie sind nicht berechtigt zum Ändern der Zeit-Eigenschaft(en) für %s" #: e2p_times.c:1004 msgid "timeset" msgstr "Zeiten_ändern" #: e2p_times.c:1007 msgid "Change _times.." msgstr "_Zeiten ändern..." #: e2p_times.c:1008 msgid "Change any of the time properties of selected items" msgstr "Ändert die Zeit-Eigenschaften eines oder mehrerer Objekte" #: e2p_track.c:190 msgid "choose query" msgstr "Wähle Abfrage" #: e2p_track.c:198 msgid "rdf" msgstr "RDF" #: e2p_track.c:355 msgid "tracker query" msgstr "Abfrage der Tracker-Datenbank" #: e2p_track.c:362 msgid "_Search for" msgstr "_Suchen nach" #: e2p_track.c:374 msgid "which match:" msgstr "die entsprechen:" #: e2p_track.c:383 msgid "Search for items whose _mimetype is any of:" msgstr "Suche nach Objekten, deren _Mime-Typ wie folgt ist:" #: e2p_track.c:390 msgid "Search for items using this rdf query:" msgstr "Suche nach Objekten mit dieser RDF-Abfrage:" #: e2p_track.c:393 msgid "Open query-selection dialog" msgstr "Abfrage-Dialog öffnen" #: e2p_track.c:409 msgid "Get help on constructing queries" msgstr "Hilfe, wie man eine Abfrage erstellt" #: e2p_track.c:412 msgid "C_lear" msgstr "_Löschen" #: e2p_track.c:418 msgid "Clear the current query" msgstr "Löscht den letzten Suchvorgang" #: e2p_track.c:427 msgid "Initiate a query using currently-specified criteria" msgstr "Abfrage starten mit den aktuellen Kriterien" #: e2p_track.c:449 msgid "track" msgstr "Trackerabfrage" #: e2p_track.c:452 msgid "_Track.." msgstr "_Trackerabfrage..." #: e2p_track.c:453 msgid "Find items in the tracker database" msgstr "Sucht Objekte in der Tracker-Datenbank" #: e2p_unpack.c:327 msgid "What do you want to do with the unpacked item(s) ?" msgstr "Was soll mit den restlichen Objekten geschehen?" #: e2p_unpack.c:331 msgid "Re_pack" msgstr "_Archiv erneut erzeugen" #: e2p_unpack.c:333 msgid "_Retain" msgstr "_Beibehalten" #: e2p_unpack.c:418 msgid "Selected item is not a supported archive" msgstr "Das ausgewählte Objekt ist kein unterstütztes Archiv!" #: e2p_unpack.c:432 msgid "Recursive unpack is not supported" msgstr "Rekursives Entpacken wird nicht unterstützt!" #: e2p_unpack.c:521 msgid "unpack_with_plugin" msgstr "Entpacken_mit_Plugin" #: e2p_unpack.c:527 msgid "Unpack archive (tar, tar.gz, tar.bz2, zip, 7z, rar, arj, zoo) into a temporary directory" msgstr "Archiv (tar, tar.gz, tar.bz2, zip, 7z, rar, arj, zoo) in ein temporäres Verzeichnis entpacken." #: e2p_upgrade.c:42 #, c-format msgid "" "Configuration arrangements for this version %s of %s are considerably different from those of old versions. To reliably ensure access to the program's current features, it is best to start with fresh settings.\n" "If you proceed, the superseded configuration files in\n" " %s will have '.save' appended to their names.\n" "Feel free to delete them." msgstr "" "Die Konfigurationseinstellungen für diese Version (%s) von %s sind bedeutend anders als die der alten Versionen. Um abzusichern, dass alle Programmteile richtig funktionieren, ist es besser mit einer neuen Konfiguration zu starten.\n" "Soll mit einer neuen Konfiguration gestartet werden?\n" "(Die alten Konfigurationsdateien in\n" " %s werden in '*.save' umbenannt\n" "und können danach gelöscht werden)" #: e2p_upgrade.c:50 #, c-format msgid "" "Several default configuration settings of this version %s of %s are different from those of recent versions (see changelog).\n" "If you click OK, those settings will be updated where possible.\n" "Or else you can Cancel, and later, via the configuration dialog, manuallychange individual settings, or change all settings to current defaults." msgstr "" "Verschiedene Standard-Konfigurationseinstellungen dieser Version (%s) von %s weichen von früheren Versionen ab (siehe CHANGELOG).\n" "Sollen diese Einstellungen jetzt aktualisiert werden (Änderungen sind jederzeit im Konfigurationsdialog möglich!)?" #: e2p_upgrade.c:132 msgid "update information" msgstr "Information aktualisieren" #: e2p_upgrade.c:806 msgid "" msgstr "" #: e2p_view.c:80 msgid "view_with_plugin" msgstr "Betrachten_mit_Plugin" #: e2p_view.c:84 #, c-format msgid "Open the first selected item with the %s text-file viewer" msgstr "Öffnet das erste ausgewählte Objekt mit dem internen Betrachter von %s." #~ msgid "You must specify the site to open" #~ msgstr "Sie müssen den Namen der zu öffnenden Seite eingeben!" #~ msgid "Cannot open '%s'" #~ msgstr "Fehler beim Öffnen '%s'!" #~ msgid "vfs data" #~ msgstr "VFS-Daten" #~ msgid "protocol" #~ msgstr "Protokoll" #~ msgid "host name" #~ msgstr "Rechnername" #~ msgid "Domain name of the site e.g. your.computer.net" #~ msgstr "" #~ "Geben Sie den Domänenname des entfernten Rechners an, z.B. ihr.computer." #~ "net" #~ msgid "path to archive" #~ msgstr "Pfad zum Archiv" #~ msgid "initial directory" #~ msgstr "Startverzeichnis" #~ msgid "Absolute path of directory at the site to be initially displayed" #~ msgstr "" #~ "Hier legen Sie fest, welches Verzeichnis nach Verbindungsaufbau angezeigt " #~ "werden soll." #~ msgid "" #~ "Absolute path of archive in this space, including archoive's full name" #~ msgstr "Geben Sie den Pfad des Archives inkl. Archivname an" #~ msgid "user" #~ msgstr "Benutzername" #~ msgid "" #~ "Username needed to log on to the specified site, or leave blank for " #~ "anonymous" #~ msgstr "" #~ "Geben Sie hier den Benutzername für den Zugriff auf den entfernten " #~ "Rechner ein oder lassen Sie das Feld frei für 'anonymous'" #~ msgid "password" #~ msgstr "Passwort" #~ msgid "" #~ "Password needed to log on to the specified site, or leave blank for email " #~ "address" #~ msgstr "" #~ "Geben Sie hier das Passwort für den Zugriff auf den entfernten Rechner " #~ "ein oder lassen Sie das Feld frei für die E-Mail-Adresse" #~ msgid "Password, if any, needed to open the archive" #~ msgstr "" #~ "Hier können Sie, sofern erforderlich, das Passwort des Archives eingeben" #~ msgid "port" #~ msgstr "Port" #~ msgid "Port number to access the specified site, or leave blank for default" #~ msgstr "" #~ "Hier können Sie eine abweichende Port-Nummer für den Zugriff auf den " #~ "entfernten Rechner angeben." #~ msgid "_Previous" #~ msgstr "_Zurück" #~ msgid "_Next" #~ msgstr "_Weiter" #~ msgid "_Local" #~ msgstr "_Lokal" #~ msgid "Store this data but do not open the place" #~ msgstr "Daten speichern und Ort nicht öffnen" #~ msgid "Open this place" #~ msgstr "Diesen Ort öffnen" #~ msgid "vfs history" #~ msgstr "VFS-Chronik" #~ msgid "Protocol" #~ msgstr "Protokoll" #~ msgid "Host" #~ msgstr "Rechner" #~ msgid "Password" #~ msgstr "Passwort" #~ msgid "Port" #~ msgstr "Port" #~ msgid "Alias" #~ msgstr "Alias" #~ msgid "Delete the selected row" #~ msgstr "Aktuelle Zeile entfernen" #~ msgid "Add a row after the selected one" #~ msgstr "Zeile nach der gewählten hinzufügen" #~ msgid "Open the place described in the selected row" #~ msgstr "Ort der gewählten Zeile öffnen" #~ msgid "Save and close" #~ msgstr "Speichern und schließen" #~ msgid "login details" #~ msgstr "Login-Details" #~ msgid "Domain" #~ msgstr "Domäne" #~ msgid "Cannot unmount %s - %s" #~ msgstr "Fehler beim Unmounten von %s - %s" #~ msgid "Cannot mount %s - %s" #~ msgstr "Fehler beim Mounten von %s - %s" #~ msgid "Cannot open file %s - %s" #~ msgstr "Fehler beim Öffnen der Datei %s - %s" #~ msgid "Error reading file %s - %s" #~ msgstr "Fehler beim Lesen der Datei %s - %s" #~ msgid "Cannot close file %s - %s" #~ msgstr "Fehler beim Schließen der Datei %s - %s" #~ msgid "Error writing file %s - %s" #~ msgstr "Fehler beim Schreiben der Datei %s - %s" #~ msgid "Enable vfs functionality using gvfs library" #~ msgstr "VFS-Funktionalität mittels gvfs-Bibliothek" #~ msgid "_file selection" #~ msgstr "_Datei-Auswahl" #~ msgid "_stock icons" #~ msgstr "_Icon-Vorrat" #~ msgid "" #~ msgstr "" #~ msgid "_Mounted directory" #~ msgstr "_Gemountetes Verzeichnis" #~ msgid "_Remote directory" #~ msgstr "_Entferntes Verzeichnis" #~ msgid "_Archive" #~ msgstr "_Archiv" #~ msgid "match unknown users" #~ msgstr "unbekannten Benutzern" #~ msgid "this gid" #~ msgstr "dieser Gruppen-ID:" #~ msgid "match unknown groups" #~ msgstr "unbekannten Gruppen" #~ msgid "this" #~ msgstr "so ist" #~ msgid "like this" #~ msgstr "so ähnlich ist" #~ msgid "like this regex" #~ msgstr "dem regulären Ausdruck entspricht" #~ msgid "Cannot stat %s" #~ msgstr "Fehler bei fstat %s!" #~ msgid "Enabled access to %s" #~ msgstr "Zugriff auf Verzeichnis '%s' freigegeben!" #~ msgid "Cannot delete part or all of existing %s" #~ msgstr "Löschen von Teilen oder alle '%s' nicht möglich!" #~ msgid "Cannot read %s" #~ msgstr "Lesen von '%s' nicht möglich!" #~ msgid "Cannot delete part or all of %s" #~ msgstr "Löschen von Teilen oder alles von '%s' nicht möglich!" #~ msgid "Cannot access directory %s" #~ msgstr "Zugriff auf Verzeichnis '%s' nicht möglich!" #~ msgid "File operation in progress, %s added to queue" #~ msgstr "Datei-Aktion in Arbeit, %s in die Warteschlange gestellt." #~ msgid "No permission to access directory '%s'!" #~ msgstr "Keine Zugriffsrechte zum Lesen des Verzeichnisses '%s'!" #~ msgid "Cannot create directory '%s' - %s" #~ msgstr "Fehler beim Erstellen des Verzeichnisses '%s' - %s!" #~ msgid "Cannot find %s" #~ msgstr "Finden von '%s' nicht möglich!" #~ msgid "_directories" #~ msgstr "_Verzeichnisse" #~ msgid "Cannot write to directory %s" #~ msgstr "Schreiben nach Verzeichnis '%s' nicht möglich!" #~ msgid "" #~ "\n" #~ "You're not allowed to write in %s" #~ msgstr "" #~ "\n" #~ "Sie haben keine Rechte, für das Schreiben in %s" #~ msgid "Calculate the 'apparent' disk usage of selected item(s)" #~ msgstr "Berechnet den belegten Speicherplatz des/der gewählten Objekte(s)." #~ msgid "List child processes" #~ msgstr "Kindprozesse auflisten" #~ msgid "list_children" #~ msgstr "Kindprozesse_auslisten" #~ msgid "c_onfirm" #~ msgstr "_Bestätigung" #~ msgid "'%s' is not a supported archive type" #~ msgstr "'%s' ist kein unterstütztes Archiv!" #~ msgid "Display a virtual directory for the selected row" #~ msgstr "Zeigt ein virtuelles Verzeichnis für die ausgewählte Zeile an." #~ msgid "run in background" #~ msgstr "Befehle im Hintergrund ausführen" #~ msgid "" #~ "This causes commands operating on the selected items to run in the " #~ "background, not blocking the interface" #~ msgstr "" #~ "Wenn aktiviert, werden Befehle im Hintergrund ausgeführt und blockieren " #~ "nicht die Oberfläche." #~ msgid "Execution of '%s' failed" #~ msgstr "Fehler beim Ausführen von '%s'!" #~ msgid "other colors" #~ msgstr "Weitere Farben" #~ msgid "" #~ "This color is used for a potential destination of a drag 'n' drop " #~ "operation" #~ msgstr "" #~ "Der Hintergrund des potentiellen Zieles einer Drag&Drop-Operation wird in " #~ "dieser Farbe dargestellt." #~ msgid "Plugin not found!" #~ msgstr "Plugin nicht gefunden!" #~ msgid "_Unpack.." #~ msgstr "_Entpacken (temporär)..." #~ msgid "" #~ msgstr "" #~ msgid "toggle_full" #~ msgstr "anderes_Fenster_aus" #~ msgid "toggle_hidden" #~ msgstr "versteckte_Dateien" emelfm2-0.4.1/po/ja.po0000600000175000017500000045113711014260437013370 0ustar cairocairo# translation of ja.po to Japanese # UTUMI Hirosi , 2005-2008. msgid "" msgstr "" "Project-Id-Version: ja\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: (null)\n" "PO-Revision-Date: 2008-03-08 15:58+0900\n" "Last-Translator: UTUMI Hirosi \n" "Language-Team: Japanese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: e2_about_dialog.c:107 e2_action.c:851 e2_alias.c:323 e2_command.c:2781 msgid "help" msgstr "ヘルプ" #: e2_about_dialog.c:118 e2_action.c:826 msgid "about" msgstr "about" #: e2_about_dialog.c:126 #, c-format msgid "" "An \"orthodox\" file manager for GTK+2\n" "\n" "Copyright © %s\n" "\n" "This program is licensed under the terms of the General Public License and " "comes with ABSOLUTELY NO WARRANTY\n" "\n" "This binary was compiled on %s\n" "using %s and GTK+%d.%d.%d" msgstr "" #: e2_about_dialog.c:142 #, c-format msgid "" "The file\n" "%s\n" "gives details." msgstr "" "詳細は\n" "%s\n" "を参照" #: e2_about_dialog.c:145 msgid "contributors" msgstr "貢献者" #: e2_about_dialog.c:150 msgid "" "This program is based on emelFM, developed by Michael Clark.\n" "\n" "Contributions have been made by many friends." msgstr "" "このプログラムは emelFM (開発者: Michael Clark) を基にしています。\n" "\n" "多くの友人たちが貢献してくれました。" #: e2_about_dialog.c:160 msgid "usage" msgstr "使いかた" #: e2_about_dialog.c:169 e2_option.c:1073 e2_output.c:980 msgid "commands" msgstr "コマンド" #: e2_about_dialog.c:179 e2_filetype_dialog.c:1193 msgid "Read the file" msgstr "お読みください" #: e2_about_dialog.c:179 e2_context_menu.c:554 e2_filetype_dialog.c:1192 #: e2p_view.c:83 msgid "_View" msgstr "見る(_V)" #: e2_action.c:798 msgid "bookmark" msgstr "ブックマーク" #: e2_action.c:799 e2_command.c:1858 msgid "command" msgstr "コマンド" #: e2_action.c:800 msgid "configure" msgstr "設定" #: e2_action.c:801 e2_option__default.c:160 e2_option__default.c:329 #: e2_toolbar.c:2794 msgid "dialog" msgstr "ダイアログ" #: e2_action.c:802 msgid "dirline" msgstr "ディレクトリ行" #: e2_action.c:803 e2p_du.c:219 msgid "file" msgstr "ファイル" #: e2_action.c:804 e2_task.c:3419 msgid "find" msgstr "find" #: e2_action.c:805 msgid "list" msgstr "リスト" #: e2_action.c:806 msgid "option" msgstr "オプション" #: e2_action.c:807 e2_option.c:1094 msgid "output" msgstr "出力" #: e2_action.c:808 msgid "pane_active" msgstr "ペインアクティヴ" #: e2_action.c:809 msgid "pane1" msgstr "ペイン1" #: e2_action.c:810 msgid "pane2" msgstr "ペイン2" #: e2_action.c:811 e2_option.c:1099 msgid "panes" msgstr "ペイン" #: e2_action.c:812 e2_config_dialog.c:935 msgid "plugin" msgstr "プラグイン" #: e2_action.c:813 msgid "toggle" msgstr "切替" #: e2_action.c:815 msgid "separator" msgstr "セパレータ" #: e2_action.c:816 msgid "" msgstr "<カスタムコマンド>" #: e2_action.c:817 msgid "" msgstr "<セパレータ>" #: e2_action.c:818 msgid "" msgstr "<サブメニュー>" #: e2_action.c:821 msgid "" msgstr "<アクション>" #: e2_action.c:822 msgid "" msgstr "<ブックマーク>" #: e2_action.c:823 msgid "" msgstr "<フィルタ>" #: e2_action.c:824 msgid "" msgstr "<ライン>" #: e2_action.c:825 msgid "" msgstr "<メニュー>" #: e2_action.c:827 msgid "add" msgstr "追加" #: e2_action.c:828 msgid "adjust_ratio" msgstr "比率を調整" #: e2_action.c:829 e2_bookmark.c:302 e2_option_tree.c:667 msgid "children" msgstr "子" #: e2_action.c:830 e2_alias.c:321 msgid "clear" msgstr "クリア" #: e2_action.c:831 msgid "clear_history" msgstr "履歴をクリア" #: e2_action.c:832 msgid "complete" msgstr "完了" #: e2_action.c:833 msgid "application" msgstr "アプリケーション" #: e2_action.c:834 e2_task.c:1426 msgid "copy" msgstr "コピー" #: e2_action.c:835 msgid "copy_as" msgstr "別名でコピー" #: e2_action.c:836 msgid "copy_merge" msgstr "マージをコピー" #: e2_action.c:837 msgid "copy_with_time" msgstr "時刻付きでコピー" #: e2_action.c:838 msgid "default" msgstr "デフォルト" #: e2_action.c:839 msgid "delete" msgstr "削除" #: e2_action.c:840 msgid "edit" msgstr "編集" #: e2_action.c:841 msgid "edit_again" msgstr "再編集" #: e2_action.c:842 msgid "filetype" msgstr "ファイルの種類" #: e2_action.c:843 msgid "focus" msgstr "フォーカス" #: e2_action.c:844 msgid "focus_toggle" msgstr "フォーカス切替" #: e2_action.c:845 msgid "fullscreen" msgstr "" #: e2_action.c:846 msgid "go_back" msgstr "戻る" #: e2_action.c:847 msgid "go_forward" msgstr "進む" #: e2_action.c:848 msgid "go_up" msgstr "上へ" #: e2_action.c:849 msgid "goto_bottom" msgstr "一番下へ" #: e2_action.c:850 msgid "goto_top" msgstr "一番上へ" #: e2_action.c:852 e2_option.c:1085 msgid "history" msgstr "履歴" #: e2_action.c:853 e2_mkdir_dialog.c:918 msgid "info" msgstr "情報" #: e2_action.c:854 msgid "insert_selection" msgstr "選択したものを挿入(_s)" #: e2_action.c:855 msgid "invert_selection" msgstr "選択を反転" #: e2_action.c:856 msgid "mirror" msgstr "同期" #: e2_action.c:857 msgid "mkdir" msgstr "mkdir" #: e2_action.c:858 msgid "mountpoints" msgstr "マウントポイント" #: e2_action.c:859 e2_task.c:1748 msgid "move" msgstr "移動" #: e2_action.c:860 msgid "move_as" msgstr "別名で移動" #: e2_action.c:861 e2_select_dir_dialog.c:45 msgid "open" msgstr "開く" #: e2_action.c:862 msgid "open_in_other" msgstr "他のペインに開く" #: e2_action.c:863 msgid "open_with" msgstr "アプリで開く" #: e2_action.c:864 e2p_find.c:3069 msgid "owners" msgstr "所有者" #: e2_action.c:865 msgid "page_down" msgstr "page_down" #: e2_action.c:866 msgid "page_up" msgstr "上へ" #: e2_action.c:867 msgid "pending" msgstr "未決定" #: e2_action.c:868 e2_permissions_dialog.c:318 msgid "permissions" msgstr "権限" #: e2_action.c:869 msgid "print" msgstr "print" #: e2_action.c:870 e2_alias.c:322 msgid "quit" msgstr "終了" #: e2_action.c:871 msgid "refresh" msgstr "更新" #: e2_action.c:872 msgid "refreshresume" msgstr "更新レジューム" #: e2_action.c:873 msgid "refreshsuspend" msgstr "更新サスペンド" #: e2_action.c:874 e2_task.c:2358 msgid "rename" msgstr "リネーム" #: e2_action.c:875 msgid "scroll_down" msgstr "scroll_down" #: e2_action.c:876 msgid "scroll_up" msgstr "scroll_up" #: e2_action.c:877 e2_option.c:1102 msgid "search" msgstr "検索" #: e2_action.c:878 msgid "send" msgstr "送る" #: e2_action.c:879 msgid "set" msgstr "セット" #: e2_action.c:880 msgid "show" msgstr "表示" #: e2_action.c:881 msgid "show_hidden" msgstr "隠しを表示(_h)" #: e2_action.c:882 msgid "show_menu" msgstr "メニューを表示" #: e2_action.c:883 msgid "sortaccesssed" msgstr "アクセス順にソート" #: e2_action.c:884 msgid "sortchanged" msgstr "changed 順にソート" #: e2_action.c:885 msgid "sortgroup" msgstr "グループ順にソート" #: e2_action.c:886 msgid "sortmodified" msgstr "modified 順にソート" #: e2_action.c:887 msgid "sortname" msgstr "名前順にソート" #: e2_action.c:888 msgid "sortpermission" msgstr "権限順にソート" #: e2_action.c:889 msgid "sortsize" msgstr "サイズ順にソート" #: e2_action.c:890 msgid "sortuser" msgstr "ユーザ順にソート" #: e2_action.c:891 msgid "switch" msgstr "切替" #: e2_action.c:892 e2_option__default.c:274 e2p_find.c:3020 msgid "symlink" msgstr "symlink" #: e2_action.c:893 msgid "symlink_as" msgstr "symlink_as" #: e2_action.c:894 msgid "sync" msgstr "同期" #: e2_action.c:895 msgid "toggle_direction" msgstr "方向を切り替え(_d)" #: e2_action.c:897 msgid "toggle_select_all" msgstr "全て選択非選択のトグル" #: e2_action.c:898 msgid "toggle_selected" msgstr "選択を切替" #: e2_action.c:899 e2_bookmark.c:564 msgid "trash" msgstr "ゴミ箱" #: e2_action.c:900 msgid "trashempty" msgstr "ゴミ箱を空に" #: e2_action.c:902 msgid "tree" msgstr "ツリー" #: e2_action.c:904 msgid "unpack" msgstr "解凍" #: e2_action.c:905 e2_option.c:1107 msgid "view" msgstr "表示" #: e2_action.c:906 msgid "view_again" msgstr "再表示" #: e2_action.c:907 msgid "view_at" msgstr "次のもので閲覧" #: e2_action.c:909 e2_bookmark.c:302 e2_option_tree.c:667 msgid "child" msgstr "子列" #: e2_action.c:910 msgid "ctrl" msgstr "ctrl" #: e2_action.c:911 msgid "dirs" msgstr "dirs" #: e2_action.c:912 msgid "escape" msgstr "escape" #: e2_action.c:913 msgid "expand" msgstr "展開" #: e2_action.c:914 e2p_du.c:219 msgid "files" msgstr "ファイル" #: e2_action.c:915 msgid "off" msgstr "オフ" #: e2_action.c:916 msgid "on" msgstr "オン" #: e2_action.c:917 msgid "quote" msgstr "引用" #: e2_action.c:918 msgid "shift" msgstr "shift" #: e2_action.c:919 msgid "top" msgstr "トップ" #: e2_action.c:923 msgid "" msgstr "<スペースメニュー>" #: e2_action.c:924 msgid "dummy" msgstr "ダミー" #: e2_action.c:925 msgid "namespace" msgstr "名前スペース" #: e2_action.c:926 msgid "unpack_in_other" msgstr "他のペインに解凍" #: e2_action.c:929 msgid "key" msgstr "キー" #: e2_action.c:930 e2_vfs_dialog.c:699 msgid "alias" msgstr "エイリアス" #: e2_alias.c:316 msgid "x" msgstr "x" #: e2_alias.c:317 e2_alias.c:318 e2_toolbar.c:2893 msgid "Done. Press enter " msgstr "完了。Enterを押してください" #: e2_alias.c:317 msgid "xx" msgstr "xx" #: e2_alias.c:324 e2_output.c:985 msgid "keys" msgstr "キー" #: e2_alias.c:325 msgid "e2ps" msgstr "e2ps" #: e2_alias.c:327 msgid "cns" msgstr "cns" #: e2_alias.c:350 msgid "Match" msgstr "一致" #: e2_alias.c:353 msgid "Stop" msgstr "停止" #: e2_alias.c:355 msgid "Replace" msgstr "置換" #: e2_bookmark.c:298 msgid "Are you sure that you want to delete the bookmark" msgstr "本当にこのブックマークを削除しますか" #: e2_bookmark.c:302 e2p_du.c:222 msgid "and" msgstr "and" #: e2_bookmark.c:307 msgid "confirm bookmark delete" msgstr "ブックマークを削除するときに確認" #: e2_bookmark.c:375 msgid "_Add after" msgstr "項目(_A)" #: e2_bookmark.c:376 msgid "Bookmark the current directory after the selected bookmark" msgstr "選んだブックマークの後に現在のディレクトリをブックマーク" #: e2_bookmark.c:382 msgid "Add as _child" msgstr "子列として追加(_c)" #: e2_bookmark.c:383 msgid "Bookmark the current directory a a child of the selected bookmark" msgstr "選んだブックマークの子項目として現在のディレクトリをブックマーク" #: e2_bookmark.c:389 e2_context_menu.c:563 e2p_unpack.c:335 msgid "_Delete" msgstr "削除(_D)" #: e2_bookmark.c:390 msgid "Delete the selected bookmark, and its children if any" msgstr "選んだブックマークと下位のブックマークを削除" #: e2_bookmark.c:552 msgid "_home" msgstr "_home" #: e2_bookmark.c:555 msgid "cdrom" msgstr "cdrom" #: e2_bookmark.c:557 msgid "root" msgstr "root" #: e2_bookmark.c:558 msgid "Your home directory" msgstr "ホームディレクトリ" #: e2_bookmark.c:558 msgid "home" msgstr "home" #: e2_bookmark.c:559 msgid "media" msgstr "メディア" #: e2_bookmark.c:560 msgid "mnt" msgstr "mnt" #: e2_bookmark.c:561 msgid "usr" msgstr "usr" #: e2_bookmark.c:562 msgid "usr/local" msgstr "usr/local" #: e2_bookmark.c:564 msgid "default trash directory" msgstr "デフォルトのゴミ箱用ディレクトリ" #: e2_bookmark.c:582 e2_context_menu.c:613 e2_menu.c:995 e2_plugins.c:1278 #: e2_toolbar.c:2851 e2_toolbar.c:2918 e2_toolbar.c:3085 msgid "Label" msgstr "ラベル" #: e2_bookmark.c:584 e2_context_menu.c:615 e2_menu.c:993 e2_plugins.c:1280 #: e2_toolbar.c:2853 e2_toolbar.c:2920 e2_toolbar.c:3087 msgid "Icon" msgstr "アイコン" #: e2_bookmark.c:586 e2_menu.c:997 e2_plugins.c:1282 e2_toolbar.c:2855 #: e2_toolbar.c:2922 e2_toolbar.c:3089 msgid "Tooltip" msgstr "ツールチップ" #: e2_bookmark.c:588 e2_plugins.c:1286 e2_vfs_dialog.c:1429 msgid "Path" msgstr "パス" #: e2_bookmark.c:596 msgid "open bookmark in other pane on middle-button click" msgstr "中ボタンのクリックで他のペインにブックマークを開く" #: e2_bookmark.c:597 msgid "" "Clicking the middle mouse button on a bookmark will open it in the other " "file pane" msgstr "ブックマークの中クリックで他のペインに開く" #: e2_bookmark.c:601 msgid "focus file pane after opening a bookmark in it" msgstr "ブックマークを開いたのちファイルペインにフォーカス" #: e2_bookmark.c:602 msgid "" "After opening a bookmark in the inactive file pane, that pane will become " "the active one" msgstr "" "アクティブでないファイルペインにブックマークを開いたのち、そのペインをアク" "ティブにする" #: e2_bookmark.c:608 msgid "confirm any delete of a selected bookmark" msgstr "選んだブックマークを削除するときに確認" #: e2_bookmark.c:609 msgid "You will be asked to confirm, before deleting any bookmark" msgstr "ブックマークを削除するときに確認" #: e2_bookmark.c:613 msgid "confirm any delete of multiple bookmarks" msgstr "ブックマークを複数削除するときに確認" #: e2_bookmark.c:614 msgid "" "You will be asked to confirm, before deleting any bookmark that has " "'children'" msgstr "子列を持つブックマークを削除するときに確認" #: e2_button.c:25 e2p_upgrade.c:107 msgid "_OK" msgstr "_OK" #: e2_button.c:28 e2p_upgrade.c:116 msgid "_Cancel" msgstr "キャンセル(_C)" #: e2_button.c:30 msgid "_Yes" msgstr "はい(_Y)" #: e2_button.c:33 msgid "_No" msgstr "いいえ(_N)" #: e2_button.c:36 msgid "Yes to _all" msgstr "全てYes(_a)" #: e2_button.c:38 e2p_cpbar.c:731 e2p_cpbar.c:735 e2p_mvbar.c:805 #: e2p_mvbar.c:809 msgid "_Stop" msgstr "停止(_S)" #: e2_button.c:40 e2p_config.c:1161 e2p_config.c:1288 msgid "_Apply" msgstr "適用(_A)" #: e2_button.c:43 msgid "_Apply to all" msgstr "全て了解(_A)" #: e2_button.c:45 e2_tree_dialog.c:1115 e2p_thumbs.c:1117 msgid "_Refresh" msgstr "更新(_R)" #: e2_button.c:47 msgid "_Close" msgstr "閉じる(_C)" #: e2_button.c:49 msgid "C_reate" msgstr "作成(_r)" #: e2_button.c:51 e2_option_tree.c:978 e2_permissions_dialog.c:458 #: e2p_acl.c:3728 msgid "_Remove" msgstr "削除(_R)" #: e2_cache.c:726 #, c-format msgid "" "%sThis file stores runtime configuration data for %s.\n" "%sThe file will be overwritten each time %s is shut down.\n" "\n" msgstr "" "%sこのファイルは実行時の設定データを %s に保存します。\n" "%sこのファイルは %s が終了するたびに上書きされます\n" "\n" #: e2_cache.c:955 #, c-format msgid "Cannot write cache file %s - %s" msgstr "キャッシュファイル %s を書き込めません - %s" #: e2_cl_option.c:43 #, c-format msgid "usage: %s [option]\n" msgstr "使い方: %s [オプション]\n" #: e2_cl_option.c:56 msgid "" "Program options:\n" "-1,--one=DIR set 1st pane's start directory to DIR\n" "-2,--two=DIR set 2nd pane's start directory to DIR\n" "-c,--config=DIR set config directory to DIR (default: ~/.config/" "emelfm2)\n" "-e,--encoding=TYPE set filesystem character encoding to TYPE\n" "-f,--fallback-encoding set fallback encoding (default: ISO-8859-1)\n" "-i,--ignore-problems ignore encoding/locale problems (at your own risk!)\n" "-l,--log-all maximise scope of error logging\n" "-m,--daemon run program as daemon\n" "-r,--run-at-start=CMD run command CMD at session start\n" "-s,--set-option=OPT set one-line gui option using config-file formatted " "OPT\n" "-t,--trash=DIR set trash directory to DIR (default: ~/.local/share/" "Trash/files)\n" "\n" "Help options:\n" "-h,--help show this help message\n" "-u,--usage display brief usage messsage\n" "-v,--version display version and build info\n" msgstr "" #: e2_cl_option.c:78 msgid "" "-d,--debug=[1-5] set debug level from 1 (low) to 5 (high)\n" "-x,--verbose display time/location info on debug messages\n" msgstr "" #: e2_cl_option.c:91 #, c-format msgid "" "%s v. %s\n" "Licensed under the GPL\n" "Copyright (C) %s\n" "Build date: %s\n" "Build platform: GTK+ %d.%d.%d %s\n" msgstr "" "%s v. %s\n" "ライセンスは GPL です\n" "Copyright (C) %s\n" "Build date: %s\n" "Build platform: GTK+ %d.%d.%d %s\n" #: e2_cl_option.c:299 msgid "Startup options must begin with \"-\" or \"--\"\n" msgstr "起動時のオプションは \"-\" か \"--\" で始めてください\n" #: e2_command.c:643 e2_command.c:1301 e2_command.c:1587 msgid "returned" msgstr "returned" #: e2_command.c:789 #, c-format msgid "Command '%s' - %s" msgstr "コマンド '%s' - %s" #: e2_command.c:1066 #, c-format msgid "Error while launching '%s'" msgstr "'%s' の起動中にエラー" #: e2_command.c:1092 msgid "Cannot find last child process" msgstr "最後の子プロセスが見つかりません" #: e2_command.c:1100 #, c-format msgid "Cannot find child process with pid %ld" msgstr "pid %ld の子プロセスが見つかりません" #: e2_command.c:1103 #, c-format msgid "Cannot communicate to process %ld" msgstr "プロセス %ld に通信できません" #: e2_command.c:1118 msgid "Failed writing to child" msgstr "子への書き込みに失敗" #: e2_command.c:1594 #, c-format msgid "Strange error: could not run '%s'" msgstr "不明なエラー: '%s' を実行できません" #: e2_command.c:1617 #, c-format msgid "The process with pid %ld is not our child" msgstr "pid %ld のプロセスは子ではありません" #: e2_command.c:1629 e2_command.c:1640 #, c-format msgid "Failed writing to child: %s" msgstr "子への書き込みに失敗: %s" #: e2_command.c:1854 msgid "nothing is waiting" msgstr "待機しているものはありません" #: e2_command.c:1878 e2_command.c:1982 e2_command.c:2045 msgid "" msgstr "<選んだアイテム>" #: e2_command.c:1953 msgid "nothing is running" msgstr "実行しているものはありません" #: e2_command.c:1957 msgid " pid || directory || command" msgstr " pid || ディレクトリ || コマンド" #: e2_command.c:2019 msgid "command || directory || result" msgstr "コマンド || ディレクトリ || 結果" #: e2_command.c:2051 e2_command.c:2063 msgid "OK" msgstr "OK" #: e2_command.c:2051 e2_command.c:2066 msgid "error" msgstr "エラー" #: e2_command.c:2321 #, c-format msgid "Cannot run '%s'" msgstr "%s を実行できません" #: e2_command.c:2439 msgid "Failed to expand macros" msgstr "マクロの展開に失敗" #: e2_command.c:2587 #, c-format msgid "Failed parsing command '%s' - %s" msgstr "コマンド '%s' の解析に失敗 - %s" #: e2_command.c:2675 #, c-format msgid "Cannot send \"%s\" to a child process" msgstr "\"%s\" を子プロセスに送れません" #: e2_command.c:2870 msgid "x terminal emulator:" msgstr "ターミナルエミュレータ:" #: e2_command.c:2871 msgid "" "This is the external command/application that will be be run when emelFM2 is " "asked to open a terminal" msgstr "emelFM2 がターミナルを実行するときの外部コマンド/アプリケーション" #: e2_command.c:2874 msgid "use external file-viewer" msgstr "外部のファイルビューアを使う" #: e2_command.c:2875 msgid "" "If activated, the command entered below will be run to view file content, " "instead of launching the internal viewer" msgstr "" "有効にすると、内蔵ビューアの代わりにこのコマンドでファイル内容を閲覧します" #: e2_command.c:2878 msgid "viewer command:" msgstr "ビューアのコマンド:" #: e2_command.c:2879 msgid "" "This is a command to run an external application for viewing file content.\n" "The first selected item will be supplied as the first argument" msgstr "" "ファイルを閲覧するための外部アプリケーションを実行するコマンド。\n" "最初に選んだアイテムが最初の引数に当てられます" #: e2_command.c:2883 msgid "use external file-editor" msgstr "外部のファイルエディタを使う" #: e2_command.c:2884 msgid "" "If activated, the command entered below will be run to edit file content, " "instead of launching the internal editor" msgstr "" "有効にすると、内蔵エディタの代わりにこのコマンドでファイル内容を編集します" #: e2_command.c:2887 msgid "editor command:" msgstr "エディタのコマンド:" #: e2_command.c:2888 msgid "" "This is a command to run an external application for editing file content.\n" "The first selected item will be supplied as the first argument" msgstr "" "ファイルを編集するための外部アプリケーションを実行するコマンド。\n" "最初に選んだアイテムが最初の引数に当てられます" #: e2_command.c:2892 msgid "use external encoding converter" msgstr "外部のエンコーディング変換ツールを使う" #: e2_command.c:2893 msgid "" "If activated, the command entered below will be run, instead of using the " "internal conversion functions, to convert file character encoding when needed" msgstr "" "有効にすると、内部変換機能の代わりに 指定したコマンドでファイルの文字エンコー" "ディングを変換します" #: e2_command.c:2896 e2p_upgrade.c:744 e2p_upgrade.c:745 msgid "File encoding:" msgstr "ファイルのエンコーディング:" #: e2_command.c:2897 msgid "converter command:" msgstr "変換ツールのコマンド:" #: e2_command.c:2898 msgid "" "A command which runs an external application to convert text encoding to UTF-" "8" msgstr "" "テキストエンコーディングを UTF-8 に変換するための外部アプリケーションのコマン" "ド" #: e2_command.c:2902 msgid "use aliases" msgstr "エイリアスを使う" #: e2_command.c:2903 msgid "This is a general switch to turn on/off alias handling for commands" msgstr "" #: e2_command.c:2906 msgid "interpret 'relative' paths" msgstr "'相対' パスを解釈する" #: e2_command.c:2907 msgid "" "This enables correct interpretation of paths containing '..' etc. These " "might be typed in, or attached to a button, say" msgstr "" #: e2_command.c:2910 msgid "running commands survive shutdown" msgstr "終了してもコマンド実行を続ける" #: e2_command.c:2911 msgid "" "If activated, commands that are still running will not be terminated at end " "of emelFM2 session" msgstr "" #: e2_command.c:2915 msgid "watch priority" msgstr "監視の優先度" #: e2_command.c:2916 msgid "" "The watch priority of commands influences how fast program\n" "output is read from i/o channels. A too-high priority might\n" "decrease gui responsiveness and break automatic scrolling.\n" "(note: negative values mean high priority, positive values mean low priority)" msgstr "" "どの頻度で入出力チャネルからの出力を読み込むか。\n" "優先度が高いと GUI の反応が遅くなり、自動スクロールが\n" "効きにくくなる。\n" "(注意: 小さい値にすると優先度高, 大きい値にすると優先度低)" #: e2_command.c:2925 msgid "stop after timeout" msgstr "タイムアウトしたら停止" #: e2_command.c:2926 msgid "" "If activated, each file operation will be terminated if not finished within " "the time-interval set below" msgstr "有効にすると、指定した時間が過ぎるとファイル操作を中止します" #: e2_command.c:2929 msgid "timeout interval" msgstr "タイムアウトまでの時間" #: e2_command.c:2930 msgid "The interval (seconds) allowed to complete any file operation" msgstr "ファイル操作の完了を待つ時間" #: e2_command.c:2933 msgid "confirm any delete" msgstr "削除時に確認" #: e2_command.c:2934 msgid "" "If activated, you will be asked for confirmation before actually deleting " "anything" msgstr "有効にすると、アイテムの削除前に確認を行います" #: e2_command.c:2936 msgid "confirm any overwrite" msgstr "上書き時に確認" #: e2_command.c:2937 msgid "" "If activated, you will be asked for confirmation before actually overwriting " "anything" msgstr "有効にすると、アイテムの上書き前に確認を行います" #: e2_command.c:2939 msgid "relative symlinks" msgstr "相対的な symlink" #: e2_command.c:2940 msgid "" "This gives each created symlink a relative path to its source, like '../../" "/', instead of a full path referenced to /" msgstr "" #: e2_command_line.c:577 msgid "mounts" msgstr "マウント" #: e2_command_line.c:579 msgid "all" msgstr "全て" #: e2_command_line.c:672 #, c-format msgid "Warning - process %s is not active" msgstr "警告 - プロセス %s はアクティブではありません" #: e2_command_line.c:988 msgid "show last" msgstr "最後のコマンドを表示" #: e2_command_line.c:989 msgid "" "If activated, the last-entered command will be displayed, instead of an " "empty line" msgstr "有効にすると、空行ではなく最後に入力したコマンドを表示します" #: e2_command_line.c:994 e2_command_line.c:1041 msgid "maximum number of history entries" msgstr "履歴の最大数" #: e2_command_line.c:995 msgid "" "This is the largest number of command-line history entries that will be " "recorded" msgstr "コマンドライン履歴の最大数" #: e2_command_line.c:998 e2_command_line.c:1046 msgid "double entries" msgstr "項目の重複" #: e2_command_line.c:999 e2_command_line.c:1047 msgid "" "This allows entries to be recorded more than once in the history list, so " "the last entry is always close to hand" msgstr "" #: e2_command_line.c:1002 e2_command_line.c:1051 msgid "cyclic list" msgstr "リストを循環" #: e2_command_line.c:1003 e2_command_line.c:1052 msgid "" "When scanning the history list, cycle from either end around to the other " "end, instead of stopping" msgstr "" #: e2_command_line.c:1007 e2_command_line.c:1056 msgid "show as a menu" msgstr "メニューとして表示" #: e2_command_line.c:1008 msgid "" "If activated, the history entries will be presented as a menu. For most Gtk" "+2 themes, this will not be as attractive as the list view" msgstr "" #: e2_command_line.c:1015 msgid "append space after unique items" msgstr "ユニークアイテムの後に空白を追加" #: e2_command_line.c:1016 msgid "" "This appends a 'space' character to the end of a unique successful match of " "a file" msgstr "" #: e2_command_line.c:1023 msgid "show last entry" msgstr "最後の項目を表示" #: e2_command_line.c:1024 msgid "" "If activated, the last-entered directory will be displayed, instead of an " "empty line" msgstr "有効にすると、空行の代わりに最後に入力したディレクトリを表示します" #: e2_command_line.c:1028 msgid "show pathname as a tooltip" msgstr "パス名をツールチップで表示" #: e2_command_line.c:1029 msgid "" "If activated, the full directory pathname will display as a tooltip. This is " "useful when the path is too long for the normal display" msgstr "" "有効にすると、ディレクトリのフルパスをツールチップで表示します。通常表示には" "長すぎるパスを表示するとき便利です" #: e2_command_line.c:1034 msgid " only" msgstr "<タブ> のみ" #: e2_command_line.c:1034 msgid "inserted" msgstr "挿入" #: e2_command_line.c:1034 msgid "selected" msgstr "選択" #: e2_command_line.c:1035 msgid "directory path completion" msgstr "ディレクトリパスの補完" #: e2_command_line.c:1036 msgid "This determines the mode of completion when keying a directory-path" msgstr "" #: e2_command_line.c:1042 msgid "" "This is the largest number of directory-line history entries that will be " "retained" msgstr "" #: e2_command_line.c:1057 msgid "" "If activated, the directory line history will be presented as a menu. For " "most for most Gtk+2 themes, this will not be as attractive as the list view" msgstr "" #: e2_config_dialog.c:175 e2_config_dialog.c:1484 msgid "configuration" msgstr "設定" #: e2_config_dialog.c:658 msgid "Reverting to default configuration cannot be undone" msgstr "デフォルトの設定に戻すと元に戻せません" #: e2_config_dialog.c:781 e2_option_tree_context_menu.c:243 msgid "_Expand" msgstr "展開(_E)" #: e2_config_dialog.c:782 msgid "Expand all rows" msgstr "全ての列を展開" #: e2_config_dialog.c:783 e2_option_tree_context_menu.c:247 #: e2_tree_dialog.c:1113 msgid "C_ollapse" msgstr "折り畳む(_o)" #: e2_config_dialog.c:784 msgid "Collapse all rows" msgstr "全ての列を畳む" #: e2_config_dialog.c:922 msgid "choose plugin" msgstr "プラグインを選択" #: e2_config_dialog.c:1001 #, c-format msgid "Choose font: %s" msgstr "フォントを選択: %s" #: e2_config_dialog.c:1041 msgid "abcd efgh ABCD EFGH" msgstr "abcd efgh ABCD EFGH" #: e2_config_dialog.c:1041 msgid "example:" msgstr "例:" #: e2_config_dialog.c:1136 msgid "Color data are not stored there" msgstr "そこには色データがありません" #: e2_config_dialog.c:1154 msgid "The current color descriptor is not valid" msgstr "現在の色記述は不正です" #: e2_config_dialog.c:1161 msgid "Set filetype color" msgstr "ファイルタイプの色を指定" #: e2_config_dialog.c:1219 #, c-format msgid "Choose color: %s" msgstr "色選択: %s" #: e2_config_dialog.c:1264 msgid "abCD" msgstr "abCD" #: e2_config_dialog.c:1264 msgid "currently:" msgstr "現在:" #: e2_config_dialog.c:1517 e2_filetype_dialog.c:872 msgid "Categories" msgstr "カテゴリ" #: e2_config_dialog.c:1631 e2_config_dialog.c:1658 e2p_find.c:2903 msgid "change" msgstr "変更" #: e2_config_dialog.c:1632 msgid "Click to open a font select dialog" msgstr "クリックでフォント選択" #: e2_config_dialog.c:1659 msgid "Click to open a color selection dialog" msgstr "クリックで色選択ダイアログを開く" #: e2_config_dialog.c:1771 msgid "_Default" msgstr "デフォルト(_D)" #: e2_config_dialog.c:1772 msgid "Revert all options to their default settings" msgstr "全てのオプションをデフォルトに戻す" #: e2_config_dialog.c:1776 msgid "_Basic" msgstr "基本(_B)" #: e2_config_dialog.c:1777 msgid "Display only the basic configuration options" msgstr "基本設定のみを表示" #: e2_config_dialog.c:1781 msgid "Ad_vanced" msgstr "詳細(_v)" #: e2_config_dialog.c:1782 msgid "Display all configuration options" msgstr "全てのオプションを表示" #: e2_context_menu.c:209 e2_filetype.c:119 e2_filetype.c:299 e2_filetype.c:493 #: e2_output.c:718 e2_task.c:3192 e2_task_backend.c:1929 msgid "" msgstr "<ディレクトリ>" #: e2_context_menu.c:273 e2_filetype.c:120 e2_filetype.c:301 e2_filetype.c:501 #: e2_output.c:757 e2_task.c:3198 msgid "" msgstr "<実行ファイル>" #: e2_context_menu.c:552 e2p_thumbs.c:1102 msgid "Open _with.." msgstr "アプリで開く(_w)" #: e2_context_menu.c:556 msgid "_Info" msgstr "情報(_I)" #: e2_context_menu.c:558 msgid "_Actions" msgstr "アクション(_A)" #: e2_context_menu.c:559 e2_dnd.c:726 e2_file_info_dialog.c:246 #: e2_filetype_dialog.c:691 e2_option_tree_context_menu.c:231 #: e2_toolbar.c:2816 e2_tree_dialog.c:1109 e2_view_dialog.c:946 #: e2p_cpbar.c:875 msgid "_Copy" msgstr "コピー(_C)" #: e2_context_menu.c:560 e2_dnd.c:732 e2_toolbar.c:2818 e2p_mvbar.c:951 msgid "_Move" msgstr "移動(_M)" #: e2_context_menu.c:561 e2_dnd.c:738 e2_toolbar.c:2820 msgid "_Link" msgstr "リンク(_L)" #: e2_context_menu.c:562 e2_toolbar.c:2824 msgid "_Trash" msgstr "ゴミ箱(_T)" #: e2_context_menu.c:565 e2p_rename.c:1580 msgid "_Rename.." msgstr "リネーム(_R)" #: e2_context_menu.c:566 msgid "Change _owners.." msgstr "所有者変更(_o)" #: e2_context_menu.c:568 msgid "Change _permissions.." msgstr "権限変更(_p)" #: e2_context_menu.c:570 msgid "Copy as.." msgstr "別名でコピー" #: e2_context_menu.c:571 msgid "Move as.." msgstr "別名で移動" #: e2_context_menu.c:572 msgid "Link as.." msgstr "別名でリンク" #: e2_context_menu.c:573 msgid "_Plugins" msgstr "プラグイン(_P)" #: e2_context_menu.c:576 msgid "_Edit plugins.." msgstr "プラグインを編集(_E)" #: e2_context_menu.c:577 msgid "_User commands" msgstr "ユーザコマンド(_U)" #: e2_context_menu.c:578 msgid "Enter file name:" msgstr "ファイル名を入力:" #: e2_context_menu.c:578 msgid "_Make new file.." msgstr "新しいファイルを作成(_M)" #: e2_context_menu.c:579 msgid "The files are different" msgstr "異なったファイルです" #: e2_context_menu.c:579 msgid "The files are identical" msgstr "同じファイルです" #: e2_context_menu.c:579 msgid "_Compare files" msgstr "ファイルを比較(_C)" #: e2_context_menu.c:580 msgid "Compare _directories" msgstr "ディレクトリを比較(_d)" #: e2_context_menu.c:582 msgid "_Remove spaces" msgstr "空白を除去(_R)" #: e2_context_menu.c:583 msgid "Enter the piece-size (in kB):" msgstr "ピースのサイズを kB で入力:" #: e2_context_menu.c:583 msgid "_Split file.." msgstr "ファイルを分割(_S)" #: e2_context_menu.c:584 msgid "Co_ncatenate files.." msgstr "ファイルを連結(_n)" #: e2_context_menu.c:584 msgid "Enter the name of the combined file:" msgstr "連結したファイルの名前を入力:" #: e2_context_menu.c:585 msgid "_Free space" msgstr "空き容量(_F)" #: e2_context_menu.c:585 msgid "percent free" msgstr "パーセントの空き" #: e2_context_menu.c:587 msgid "_Edit user commands.." msgstr "ユーザのコマンドを編集(_E)" #: e2_context_menu.c:588 e2_toolbar.c:2826 msgid "Ma_ke dir.." msgstr "dirを作成(_k)" #: e2_context_menu.c:590 msgid "_Bookmarks" msgstr "ブックマーク(_B)" #: e2_context_menu.c:591 e2_toolbar.c:2979 e2_toolbar.c:3044 msgid "Add _top" msgstr "一番上に追加(_t)" #: e2_context_menu.c:593 e2_toolbar.c:2983 e2_toolbar.c:3048 msgid "Add _bottom" msgstr "一番下に追加(_b)" #: e2_context_menu.c:595 msgid "_Edit bookmarks.." msgstr "ブックマークを編集(_E)" #: e2_context_menu.c:597 msgid "_Edit filetype.." msgstr "ファイルの種類(_E)" #: e2_context_menu.c:618 msgid "Shift" msgstr "Shift" #: e2_context_menu.c:620 msgid "Ctrl" msgstr "Ctrl" #: e2_context_menu.c:622 e2_keybinding.c:1148 e2_toolbar.c:2857 #: e2_toolbar.c:2924 e2_toolbar.c:3091 msgid "Action" msgstr "アクション" #: e2_context_menu.c:625 e2_keybinding.c:1151 e2_toolbar.c:2860 #: e2_toolbar.c:2927 e2_toolbar.c:3094 msgid "Argument" msgstr "引数" #: e2_date_filter_dialog.c:216 msgid "Display only the items:" msgstr "このアイテムだけを表示:" #: e2_date_filter_dialog.c:217 msgid "date filter" msgstr "日付で絞る" #: e2_date_filter_dialog.c:225 e2p_glob.c:515 msgid "accessed since" msgstr "アクセス日 >" #: e2_date_filter_dialog.c:225 e2p_glob.c:515 msgid "modified before" msgstr "modify日 <" #: e2_date_filter_dialog.c:225 e2p_glob.c:515 msgid "modified since" msgstr "modify日 >" #: e2_date_filter_dialog.c:226 e2p_glob.c:516 msgid "accessed before" msgstr "アクセス日 <" #: e2_date_filter_dialog.c:226 e2p_glob.c:516 msgid "changed before" msgstr "変更日 <" #: e2_date_filter_dialog.c:226 e2p_glob.c:516 msgid "changed since" msgstr "変更日 >" #: e2_dialog.c:684 e2_tree_dialog.c:1458 msgid "_Hidden" msgstr "隠す(_H)" #: e2_dialog.c:686 msgid "Toggle display of hidden items" msgstr "隠しアイテムの表示をトグル" #: e2_dialog.c:831 msgid "user input" msgstr "ユーザ入力" #: e2_dialog.c:1001 msgid "older" msgstr "古い" #: e2_dialog.c:1003 msgid "newer" msgstr "新しい" #: e2_dialog.c:1012 msgid "existing" msgstr "存在している" #: e2_dialog.c:1027 #, c-format msgid "" "Remove all contents of %s\n" "%s ?" msgstr "" "%s %s の\n" "全ての内容を削除しますか?" #: e2_dialog.c:1032 #, c-format msgid "" "Overwrite %s %s\n" "in %s ?" msgstr "" "%s %s in %s を\n" "上書きしますか?" #: e2_dialog.c:1039 e2_dialog.c:1079 e2_dialog.c:1108 e2_edit_dialog.c:696 #: e2_edit_dialog.c:928 e2_task.c:2117 e2p_crypt.c:430 e2p_rename.c:899 msgid "confirm" msgstr "確認" #: e2_dialog.c:1106 #, c-format msgid "%s is taking a long time. Continue waiting ?" msgstr "%s には長い時間がかかります。待ちますか?" #: e2_dialog.c:1113 msgid "_Quiet" msgstr "尋ねない(_Q)" #: e2_dialog.c:1114 msgid "Don't ask any more" msgstr "二度と尋ねない" #: e2_dialog.c:1118 #, c-format msgid "Cancel the %s" msgstr "%s をキャンセルできません" #: e2_dialog.c:1122 msgid "Wait some more" msgstr "もう少し待つ" #: e2_dialog.c:1224 msgid "center" msgstr "中央" #: e2_dialog.c:1224 msgid "mouse" msgstr "マウス" #: e2_dialog.c:1224 e2_output.c:2442 e2_toolbar.c:2729 e2p_acl.c:3513 msgid "none" msgstr "なし" #: e2_dialog.c:1225 msgid "always center" msgstr "常に中央" #: e2_dialog.c:1225 msgid "center on parent" msgstr "ペアレントの中央" #: e2_dialog.c:1227 msgid "dialog position" msgstr "ダイアログの位置" #: e2_dialog.c:1228 msgid "This determines the position where dialog windows will pop up" msgstr "" #: e2_dnd.c:749 msgid "C_ancel" msgstr "キャンセル(_a)" #: e2_edit_dialog.c:119 e2_edit_dialog.c:344 msgid "file save as" msgstr "以下の名前で保存" #: e2_edit_dialog.c:233 #, c-format msgid "Cannot save %s in its original encoding %s. Reverting to UTF-8" msgstr "%s を エンコーディング %s で保存できません。UTF-8 に戻します" #: e2_edit_dialog.c:240 #, c-format msgid "Encoding conversion failed for %s" msgstr "%s のエンコーディング変換に失敗" #: e2_edit_dialog.c:312 #, c-format msgid "Original encoding of '%s' is unknown, now saved as UTF-8" msgstr "'%s' のオリジナルのエンコーディングが不明です。UTF-8 で保存します" #: e2_edit_dialog.c:412 msgid "Reverting to saved version cannot be undone" msgstr "保存した版に戻すと元に戻せません" #: e2_edit_dialog.c:477 #, c-format msgid "Cannot initialize spell checking: %s" msgstr "スペルチェックを初期化できません: %s" #: e2_edit_dialog.c:524 msgid "choose language" msgstr "言語を選択" #: e2_edit_dialog.c:525 msgid "Enter the spell-checker language (like en_CA):" msgstr "スペルチェックする言語を入力(例 en_CA):" #: e2_edit_dialog.c:535 #, c-format msgid "Cannot set speller language to %s (%s)" msgstr "スペル言語を %s に指定できません (%s)" #: e2_edit_dialog.c:696 msgid "Replace this one ?" msgstr "これを置き換えますか?" #: e2_edit_dialog.c:810 msgid "Spelling suggestions" msgstr "スペルの提案" #: e2_edit_dialog.c:817 e2_edit_dialog.c:1538 e2_vfs_dialog.c:1025 #: e2p_config.c:1100 msgid "_Save" msgstr "保存(_S)" #: e2_edit_dialog.c:818 msgid "Save the file" msgstr "ファイルを保存" #: e2_edit_dialog.c:820 msgid "Save as.." msgstr "別名で保存" #: e2_edit_dialog.c:821 msgid "Save the file with a new name" msgstr "ファイルを新しい名前で保存" #: e2_edit_dialog.c:823 msgid "Save se_lection.." msgstr "選んだものを保存(_l)" #: e2_edit_dialog.c:824 msgid "Save the selected text" msgstr "選んだテキストを保存" #: e2_edit_dialog.c:829 e2_view_dialog.c:952 msgid "Print file" msgstr "ファイルを印刷" #: e2_edit_dialog.c:829 e2_view_dialog.c:952 msgid "Print selected text" msgstr "選んだテキストを印刷" #: e2_edit_dialog.c:830 e2_view_dialog.c:953 msgid "_Print.." msgstr "印刷(_P)" #: e2_edit_dialog.c:834 msgid "R_efresh" msgstr "更新(_e)" #: e2_edit_dialog.c:835 msgid "Reload the file being edited" msgstr "編集するファイルをリロード" #: e2_edit_dialog.c:838 e2_toolbar.c:2889 e2p_find.c:3667 msgid "_Find.." msgstr "検索(_F)" #: e2_edit_dialog.c:839 msgid "Find matching text" msgstr "合致するテキストを検索" #: e2_edit_dialog.c:840 msgid "_Replace.." msgstr "置換(_R)" #: e2_edit_dialog.c:841 msgid "Find and replace matching text" msgstr "合致するテキストを検索して置換" #: e2_edit_dialog.c:843 e2_edit_dialog.c:1549 e2_output.c:1346 #: e2_view_dialog.c:1579 msgid "_Hide" msgstr "隠す(_H)" #: e2_edit_dialog.c:844 e2_view_dialog.c:1587 msgid "Hide the search options bar" msgstr "検索オプションバーを隠す" #: e2_edit_dialog.c:847 e2_edit_dialog.c:1550 msgid "_Undo" msgstr "元に戻す(_U)" #: e2_edit_dialog.c:848 msgid "Undo last change" msgstr "前回の変更を元に戻す" #: e2_edit_dialog.c:852 e2_edit_dialog.c:1552 msgid "Re_do" msgstr "やり直す(_d)" #: e2_edit_dialog.c:853 msgid "Reverse last undo" msgstr "元に戻したものをやり直す" #: e2_edit_dialog.c:858 msgid "_Check spelling" msgstr "スペルをチェック(_C)" #: e2_edit_dialog.c:859 msgid "Flag mis-spelt words" msgstr "綴り違いの単語にフラグ" #: e2_edit_dialog.c:861 msgid "_Clear spellcheck" msgstr "スペルチェックをクリア(_C)" #: e2_edit_dialog.c:862 msgid "Remove all spell-check flags" msgstr "全てのスペルチェックフラグを削除" #: e2_edit_dialog.c:867 msgid "_Language.." msgstr "言語(_L)" #: e2_edit_dialog.c:868 msgid "Set spell-checker language" msgstr "スペルチェックする言語を指定" #: e2_edit_dialog.c:872 msgid "_Wrap" msgstr "折り返す(_W)" #: e2_edit_dialog.c:880 e2_view_dialog.c:1593 msgid "If activated, text in the window will be word-wrapped" msgstr "有効にすると、ウィンドウのテキストを折り返します" #: e2_edit_dialog.c:883 msgid "Se_ttings" msgstr "設定(_t)" #: e2_edit_dialog.c:884 e2_view_dialog.c:959 msgid "Open the configuration dialog at the options page" msgstr "オプションページに設定ダイアログを開く" #: e2_edit_dialog.c:928 msgid "Save modified file ?" msgstr "変更したファイルを保存しますか?" #: e2_edit_dialog.c:1416 msgid "editing file" msgstr "ファイルを編集" #: e2_edit_dialog.c:1491 msgid "Replacements" msgstr "置換" #: e2_edit_dialog.c:1495 msgid "If activated, the next match will be sought after each replacement" msgstr "" #: e2_edit_dialog.c:1495 msgid "r_epeat" msgstr "繰り返し(_e)" #: e2_edit_dialog.c:1498 msgid "If activated, all matches will be replaced at once" msgstr "有効にすると、合致するものを全て一回で置き換えます" #: e2_edit_dialog.c:1498 msgid "_all" msgstr "全て(_a)" #: e2_edit_dialog.c:1501 msgid "If activated, confirmation will be sought when \"replacing all\"" msgstr "" #: e2_edit_dialog.c:1501 msgid "co_nfirm" msgstr "確認(_n)" #: e2_edit_dialog.c:1509 e2_view_dialog.c:1571 msgid "not found" msgstr "見つかりません" #: e2_edit_dialog.c:1516 msgid "_Replace" msgstr "置換(_R)" #: e2_edit_dialog.c:1524 msgid "Replace this match" msgstr "これを置き換える" #: e2_edit_dialog.c:1527 e2_view_dialog.c:1596 e2p_find.c:3627 msgid "_Find" msgstr "検索(_F)" #: e2_edit_dialog.c:1535 e2_view_dialog.c:1605 msgid "Find the next match" msgstr "次を検索" #: e2_edit_dialog.c:1705 msgid "backup when saving" msgstr "保存時にバックアップする" #: e2_edit_dialog.c:1706 msgid "" "When saving an edited file, an existing file with the same name will be " "renamed" msgstr "" #: e2_file_info_dialog.c:207 e2_file_info_dialog.c:210 #: e2_file_info_dialog.c:446 e2_file_info_dialog.c:478 #: e2_file_info_dialog.c:499 msgid "Type:" msgstr "種類:" #: e2_file_info_dialog.c:215 msgid "Item:" msgstr "アイテム:" #: e2_file_info_dialog.c:217 e2_file_info_dialog.c:645 msgid "Size:" msgstr "サイズ:" #: e2_file_info_dialog.c:218 e2_file_info_dialog.c:672 msgid "User:" msgstr "ユーザ:" #: e2_file_info_dialog.c:219 e2_file_info_dialog.c:680 msgid "Group:" msgstr "グループ:" #: e2_file_info_dialog.c:220 e2_file_info_dialog.c:692 msgid "Permissions:" msgstr "権限:" #: e2_file_info_dialog.c:221 e2_file_info_dialog.c:703 msgid "Accessed:" msgstr "アクセス:" #: e2_file_info_dialog.c:222 e2_file_info_dialog.c:717 msgid "Modified:" msgstr "修正:" #: e2_file_info_dialog.c:223 e2_file_info_dialog.c:731 msgid "Changed:" msgstr "変更:" #: e2_file_info_dialog.c:247 msgid "Copy displayed data" msgstr "表示されているデータをコピー" #: e2_file_info_dialog.c:344 msgid "file info" msgstr "ファイル情報" #: e2_file_info_dialog.c:365 msgid "Virtual file" msgstr "仮想ファイル" #: e2_file_info_dialog.c:381 e2_file_info_dialog.c:497 e2_utf8.c:339 #: e2_utf8.c:369 e2_utf8.c:397 e2_utf8.c:427 msgid "Unknown" msgstr "不明" #: e2_file_info_dialog.c:464 e2p_acl.c:3609 e2p_crypt.c:2798 msgid "Directory" msgstr "ディレクトリ" #: e2_file_info_dialog.c:487 msgid "Symbolic Link" msgstr "シンボリックリンク" #: e2_file_info_dialog.c:489 msgid "Character Device" msgstr "キャラクタデバイス" #: e2_file_info_dialog.c:491 msgid "Block Device" msgstr "ブロックデバイス" #: e2_file_info_dialog.c:493 msgid "FIFO Pipe" msgstr "FIFOパイプ" #: e2_file_info_dialog.c:495 msgid "Socket" msgstr "ソケット" #: e2_file_info_dialog.c:523 #, c-format msgid "to %s" msgstr "to %s" #: e2_file_info_dialog.c:571 msgid "(which is missing)" msgstr "" #: e2_file_info_dialog.c:573 msgid "(which is itself)" msgstr "" #: e2_file_info_dialog.c:588 #, c-format msgid "and ultimately to %s" msgstr "" #: e2_file_info_dialog.c:602 msgid "(Cannot resolve the link)" msgstr "(リンクを解決できません)" #: e2_file_info_dialog.c:623 #, fuzzy msgid "Encoding:" msgstr "ファイルのエンコーディング:" #: e2_file_info_dialog.c:623 e2_file_info_dialog.c:627 #: e2_file_info_dialog.c:633 #, fuzzy msgid "Mime:" msgstr "Mime:" #: e2_file_info_dialog.c:666 e2_size_filter_dialog.c:192 e2p_du.c:170 #: e2p_find.c:2781 e2p_glob.c:501 msgid "bytes" msgstr "bytes" #: e2_file_info_dialog.c:711 msgid "Item was last opened, run, read etc" msgstr "" #: e2_file_info_dialog.c:725 msgid "Content of the item was last altered" msgstr "" #: e2_file_info_dialog.c:739 msgid "Property of the item (name, permission, owner etc) was last altered" msgstr "" #: e2_filelist.c:261 msgid "Something is wrong with the auto-refresh mechanism" msgstr "自動更新中に不具合が発生しました" #: e2_filelist.c:773 e2_toolbar.c:1621 msgid "k" msgstr "k" #: e2_filelist.c:778 e2_toolbar.c:1626 msgid "M" msgstr "M" #: e2_filetype.c:121 e2_filetype.c:307 e2_filetype.c:502 msgid "" msgstr "<なし>" #: e2_filetype.c:491 e2p_du.c:220 msgid "directories" msgstr "ディレクトリ" #: e2_filetype.c:495 e2_filetype.c:602 e2_filetype.c:616 e2_filetype.c:631 #: e2_filetype.c:645 e2_filetype.c:680 e2_filetype_dialog.c:1201 #: e2_output.c:1397 e2_vfs_dialog.c:1029 e2_vfs_dialog.c:1571 msgid "_Open" msgstr "開く(_O)" #: e2_filetype.c:496 e2_filetype.c:603 e2_filetype.c:617 e2_filetype.c:632 #: e2_filetype.c:646 e2_filetype.c:681 msgid "O_pen in other" msgstr "他のペインに開く(_p)" #: e2_filetype.c:497 msgid "_Mount" msgstr "マウント(_M)" #: e2_filetype.c:498 msgid "_Unmount" msgstr "アンマウント(_U)" #: e2_filetype.c:499 msgid "executables" msgstr "実行ファイル" #: e2_filetype.c:505 e2_filetype_dialog.c:1198 e2p_upgrade.c:742 #: e2p_upgrade.c:743 msgid "_Run" msgstr "実行(_R)" #: e2_filetype.c:508 e2_filetype.c:691 msgid "Edit _with.." msgstr "アプリで編集(_w)" #: e2_filetype.c:508 e2_filetype.c:691 e2p_upgrade.c:746 e2p_upgrade.c:747 msgid "Editor command:" msgstr "エディタのコマンド:" #: e2_filetype.c:509 e2_filetype.c:543 e2_filetype.c:694 #: e2_filetype_dialog.c:1188 e2_output.c:1394 msgid "_Edit" msgstr "編集(_E)" #: e2_filetype.c:510 e2p_find.c:318 msgid "office documents" msgstr "オフィス文書" #: e2_filetype.c:516 e2_filetype.c:553 msgid "_Openoffice" msgstr "_Openoffice" #: e2_filetype.c:517 msgid "HTML documents" msgstr "HTML文書" #: e2_filetype.c:522 msgid "_Firefox" msgstr "_Firefox" #: e2_filetype.c:523 msgid "_Mozilla" msgstr "_Mozilla" #: e2_filetype.c:524 msgid "_Lynx" msgstr "_Lynx" #: e2_filetype.c:525 msgid "_Opera" msgstr "_Opera" #: e2_filetype.c:526 msgid "PDF documents" msgstr "PDF文書" #: e2_filetype.c:532 msgid "postscript documents" msgstr "postscript文書" #: e2_filetype.c:537 msgid "text documents" msgstr "テキスト文書" #: e2_filetype.c:542 msgid "View" msgstr "見る" #: e2_filetype.c:547 msgid "spreadsheets" msgstr "スプレッドシート" #: e2_filetype.c:554 msgid "audio files" msgstr "オーディオファイル" #: e2_filetype.c:565 msgid "image files" msgstr "画像ファイル" #: e2_filetype.c:585 msgid "video files" msgstr "動画ファイル" #: e2_filetype.c:595 msgid "plain tarballs" msgstr "プレーン tarballs" #: e2_filetype.c:599 e2_filetype.c:605 e2_filetype.c:619 e2_filetype.c:634 #: e2_filetype.c:648 msgid "Unpack" msgstr "解凍" #: e2_filetype.c:600 msgid "Unpack in other pane" msgstr "他のペインに解凍" #: e2_filetype.c:607 e2_filetype.c:621 e2_filetype.c:636 e2_filetype.c:650 msgid "_List contents" msgstr "内容を表示(_L)" #: e2_filetype.c:608 msgid "gzip tarballs" msgstr "gzip tarballs" #: e2_filetype.c:613 e2_filetype.c:628 e2p_unpack.c:526 msgid "_Unpack" msgstr "解凍(_U)" #: e2_filetype.c:614 e2_filetype.c:629 msgid "Unpack in _other pane" msgstr "他のペインに解凍(_o)" #: e2_filetype.c:622 msgid "bzip2 tarballs" msgstr "bzip2 tarballs" #: e2_filetype.c:637 msgid "zip archives" msgstr "zip アーカイブ" #: e2_filetype.c:641 msgid "_Unzip" msgstr "_Unzip" #: e2_filetype.c:643 msgid "Unzip in _other pane" msgstr "他のペインに Unzip(_o)" #: e2_filetype.c:673 msgid "RPM packages" msgstr "RPM パッケージ" #: e2_filetype.c:677 msgid "In_formation" msgstr "情報(_f)" #: e2_filetype.c:678 msgid "_Install" msgstr "インストール(_I)" #: e2_filetype.c:683 msgid "source code files" msgstr "ソースコードファイル" #: e2_filetype.c:695 msgid "object files" msgstr "オブジェクトファイル" #: e2_filetype.c:701 msgid "_View symbols" msgstr "シンボルを表示(_V)" #: e2_filetype.c:727 e2_keybinding.c:1142 msgid "Category" msgstr "カテゴリ" #: e2_filetype.c:732 e2_menu.c:999 msgid "Command" msgstr "コマンド" #: e2_filetype.c:739 msgid "case-insensitive filetypes" msgstr "ファイルの種類は大文字小文字を区別しない" #: e2_filetype.c:740 msgid "" "This causes text-case to always be ignored when matching a file-extension" msgstr "" #: e2_filetype_dialog.c:443 msgid "new" msgstr "新規" #: e2_filetype_dialog.c:689 e2_option_tree_context_menu.c:227 msgid "Cu_t" msgstr "切り取り(_t)" #: e2_filetype_dialog.c:690 msgid "Cut selected row(s)" msgstr "選んだ列をカット" #: e2_filetype_dialog.c:692 msgid "Copy selected row(s)" msgstr "選んだ列をコピー" #: e2_filetype_dialog.c:697 e2_option_tree_context_menu.c:235 msgid "_Paste" msgstr "貼り付け(_P)" #: e2_filetype_dialog.c:698 e2_option_tree_context_menu.c:236 msgid "Paste previously copied or cut row(s) after current row" msgstr "コピーもしくはカットした列を現在の列の後ろに貼り付ける" #: e2_filetype_dialog.c:702 e2_option_tree_context_menu.c:256 #: e2_permissions_dialog.c:454 e2p_acl.c:3725 msgid "_Add" msgstr "追加(_A)" #: e2_filetype_dialog.c:703 e2_option_tree_context_menu.c:257 msgid "Add a row after the current one" msgstr "行を現在のものの後に追加" #: e2_filetype_dialog.c:705 e2_filetype_dialog.c:1002 e2_option_tree.c:969 #: e2_option_tree_context_menu.c:264 e2_toolbar.c:386 e2_toolbar.c:2998 #: e2_toolbar.c:3059 e2_toolbar.c:3065 msgid "_Up" msgstr "上へ(_U)" #: e2_filetype_dialog.c:706 e2_option_tree_context_menu.c:265 msgid "Move selected row up" msgstr "選んだ列を上に移動" #: e2_filetype_dialog.c:707 e2_filetype_dialog.c:1008 e2_option_tree.c:972 #: e2_option_tree_context_menu.c:267 e2_toolbar.c:388 msgid "_Down" msgstr "下へ(_D)" #: e2_filetype_dialog.c:708 e2_option_tree_context_menu.c:268 msgid "Move selected row down" msgstr "選んだ列を下に移動" #: e2_filetype_dialog.c:834 msgid "edit filetypes" msgstr "ファイルの種類を編集" #: e2_filetype_dialog.c:903 msgid "Extensions" msgstr "拡張子" #: e2_filetype_dialog.c:928 msgid "Labels" msgstr "ラベル" #: e2_filetype_dialog.c:937 msgid "Commands" msgstr "コマンド" #: e2_filetype_dialog.c:1006 e2_option_tree.c:970 msgid "Move the selected row one place up" msgstr "選んだ行を上に移動" #: e2_filetype_dialog.c:1012 e2_option_tree.c:973 msgid "Move the selected row one place down" msgstr "選んだ行を下に移動" #: e2_filetype_dialog.c:1014 e2_option_tree.c:980 e2_vfs_dialog.c:1557 #: e2p_acl.c:3795 msgid "Add a_fter" msgstr "項目(_f)" #: e2_filetype_dialog.c:1018 msgid "Add a row after the currently selected one" msgstr "現在選択しているものの後に列を追加" #: e2_filetype_dialog.c:1157 msgid "ambiguous filetype" msgstr "あいまいな種類のファイル" #: e2_filetype_dialog.c:1157 msgid "unrecognised filetype" msgstr "不明なファイルタイプ" #: e2_filetype_dialog.c:1162 #, c-format msgid "" "What would you like to do with\n" "%s\n" "in %s ?" msgstr "" "何をしますか?\n" "%s\n" "in %s" #: e2_filetype_dialog.c:1173 msgid "_Add.." msgstr "追加(_A)" #: e2_filetype_dialog.c:1174 msgid "" "Create a new filetype for this extension, or add it to an existing filetype" msgstr "" "この拡張子のファイルタイプを作成するか、既存のファイルタイプに追加してくださ" "い" #: e2_filetype_dialog.c:1189 msgid "Edit the file" msgstr "ファイルを編集" #: e2_filetype_dialog.c:1199 msgid "Execute the item" msgstr "アイテムを実行" #: e2_filetype_dialog.c:1202 msgid "Open with the default application" msgstr "デフォルトのアプリケーションで開く" #: e2_filetype_dialog.c:1206 msgid "_Open.." msgstr "開く(_O)" #: e2_filetype_dialog.c:1207 msgid "Enter a command with which to open the file" msgstr "このファイルを開くコマンドを入力" #: e2_fileview.c:155 e2_ownership_dialog.c:217 e2_permissions_dialog.c:327 #: e2_plugins.c:1284 e2p_acl.c:3467 e2p_crypt.c:2805 e2p_times.c:697 msgid "Filename" msgstr "ファイル名" #: e2_fileview.c:156 msgid "Size" msgstr "サイズ" #: e2_fileview.c:157 e2_permissions_dialog.c:386 e2p_acl.c:3573 msgid "Permissions" msgstr "権限" #: e2_fileview.c:158 msgid "Owner" msgstr "所有者" #: e2_fileview.c:159 e2_permissions_dialog.c:355 e2_permissions_dialog.c:410 #: e2p_acl.c:229 e2p_acl.c:2093 e2p_acl.c:3495 msgid "Group" msgstr "グループ" #: e2_fileview.c:160 msgid "Modified" msgstr "修正" #: e2_fileview.c:161 e2p_times.c:716 msgid "Accessed" msgstr "アクセス" #: e2_fileview.c:162 msgid "Changed" msgstr "変更" #: e2_fs.c:711 #, c-format msgid "'%s' is not a directory" msgstr "'%s' はディレクトリではありません" #: e2_fs.c:720 #, c-format msgid "Cannot access directory '%s' - No permission" msgstr "ディレクトリ '%s' にアクセスできません- 権限なし" #: e2_fs.c:725 #, c-format msgid "Directory '%s' does not exist" msgstr "ディレクトリ '%s' はありません" #: e2_fs.c:1293 e2_fs.c:1378 msgid "Reading directory data" msgstr "ディレクトリのデータを読む" #: e2_fs.c:1294 msgid "directory read" msgstr "ディレクトリを読む" #: e2_fs.c:2082 #, c-format msgid "Cannot open '%s' for writing - %s" msgstr "書き込み用に '%s' を開けません - %s" #: e2_fs.c:2127 #, c-format msgid "Error writing file '%s' - %s" msgstr "ファイル '%s' の書き込みエラー - %s" #: e2_fs.c:2243 e2_fs.c:2289 e2_fs.c:2514 e2_view_dialog.c:491 #: e2p_config.c:652 e2p_crypt.c:1720 e2p_dircmp.c:440 #, c-format msgid "Error reading file %s" msgstr "ファイル %s の読み込みエラー" #: e2_fs.c:2272 e2_fs.c:2465 e2_fs_walk.c:311 e2_fs_walk.c:471 #: e2_fs_walk.c:700 e2_output.c:825 e2_task_backend.c:1261 e2p_acl.c:1808 #: e2p_acl.c:1882 #, c-format msgid "Cannot get information about %s" msgstr "%s の情報を得られません" #: e2_fs.c:2279 #, c-format msgid "Cannot open file %s" msgstr "ファイル %s を開けません" #: e2_fs.c:2350 e2_fs.c:2454 #, c-format msgid "Cannot create file %s" msgstr "ファイル %s を作れません" #: e2_fs.c:2362 e2_fs.c:2529 e2p_crypt.c:1752 #, c-format msgid "Error writing file %s" msgstr "ファイル %s の書き込みエラー" #: e2_fs.c:2440 e2p_crypt.c:867 e2p_crypt.c:1088 e2p_dircmp.c:408 #, c-format msgid "Cannot open '%s' for reading" msgstr "'%s' を読み込み用に開けません" #: e2_fs.c:2589 #, c-format msgid "Cannot open pipe for command '%s'" msgstr "コマンド '%s' のパイプを開けません" #: e2_fs.c:2612 #, c-format msgid "Command %s failed: not enough memory" msgstr "コマンド %s に失敗: メモリが足りません" #: e2_fs.c:2805 msgid "-rwxrwxrwx" msgstr "" #: e2_fs.c:2810 msgid "ldbcfs" msgstr "" #: e2_fs.c:2815 msgid "TtSs" msgstr "" #: e2_fs_walk.c:176 #, c-format msgid "Cannot change anything in %s" msgstr "%s の全てを変更できません" #: e2_fs_walk.c:176 e2_fs_walk.c:652 e2_task_backend.c:160 e2p_find.c:1766 #: e2p_find.c:2337 e2p_times.c:359 #, c-format msgid "Cannot change permissions of %s" msgstr "%s の権限を変更できません" #: e2_fs_walk.c:333 e2_fs_walk.c:341 #, c-format msgid "Directory %s not opened" msgstr "ディレクトリ %s を開けません" #: e2_fs_walk.c:386 #, c-format msgid "Cannot open directory %s" msgstr "ディレクトリ %s を開けません" #: e2_keybinding.c:840 #, c-format msgid "Cannot find a key binding named %s" msgstr "%s というキーバインディングはありません" #: e2_keybinding.c:850 e2_option.c:1089 msgid "key bindings" msgstr "キーバインディング" #: e2_keybinding.c:1062 msgid "Now press one of h,m,d\\n" msgstr "h,m,d のどれかを押してください\\n" #: e2_keybinding.c:1144 msgid "Key" msgstr "キー" #: e2_keybinding.c:1146 msgid "Continue" msgstr "継続" #: e2_keybinding.c:1160 msgid "chained keybindings timeout (ms)" msgstr "組み合わせキーのタイムアウト (ms)" #: e2_keybinding.c:1161 msgid "" "This sets the time limit (in milliseconds) for accepting 'chained' " "keybindings" msgstr "組み合わせキーだと認識する制限時間 (ミリ秒)" #: e2_main.c:231 #, c-format msgid "Your current locale is '%s'.\n" msgstr "現在のロケールは '%s' です\n" #: e2_main.c:233 #, c-format msgid "" "You have set the environment variable G_BROKEN_FILENAMES, which\n" "causes GTK+ to convert filename encoding, from the one specified\n" "by the system locale, to UTF-8.\n" "However, you have not set a system locale. Please do so, by setting\n" "the environment variable LANG or LC_CTYPE!\n" msgstr "" #: e2_main.c:241 #, c-format msgid "" "(Note: There is a command line option -i/--ignore-problems, but use it\n" "at your own risk!)\n" msgstr "" "(注意: -i/--ignore-problems というコマンドラインオプションもありますが、\n" "自己責任でお使いください)\n" #: e2_main.c:246 #, c-format msgid "" "%s will ignore locale problems when reading filenames because\n" "--ignore-problems/-i has been set. This might result in segfaults and all\n" "kind of problems. You really should set a system locale with the\n" "LANG or LC_CTYPE environment variable.\n" msgstr "" #: e2_main.c:342 msgid "emelFM2" msgstr "emelFM2" #: e2_main.c:558 #, c-format msgid "%u process(es) are running" msgstr "%u 個のプロセスを実行中" #: e2_menu.c:745 msgid "no children" msgstr "子プロセスなし" #: e2_menu.c:766 msgid "_Name filter" msgstr "名前で絞る(_N)" #: e2_menu.c:768 msgid "_Size filter" msgstr "サイズで絞る(_S)" #: e2_menu.c:770 msgid "_Date filter" msgstr "日付で絞る(_D)" #: e2_menu.c:773 msgid "_Directories too" msgstr "ディレクトリも(_D)" #: e2_menu.c:780 msgid "_Remove all filters" msgstr "全ての条件を削除(_R)" #: e2_menu.c:990 e2_plugins.c:1276 msgid "Menu" msgstr "メニュー" #: e2_mkdir_dialog.c:58 msgid "new directory" msgstr "新規ディレクトリ" #: e2_mkdir_dialog.c:332 msgid "yes" msgstr "はい" #: e2_mkdir_dialog.c:339 msgid "no" msgstr "いいえ" #: e2_mkdir_dialog.c:411 #, c-format msgid "cannot write to '%s' - %s" msgstr "'%s' に書き込めません - %s" #: e2_mkdir_dialog.c:419 #, c-format msgid "cannot write to parent directory - %s" msgstr "親ディレクトリに書き込めません - %s" #: e2_mkdir_dialog.c:427 #, c-format msgid "only '%s' exists - %s" msgstr "'%s' だけがあります - %s" #: e2_mkdir_dialog.c:456 msgid "the directory already exists." msgstr "そのディレクトリは既にあります" #: e2_mkdir_dialog.c:458 msgid "something is in the way." msgstr "何かが邪魔をしています" #: e2_mkdir_dialog.c:468 msgid "directories will be created" msgstr "ディレクトリを作成" #: e2_mkdir_dialog.c:656 e2_mkdir_dialog.c:701 e2_task_backend.c:925 #, c-format msgid "Cannot create directory %s" msgstr "ディレクトリ %s を作成できません" #: e2_mkdir_dialog.c:902 msgid "What is the new directory's name?" msgstr "新しいディレクトリの名前" #: e2_mkdir_dialog.c:902 msgid "create directory" msgstr "ディレクトリ作成" #: e2_mkdir_dialog.c:927 msgid "parent directory:" msgstr "親ディレクトリ:" #: e2_mkdir_dialog.c:933 msgid "creation possible:" msgstr "作成可能:" #: e2_mkdir_dialog.c:1003 msgid "open info frame" msgstr "情報フレームを開く" #: e2_mkdir_dialog.c:1004 msgid "" "This causes make-directory dialogs to start with extra information displayed" msgstr "" #: e2_mkdir_dialog.c:1007 msgid "follow active-pane directory" msgstr "アクティブペインディレクトリに従う" #: e2_mkdir_dialog.c:1008 msgid "" "This makes the parent directory for new directories the same as the one in " "the active pane, even if the latter changes" msgstr "" #: e2_mkdir_dialog.c:1012 msgid "suggest directory name" msgstr "ディレクトリの名前を提案" #: e2_mkdir_dialog.c:1013 msgid "" "This presents a suggested name for each new directory, based on the last-" "created directory with an increasing number appended" msgstr "" #: e2_mkdir_dialog.c:1017 msgid "show last directory name" msgstr "最後のディレクトリ名を表示" #: e2_mkdir_dialog.c:1018 msgid "" "This causes the name of the last-created directory to be shown in the entry " "field, after opening the dialog or when creating another directory" msgstr "" #: e2_mkdir_dialog.c:1021 msgid "replicate changes" msgstr "変更を再現する" #: e2_mkdir_dialog.c:1022 msgid "" "This causes option-changes to be replicated in other mkdir dialogs. " "Otherwise such changes will be confined to the current dialog" msgstr "" #: e2_name_filter_dialog.c:57 e2p_glob.c:301 msgid "Invalid filename pattern" msgstr "不正なファイル名のパターン" #: e2_name_filter_dialog.c:161 msgid "Display only the items named like:" msgstr "この名前のものだけを表示:" #: e2_name_filter_dialog.c:162 msgid "name filter" msgstr "名前で絞る" #: e2_name_filter_dialog.c:175 e2p_glob.c:438 msgid "example: *.c,*.h" msgstr "例: *.c,*.h" #: e2_name_filter_dialog.c:178 msgid "Invert" msgstr "反転" #: e2_name_filter_dialog.c:186 msgid "Show files that DO NOT match the given mask" msgstr "条件に合致しないファイルを表示" #: e2_name_filter_dialog.c:187 msgid "Case sensitive" msgstr "大文字小文字を区別" #: e2_option.c:181 #, c-format msgid "Cannot create trash directory %s" msgstr "ゴミ箱用ディレクトリ %s を作成できません" #: e2_option.c:346 msgid "Configuration data re-loaded" msgstr "設定ファイルをリロードしました" #: e2_option.c:611 #, c-format msgid "" "# This is the %s configuration data file.\n" "# It will be overwritten each time the program is run!\n" "\n" "# If you're inclined to edit the file between program sessions, note this:\n" "# for tree options, you have to use \\| to escape | and you have to use \\< " "to escape <,\n" "# if that is the first non-space character on a line.\n" "\n" msgstr "" #: e2_option.c:704 #, c-format msgid "Cannot write config file %s - %s" msgstr "設定ファイル %s を書き込めません - %s" #: e2_option.c:1067 msgid "aliases" msgstr "エイリアス" #: e2_option.c:1068 msgid "bookmarks" msgstr "ブックマーク" #: e2_option.c:1069 msgid "colors" msgstr "配色" #: e2_option.c:1070 msgid "columns" msgstr "表示項目" #: e2_option.c:1071 msgid "command bar" msgstr "コマンドバー" #: e2_option.c:1072 msgid "command line" msgstr "コマンドライン" #: e2_option.c:1074 msgid "confirmation" msgstr "確認" #: e2_option.c:1075 msgid "context menu" msgstr "コンテクストメニュー" #: e2_option.c:1076 msgid "custom menus" msgstr "カスタムメニュー" #: e2_option.c:1077 msgid "delays" msgstr "遅延" #: e2_option.c:1078 msgid "dialogs" msgstr "ダイアログ" #: e2_option.c:1079 msgid "directory lines" msgstr "ディレクトリ欄" #: e2_option.c:1080 msgid "extensions" msgstr "拡張子" #: e2_option.c:1081 msgid "file actions" msgstr "ファイルアクション" #: e2_option.c:1082 msgid "filetypes" msgstr "ファイルの種類" #: e2_option.c:1083 msgid "fonts" msgstr "フォント" #: e2_option.c:1084 msgid "general" msgstr "一般" #: e2_option.c:1086 e2p_config.c:1266 e2p_config.c:1301 e2p_config.c:1323 msgid "icons" msgstr "アイコン" #: e2_option.c:1087 msgid "interface" msgstr "インタフェース" #: e2_option.c:1088 msgid "item types" msgstr "アイテムの種類" #: e2_option.c:1090 msgid "make directory" msgstr "ディレクトリ作成" #: e2_option.c:1091 msgid "menus" msgstr "メニュー" #: e2_option.c:1092 msgid "miscellaneous" msgstr "その他" #: e2_option.c:1093 msgid "options" msgstr "オプション" #: e2_option.c:1095 msgid "pane 1" msgstr "ペイン 1" #: e2_option.c:1096 msgid "pane1 bar" msgstr "ペイン1 のバー" #: e2_option.c:1097 msgid "pane 2" msgstr "ペイン 2" #: e2_option.c:1098 msgid "pane2 bar" msgstr "ペイン2 のバー" #: e2_option.c:1100 msgid "plugins" msgstr "プラグイン" #: e2_option.c:1101 msgid "position" msgstr "位置" #: e2_option.c:1103 msgid "startup" msgstr "起動" #: e2_option.c:1104 msgid "style" msgstr "スタイル" #: e2_option.c:1105 msgid "tab completion" msgstr "タブ補完" #: e2_option.c:1106 msgid "task bar" msgstr "タスクバー" #: e2_option__default.c:70 msgid "show all options in config dialogs" msgstr "設定ダイアログの全オプションを表示" #: e2_option__default.c:74 msgid "reload config on external change" msgstr "設定ファイルに変更があればリロード" #: e2_option__default.c:75 msgid "" "This enables automatic reloading of the configuration data for this program, " "if that data is changed by another program instance" msgstr "" #: e2_option__default.c:85 msgid "document containing usage advice" msgstr "使いかたを説明するドキュメント" #: e2_option__default.c:86 msgid "This document is opened from the help dialog usage page" msgstr "このドキュメントはヘルプダイアログの使いかたページから開かれます" #: e2_option__default.c:92 msgid "document containing configuration advice" msgstr "設定を説明するドキュメント" #: e2_option__default.c:93 msgid "This document is opened from the help dialog configuration page" msgstr "このドキュメントはヘルプダイアログの設定ページから開かれます" #: e2_option__default.c:98 msgid "warn about running processes when shutting down" msgstr "終了時にプロセスが実行されているときは警告" #: e2_option__default.c:99 msgid "This enables a reminder about incomplete commands and actions" msgstr "完了していないコマンドとアクションがあることを通知する" #: e2_option__default.c:115 msgid "opacity" msgstr "透過率" #: e2_option__default.c:116 msgid "Window translucence, 30 (faint) to 100 (opaque)" msgstr "ウィンドウの透過率。30 (薄い) から 100 (不透明) まで" #: e2_option__default.c:120 msgid "'go up' on middle-button click" msgstr "中ボタンのクリックで上に移動" #: e2_option__default.c:121 msgid "" "This is a faster alternative to double clicking '..' or clicking the 'go up' " "button" msgstr "'..' をダブルクリックしたり '1つ上へ' をクリックするのを短縮する" #: e2_option__default.c:123 msgid "match windows (TM) right-click behaviour" msgstr "Windows (TM) 風の右クリック操作" #: e2_option__default.c:124 msgid "" "If activated, clicking the right mouse button will also select the row where " "the mouse cursor is" msgstr "" #: e2_option__default.c:127 msgid "advanced windows (TM) right-click behaviour" msgstr "より高度な Windows (TM) 風の右クリック操作" #: e2_option__default.c:128 msgid "" "If activated, clicking on a free area will not clear the current selection" msgstr "有効にすると、空きエリアをクリックしても現在の選択をクリアしません" #: e2_option__default.c:131 msgid "show icons in dialog buttons" msgstr "ダイアログボタンにアイコンを表示" #: e2_option__default.c:132 msgid "If activated, dialog buttons will show an icon as well as a label" msgstr "有効にすると、ラベルといっしょにアイコンを表示します" #: e2_option__default.c:137 msgid "use icons directory" msgstr "アイコンディレクトリを使う" #: e2_option__default.c:138 msgid "" "If activated, icon files in the directory shown below will be used for " "toolbars etc" msgstr "" #: e2_option__default.c:142 msgid "icons directory" msgstr "アイコンディレクトリ" #: e2_option__default.c:143 msgid "The directory from which icon files will be retrieved" msgstr "アイコンファイルがあるディレクトリ" #: e2_option__default.c:155 msgid "show icons in menus" msgstr "メニューにアイコンを表示" #: e2_option__default.c:156 msgid "Some people think that icons help you recognize items more quickly" msgstr "アイコンがあるとアイテムを認識しやすくなります" #: e2_option__default.c:160 e2_option__default.c:329 e2_toolbar.c:2794 msgid "button" msgstr "ボタン" #: e2_option__default.c:160 e2_option__default.c:329 e2_toolbar.c:2794 msgid "dnd" msgstr "dnd" #: e2_option__default.c:160 e2_option__default.c:329 e2_toolbar.c:2794 msgid "menu" msgstr "メニュー" #: e2_option__default.c:160 e2_option__default.c:329 e2_toolbar.c:2794 msgid "toolbar large" msgstr "大きいツールバー" #: e2_option__default.c:160 e2_option__default.c:329 e2_toolbar.c:2794 msgid "toolbar small" msgstr "小さいツールバー" #: e2_option__default.c:161 msgid "menu icon size" msgstr "メニューアイコンのサイズ" #: e2_option__default.c:162 msgid "This sets the icon size for ALL menus" msgstr "全てのメニューのアイコンサイズを指定" #: e2_option__default.c:167 msgid "menu popup delay (ms)" msgstr "メニューがポップアップするまでの時間 (ms)" #: e2_option__default.c:168 msgid "" "The delay (in milliseconds) after the mouse pointer arrives at a menu item, " "before any submenu will pop up" msgstr "" #: e2_option__default.c:171 msgid "menu popdown delay (ms)" msgstr "メニューがポップダウンするまでの時間 (ms)" #: e2_option__default.c:172 msgid "" "The delay (in milliseconds) after the mouse pointer leaves a menu item, " "before popping down an activated submenu" msgstr "" #: e2_option__default.c:181 msgid "auto refresh" msgstr "自動的に更新" #: e2_option__default.c:182 msgid "" "This enables automatic checking for changes to the content of displayed " "directories. Otherwise you need to refresh manually" msgstr "" #: e2_option__default.c:185 msgid "focus the pane after completing a directory entry" msgstr "ディレクトリの表示が完了したのちペインをフォーカスする" #: e2_option__default.c:186 msgid "" "If activated, after return/enter is pressed in a directory line, the pane " "containing that dir line will become the active one" msgstr "" #: e2_option__default.c:190 msgid "select first item in newly-opened directories" msgstr "新規に開いたディレクトリでは最初のアイテムを選択する" #: e2_option__default.c:191 msgid "" "If activated, when a directory is first displayed in a session, the first " "item there will be selected" msgstr "" #: e2_option__default.c:196 msgid "use horizontal panes" msgstr "ペインを水平に並べる" #: e2_option__default.c:197 msgid "Horizontal (vertical-stacked) panes present more columns at once" msgstr "ペインを水平表示にするとより多くのカラムを表示できます" #: e2_option__default.c:205 msgid "bottom-left" msgstr "左下" #: e2_option__default.c:205 msgid "bottom-right" msgstr "右下" #: e2_option__default.c:205 msgid "top-left" msgstr "左上" #: e2_option__default.c:205 msgid "top-right" msgstr "右上" #: e2_option__default.c:206 msgid "scrollbar position" msgstr "スクロールバーの位置" #: e2_option__default.c:207 msgid "The default (bottom-right) should be ok for most users" msgstr "デフォルト (右下) が無難でしょう" #: e2_option__default.c:210 msgid "bold name header" msgstr "太字にする" #: e2_option__default.c:210 msgid "colored headers" msgstr "色を付ける" #: e2_option__default.c:210 msgid "sensitivity" msgstr "細かさ" #: e2_option__default.c:211 msgid "type of indicator for active pane" msgstr "アクティブペインの表示方法" #: e2_option__default.c:212 msgid "" "This determines how the active pane is indicated on-screen. Some GTK themes " "do not allow column-header re-coloring" msgstr "" #: e2_option__default.c:216 msgid "banded background" msgstr "背景を縞状に" #: e2_option__default.c:217 msgid "If activated, lines in file lists will alternate background color" msgstr "" #: e2_option__default.c:221 e2_output.c:2457 e2_view_dialog.c:1862 msgid "use custom font" msgstr "カスタムフォントを使う" #: e2_option__default.c:222 e2_output.c:2458 e2_view_dialog.c:1863 msgid "" "If activated, the font specified below will be used, instead of the theme " "default" msgstr "" #: e2_option__default.c:225 msgid "custom font" msgstr "カスタムフォント" #: e2_option__default.c:226 msgid "This is the font used for flle pane text" msgstr "ファイルペインの文字のフォント" #: e2_option__default.c:229 msgid "Default: May 20 09:11" msgstr "デフォルト: May 20 09:11" #: e2_option__default.c:230 msgid "American: 05/20/04 09:11" msgstr "アメリカ式: 05/20/04 09:11" #: e2_option__default.c:230 msgid "Standard: 20/05/04 09:11" msgstr "標準: 20/05/04 09:11" #: e2_option__default.c:231 msgid "LC_TIME locale specified" msgstr "LC_TIME ロケールに従う" #: e2_option__default.c:232 msgid "date format" msgstr "日付形式" #: e2_option__default.c:233 msgid "This determines the format of all dates displayed in the panes" msgstr "" #: e2_option__default.c:237 msgid "condensed" msgstr "単位を付ける" #: e2_option__default.c:237 msgid "exact" msgstr "そのまま" #: e2_option__default.c:238 msgid "size format" msgstr "サイズ形式" #: e2_option__default.c:239 msgid "" "Displayed item-sizes can be 'condensed' to show kB, MB where appropriate" msgstr "kB, MB などの単位をつけてサイズを表示" #: e2_option__default.c:243 msgid "show parent directory entry '..' in file lists" msgstr "親ディレクトリを '..' でファイルリストに表示" #: e2_option__default.c:244 msgid "This slows status-line updates" msgstr "状態の更新を遅くする" #: e2_option__default.c:246 msgid "filename sort is case sensitive" msgstr "大文字小文字を区別してファイルをソート" #: e2_option__default.c:247 msgid "" "This places all the capitalised file/directory names ahead of the others, if " "your LANG_C suports that" msgstr "" #: e2_option__default.c:250 msgid "both panes are like pane 1" msgstr "両方のペインを ペイン 1 のようにする" #: e2_option__default.c:252 e2_option__default.c:258 #, c-format msgid "" "This makes pane %d options (other than toolbar placement and content) apply " "to pane %d" msgstr "" #: e2_option__default.c:256 msgid "both panes are like pane 2" msgstr "両方のペインを ペイン 2 のようにする" #: e2_option__default.c:268 msgid "executable" msgstr "実行ファイル" #: e2_option__default.c:269 msgid "Executable file names are listed in this color" msgstr "実行ファイルをこの色で表示する" #: e2_option__default.c:271 e2p_du.c:220 e2p_find.c:3018 msgid "directory" msgstr "ディレクトリ" #: e2_option__default.c:272 msgid "Directory names are listed in this color" msgstr "ディレクトリ名をこの色で表示する" #: e2_option__default.c:275 msgid "Symbolic link names are listed in this color" msgstr "シンボリックリンクをこの色で表示する" #: e2_option__default.c:277 msgid "device" msgstr "デバイス" #: e2_option__default.c:278 msgid "Device names are listed in this color" msgstr "デバイス名をこの色で表示する" #: e2_option__default.c:280 e2p_find.c:3051 msgid "socket" msgstr "ソケット" #: e2_option__default.c:281 msgid "Sockets are listed in this color" msgstr "ソケットをこの色で表示する" #: e2_option__default.c:286 msgid "custom background color" msgstr "カスタム背景色" #: e2_option__default.c:287 msgid "If enabled, the color specified below will be used for background" msgstr "" #: e2_option__default.c:290 msgid "background color" msgstr "背景色" #: e2_option__default.c:291 msgid "Background color used instead of theme color" msgstr "" #: e2_option__default.c:295 msgid "active pane header color" msgstr "アクティブペインのヘッダ" #: e2_option__default.c:296 msgid "" "This color is used for the background of column headers in the active pane" msgstr "" #: e2_option__default.c:298 msgid "highlight color" msgstr "ハイライト時の色" #: e2_option__default.c:299 msgid "" "This color is used for the background of highlighed item-names and other text" msgstr "" #: e2_option__default.c:320 e2_toolbar.c:2784 msgid "icon above label" msgstr "文字の上にアイコン" #: e2_option__default.c:320 e2_toolbar.c:2784 msgid "icon beside label" msgstr "文字の横にアイコン" #: e2_option__default.c:320 e2_toolbar.c:2784 msgid "icon only" msgstr "アイコンのみ" #: e2_option__default.c:320 e2_toolbar.c:2784 msgid "label only" msgstr "文字のみ" #: e2_option__default.c:320 e2_option__default.c:329 e2_toolbar.c:2784 #: e2_toolbar.c:2794 msgid "theme" msgstr "テーマに従う" #: e2_option__default.c:321 e2_toolbar.c:2785 #, c-format msgid "'%s' uses the Gtk default, '%s' leaves most space for other things" msgstr "" #: e2_option__default.c:323 msgid "toolbars button style" msgstr "ツールバーボタンの表示" #: e2_option__default.c:330 e2_toolbar.c:2795 #, c-format msgid "'%s' uses the Gtk default, '%s' is smallest, '%s' is largest" msgstr "'%s' は Gtk のデフォルト, '%s' は最小, '%s' は最大" #: e2_option__default.c:332 msgid "toolbars icon size" msgstr "ツールバーのアイコンサイズ" #: e2_option_tree.c:163 e2_option_tree.c:408 msgid "Press key" msgstr "キーを押してください" #: e2_option_tree.c:621 #, c-format msgid "select icon for %s" msgstr "%s のアイコンを選択" #: e2_option_tree.c:666 #, c-format msgid "Are you sure that you want to delete this row and %d %s?" msgstr "この行と %d %s を削除しますか" #: e2_option_tree.c:670 msgid "confirm row delete" msgstr "行の削除時に確認" #: e2_option_tree.c:956 e2_toolbar.c:2899 e2p_acl.c:3791 e2p_find.c:3613 #: e2p_rename.c:1538 msgid "_Help" msgstr "ヘルプ(_H)" #: e2_option_tree.c:957 msgid "Get help on this option" msgstr "このオプションのヘルプ" #: e2_option_tree.c:960 msgid "_Select" msgstr "選択(_S)" #: e2_option_tree.c:961 msgid "Show plugin-selection dialog" msgstr "プラグイン選択ダイアログを表示" #: e2_option_tree.c:964 msgid "Co_lor" msgstr "配色(_l)" #: e2_option_tree.c:965 msgid "Show color-selection dialog" msgstr "色選択ダイアログを表示" #: e2_option_tree.c:979 msgid "Remove the selected row" msgstr "選んだ行を削除" #: e2_option_tree.c:981 msgid "Add a row after the currently selected row in the tree" msgstr "このツリーの現在選んでいる列の後に列を追加" #: e2_option_tree.c:984 #, fuzzy msgid "Add ch_ild" msgstr "子列(_h)" #: e2_option_tree.c:985 msgid "Add a child row to the currently selected one" msgstr "現在選んでいるものに子列を追加" #: e2_option_tree_context_menu.c:228 msgid "Cut selected row and any descendant(s)" msgstr "選んだ列と全ての子列をカット" #: e2_option_tree_context_menu.c:232 msgid "Copy selected row and any descendant(s)" msgstr "選んだ列と全ての子列をコピー" #: e2_option_tree_context_menu.c:244 msgid "Expand all rows on this page" msgstr "このページの全ての列を展開" #: e2_option_tree_context_menu.c:248 msgid "Collapse all rows on this page" msgstr "このページの全ての列を畳む" #: e2_option_tree_context_menu.c:260 msgid "Add c_hild" msgstr "子列(_h)" #: e2_option_tree_context_menu.c:261 msgid "Add a child to the selected row" msgstr "選んだ列に子を追加" #: e2_output.c:548 msgid "output tabs" msgstr "出力タブ" #: e2_output.c:1070 #, c-format msgid "Cannot find any output from process %s" msgstr "プロセス %s からの出力はありません" #: e2_output.c:1347 msgid "Do not show the output pane" msgstr "出力ペインを表示しない" #: e2_output.c:1351 msgid "_Toggle full" msgstr "最大化のトグル(_T)" #: e2_output.c:1354 msgid "Toggle output pane size to/from the full window size" msgstr "出力ペインのサイズを全画面にするかどうかの切替" #: e2_output.c:1358 msgid "_New tab" msgstr "新規タブ(_N)" #: e2_output.c:1359 msgid "Add another tab for the output pane" msgstr "出力ペイン用に別のタブを追加" #: e2_output.c:1363 msgid "_Remove tab" msgstr "タブを削除(_R)" #: e2_output.c:1364 msgid "Close this this tab" msgstr "このタブを閉じる" #: e2_output.c:1373 msgid "_Attach" msgstr "添付(_A)" #: e2_output.c:1374 msgid "Move this tab back to output pane" msgstr "このタブを出力ペインに移動" #: e2_output.c:1381 e2_output.c:1385 msgid "_Clear" msgstr "クリア(_C)" #: e2_output.c:1382 e2_output.c:1386 msgid "Clear this tab" msgstr "このタブをクリア" #: e2_output.c:1395 msgid "Edit the tab contents" msgstr "タブの内容を編集" #: e2_output.c:1417 msgid "Co_mmand output" msgstr "コマンドの出力(_m)" #: e2_output.c:1418 msgid "Show output from a completed command" msgstr "完了したコマンドからの出力を表示" #: e2_output.c:1431 e2_view_dialog.c:958 msgid "_Settings" msgstr "設定(_S)" #: e2_output.c:1442 msgid "_Other" msgstr "その他(_O)" #: e2_output.c:1443 msgid "Open the configuration dialog at the output options page" msgstr "出力オプションページに設定ダイアログを開く" #: e2_output.c:1772 msgid "-- end-of-output --" msgstr "-- 出力完了 --" #: e2_output.c:2415 msgid "show output pane when a new message appears" msgstr "新しいメッセージが出たら出力ペインを表示" #: e2_output.c:2416 msgid "This will ensure you don't miss any messages" msgstr "" #: e2_output.c:2419 msgid "show output pane if the command line is focused" msgstr "コマンドラインがフォーカスされると出力ペインを表示" #: e2_output.c:2420 msgid "" "This causes the output pane to be opened when you are about enter a command" msgstr "" #: e2_output.c:2424 msgid "hide output pane if the command line is unfocused" msgstr "コマンドラインのフォーカスが外れると出力ペインを隠す" #: e2_output.c:2425 msgid "" "This causes the output pane to be closed when you move focus away from the " "command line" msgstr "" #: e2_output.c:2428 msgid "show commands" msgstr "コマンドを表示" #: e2_output.c:2429 msgid "This echoes the commands that are run and their exit value" msgstr "" #: e2_output.c:2431 msgid "scroll to new output" msgstr "新しい出力へスクロール" #: e2_output.c:2432 msgid "" "This will automatically scroll the output pane content, to show new message" "(s)" msgstr "新しいメッセージを表示するために出力ペインの内容を自動的にスクロール" #: e2_output.c:2434 msgid "only scroll when really following" msgstr "表示を続けているときだけスクロール" #: e2_output.c:2435 msgid "" "This stops automatic pane scrolling to new output if you've manually " "scrolled away" msgstr "手動でスクロールすると自動スクロールを止める" #: e2_output.c:2438 msgid "only scroll when new output is at the end" msgstr "出力が終わったときだけスクロール" #: e2_output.c:2439 msgid "" "This stops pane scrolling if a different process has displayed text after " "the current insert position" msgstr "" #: e2_output.c:2442 msgid "everywhere" msgstr "全て" #: e2_output.c:2442 msgid "words" msgstr "単語" #: e2_output.c:2444 msgid "line wrap mode" msgstr "行の折り返しモード" #: e2_output.c:2445 msgid "" "If mode is 'none', a horizontal scrollbar will be available. Mode 'words' " "will only break the line between words" msgstr "" "モードが 'なし' だとスクロールバーを表示する。モードが '単語' だと単語間の行" "を分割するだけ" #: e2_output.c:2448 msgid "left margin (pixels)" msgstr "左マージン (ピクセル)" #: e2_output.c:2449 msgid "This is the left margin between the output-pane edge and the text in it" msgstr "" #: e2_output.c:2452 msgid "right margin (pixels)" msgstr "右マージン (ピクセル)" #: e2_output.c:2453 msgid "" "This is the right margin between the output-pane edge and the text in it" msgstr "" #: e2_output.c:2461 msgid "custom output font" msgstr "出力用フォント" #: e2_output.c:2462 msgid "This is the font used for text in the output pane" msgstr "出力ペインの文字のフォント" #: e2_output.c:2467 msgid "positive color" msgstr "成功したときの色" #: e2_output.c:2468 msgid "" "This color is used for messages about successful operations or other " "'positive events'" msgstr "" #: e2_output.c:2471 msgid "negative color" msgstr "失敗したときの色" #: e2_output.c:2472 msgid "" "This color is used for messages about unsuccessful operations or other " "'negative events'" msgstr "" #: e2_output.c:2475 msgid "unimportant color" msgstr "重要度低" #: e2_output.c:2476 msgid "" "This color is used for messages of minor importance or other miscellaneous " "events" msgstr "" #: e2_ownership_dialog.c:167 msgid "You are not authorised to make any change." msgstr "変更する権限がありません" #: e2_ownership_dialog.c:209 msgid "ownership" msgstr "所有権" #: e2_ownership_dialog.c:217 e2_permissions_dialog.c:327 e2p_acl.c:3467 #: e2p_times.c:697 msgid "Directory name" msgstr "ディレクトリ名" #: e2_ownership_dialog.c:233 msgid "Permissions: " msgstr "権限: " #: e2_ownership_dialog.c:245 msgid "User: " msgstr "ユーザ: " #: e2_ownership_dialog.c:251 msgid "Group: " msgstr "グループ: " #: e2_ownership_dialog.c:375 msgid "Recurse" msgstr "下位dirも" #: e2_pane.c:883 msgid "No trash directory is available" msgstr "ゴミ箱用ディレクトリがありません" #: e2_pane.c:985 msgid "_Edit places" msgstr "場所を編集(_E)" #: e2_pane.c:1584 #, c-format msgid "show %s column" msgstr "%s 欄を表示する" #: e2_pane.c:1585 #, c-format msgid "This causes the the named column to be displayed in pane %d (duh ...)" msgstr "" #: e2_pane.c:1615 #, c-format msgid "At startup, show a specific directory in pane %d" msgstr "起動時 ペイン %d に特定のディレクトリを表示" #: e2_pane.c:1617 msgid "" "This causes the directory named below, instead of the last-opened directory, " "to be shown at session start" msgstr "" #: e2_pane.c:1622 #, c-format msgid "pane %d startup directory:" msgstr "ペイン %d の起動時のディレクトリ:" #: e2_pane.c:1624 msgid "This is the directory to show in this pane, at session start" msgstr "セッション開始時にこのペインに表示するディレクトリ" #: e2_password_dialog.c:113 e2p_crypt.c:2922 msgid "Enter password:" msgstr "パスワードを入力:" #: e2_password_dialog.c:191 e2p_crypt.c:2934 msgid "Confirm password:" msgstr "パスワードを確認:" #: e2_password_dialog.c:249 msgid "Entered passwords are different" msgstr "異なるパスワードを入力しています" #: e2_permissions_dialog.c:337 e2_permissions_dialog.c:400 #: e2_vfs_dialog.c:1430 e2p_acl.c:229 e2p_acl.c:2090 e2p_acl.c:3477 #: e2p_gvfs.c:647 msgid "User" msgstr "ユーザ" #: e2_permissions_dialog.c:379 msgid "currently" msgstr "現在" #: e2_permissions_dialog.c:395 e2p_acl.c:222 msgid "Read" msgstr "読み" #: e2_permissions_dialog.c:396 e2p_acl.c:222 msgid "Write" msgstr "書き" #: e2_permissions_dialog.c:397 e2p_acl.c:222 msgid "Exec" msgstr "実行" #: e2_permissions_dialog.c:398 msgid "Special" msgstr "特殊" #: e2_permissions_dialog.c:407 msgid "Set UID" msgstr "Set UID" #: e2_permissions_dialog.c:417 msgid "Set GID" msgstr "Set GID" #: e2_permissions_dialog.c:420 e2p_acl.c:229 e2p_acl.c:2099 msgid "Other" msgstr "その他" #: e2_permissions_dialog.c:427 msgid "Sticky" msgstr "Sticky" #: e2_permissions_dialog.c:440 e2p_acl.c:3704 msgid "Action:" msgstr "アクション:" #: e2_permissions_dialog.c:450 e2p_acl.c:3722 e2p_times.c:751 e2p_times.c:753 #: e2p_times.c:755 msgid "_Set" msgstr "セット(_S)" #: e2_permissions_dialog.c:466 e2p_acl.c:3746 e2p_times.c:757 msgid "R_ecurse:" msgstr "下位dirも(_e):" #: e2_permissions_dialog.c:475 e2p_acl.c:3754 msgid "" "If activated, changes will be applied to selected items and also their " "descendents, if such items match your choice of \"directories\" and/or " "\"others\" (anything not a directory)" msgstr "" #: e2_permissions_dialog.c:478 e2p_acl.c:3757 msgid "d_irectories" msgstr "ディレクトリ(_i)" #: e2_permissions_dialog.c:481 e2p_acl.c:3760 msgid "o_thers" msgstr "その他(_t)" #: e2_plugins.c:299 msgid "Cannot unload plugin" msgstr "プラグインをアンロードできません" #: e2_plugins.c:738 msgid "Plugin not loaded" msgstr "プラグインをロードできません" #: e2_plugins.c:1274 msgid "Loaded" msgstr "ロードしました" #: e2_select_image_dialog.c:607 #, fuzzy msgid "Choose icons directory" msgstr "アイコンディレクトリを使う" #: e2_select_image_dialog.c:627 #, fuzzy msgid "_other" msgstr "その他(_O)" #: e2_select_image_dialog.c:631 #, fuzzy msgid "_stock" msgstr "ソケット(_S)" #: e2_select_image_dialog.c:646 msgid "Remove the current icon" msgstr "現在のアイコンを削除" #: e2_size_filter_dialog.c:145 msgid "Display only the items which are:" msgstr "このアイテムだけを表示:" #: e2_size_filter_dialog.c:146 msgid "size filter" msgstr "サイズで絞る" #: e2_size_filter_dialog.c:153 e2p_glob.c:460 msgid "bigger than" msgstr "これより大きい" #: e2_size_filter_dialog.c:153 e2p_glob.c:460 msgid "equal to" msgstr "これに等しい" #: e2_size_filter_dialog.c:153 e2p_glob.c:460 msgid "smaller than" msgstr "これより小さい" #: e2_size_filter_dialog.c:192 e2p_find.c:2785 e2p_glob.c:501 msgid "Mbytes" msgstr "Mbytes" #: e2_size_filter_dialog.c:192 e2p_find.c:2783 e2p_glob.c:501 msgid "kbytes" msgstr "kbytes" #: e2_task.c:516 #, c-format msgid "%s added to tasks queue" msgstr "%s をタスクキューに追加" #: e2_task.c:784 #, c-format msgid "%s operation incomplete - time limit exceeded" msgstr "%s の操作は未完了 - タイムリミットが過ぎました" #: e2_task.c:868 #, c-format msgid "The current operation (%s)" msgstr "現在の操作 (%s)" #: e2_task.c:870 msgid "operation" msgstr "操作" #: e2_task.c:1039 msgid "File operation in progress" msgstr "ファイル操作を実行中" #: e2_task.c:1184 msgid "Operation not permitted - Circular directories" msgstr "操作は許可されません - 循環ディレクトリです" #: e2_task.c:1416 e2_task.c:1742 e2_task.c:1985 e2_task.c:2352 e2p_clone.c:78 msgid "Enter new name for" msgstr "新しい名前を入力" #: e2_task.c:1991 msgid "link" msgstr "link" #: e2_task.c:2113 #, c-format msgid "Are you sure you want to delete %s?" msgstr "本当に %s を削除しますか?" #: e2_task.c:2444 e2p_rename.c:1017 e2p_rename.c:1019 #, c-format msgid "Cannot rename %s" msgstr "%s をリネームできません" #: e2_task.c:2582 e2p_acl.c:4165 #, c-format msgid "You do not have authority to change permission(s) of %s" msgstr "%s の権限を変更する許可がありません" #: e2_task.c:2740 #, c-format msgid "You do not have authority to change owner(s) of %s" msgstr "%s の所有者を変更する許可がありません" #: e2_task.c:3248 msgid "open with" msgstr "アプリで開く" #: e2_task.c:3249 e2_toolbar.c:2893 msgid "Enter command:" msgstr "コマンドを入力:" #: e2_task.c:3420 msgid "Enter a name or partial name to find:" msgstr "検索する名前の一部または全部を入力:" #: e2_task_backend.c:330 #, c-format msgid "Cannot change ownership of %s" msgstr "%s の所有者を変更できません" #: e2_task_backend.c:490 e2_task_backend.c:506 e2_task_backend.c:1613 #, c-format msgid "Cannot delete %s" msgstr "%s を削除できません" #: e2_task_backend.c:663 e2_task_backend.c:1578 #, c-format msgid "Cannot delete existing %s" msgstr "既存の %s を削除できません" #: e2_task_backend.c:722 #, c-format msgid "Cannot create special file %s" msgstr "special ファイル %s を作成できません" #: e2_task_backend.c:737 #, c-format msgid "Cannot create FIFO %s" msgstr "FIFO を作成できません %s" #: e2_task_backend.c:874 #, c-format msgid "Cannot create new link to %s" msgstr "%s への新規リンクを作れません" #: e2_task_backend.c:885 #, c-format msgid "Cannot get target info for link %s" msgstr "リンク %s のターゲット情報を取得できません" #: e2_task_backend.c:1357 #, c-format msgid "Cannot copy all of %s" msgstr "%s の全てをコピーできません" #: e2_task_backend.c:1378 msgid "incomplete" msgstr "不完全" #: e2_task_backend.c:1463 #, c-format msgid "Cannot move %s to %s" msgstr "%s を %s に移動できません" #: e2_task_backend.c:1518 #, c-format msgid "Cannot create link to %s" msgstr "%s へのリンクを作成できません" #: e2_task_backend.c:1566 #, c-format msgid "Cannot rename %s to %s" msgstr "%s を %s にリネームできません" #: e2_task_backend.c:1648 e2_task_backend.c:1764 e2p_acl.c:2826 #: e2p_crypt.c:1773 e2p_times.c:319 #, c-format msgid "Cannot get current data for %s" msgstr "%s の現在のデータを取得できません" #: e2_task_backend.c:1699 e2p_acl.c:1870 #, c-format msgid "Cannot change permission(s) of all of %s" msgstr "%s の全ての権限を変更できません" #: e2_task_backend.c:1718 #, c-format msgid "Cannot get current permissions of %s" msgstr "%s の現在の権限を取得できません" #: e2_task_backend.c:1834 #, c-format msgid "Cannot change ownership of all of %s" msgstr "%s の全ての所有者を変更できません" #: e2_toolbar.c:357 msgid "Show _bar" msgstr "バーを表示(_b)" #: e2_toolbar.c:359 msgid "Show _tooltips" msgstr "ツールチップを表示(_t)" #: e2_toolbar.c:361 msgid "Space _handling" msgstr "スペースの操作(_h)" #: e2_toolbar.c:364 msgid "_none" msgstr "なし(_n)" #: e2_toolbar.c:366 msgid "_scrollbars" msgstr "スクロールバー(_s)" #: e2_toolbar.c:368 msgid "_rest button" msgstr "残りのボタン(_r)" #: e2_toolbar.c:370 msgid "_Placement" msgstr "配置(_P)" #: e2_toolbar.c:371 msgid "_horizontal" msgstr "水平に並べる(_h)" #: e2_toolbar.c:373 msgid "_Container" msgstr "_Container" #: e2_toolbar.c:376 msgid "_main window" msgstr "メインウィンドウ(_m)" #: e2_toolbar.c:378 msgid "_both panes" msgstr "両方のペイン(_b)" #: e2_toolbar.c:380 msgid "file-pane _1" msgstr "ファイルペイン _1" #: e2_toolbar.c:382 msgid "flle-pane _2" msgstr "ファイルペイン _2" #: e2_toolbar.c:392 msgid "_Left" msgstr "左(_L)" #: e2_toolbar.c:394 msgid "_Right" msgstr "右(_R)" #: e2_toolbar.c:397 msgid "_Reset order" msgstr "順序をリセット(_R)" #: e2_toolbar.c:399 msgid "Buttons _relief" msgstr "浮き出しボタン(_r)" #: e2_toolbar.c:401 msgid "Buttons _same size" msgstr "同じサイズのボタン(_s)" #: e2_toolbar.c:403 msgid "_Button style" msgstr "ボタンのスタイル(_B)" #: e2_toolbar.c:407 e2_toolbar.c:421 msgid "_theme" msgstr "テーマに従う(_t)" #: e2_toolbar.c:409 msgid "icon _only" msgstr "アイコンのみ(_o)" #: e2_toolbar.c:411 msgid "_label only" msgstr "文字のみ(_l)" #: e2_toolbar.c:413 msgid "icon _above label" msgstr "文字の上にアイコン(_a)" #: e2_toolbar.c:415 msgid "icon _beside label" msgstr "文字の横にアイコン(_b)" #: e2_toolbar.c:417 msgid "_Icon size" msgstr "アイコンのサイズ(_I)" #: e2_toolbar.c:423 msgid "_menu" msgstr "メニュー(_m)" #: e2_toolbar.c:425 msgid "toolbar _small" msgstr "小さいツールバー(_s)" #: e2_toolbar.c:427 msgid "toolbar _large" msgstr "大きいツールバー(_l)" #: e2_toolbar.c:429 msgid "_button" msgstr "ボタン(_b)" #: e2_toolbar.c:431 msgid "drag '_n' drop" msgstr "ドラッグアンドドロップ(_n)" #: e2_toolbar.c:433 msgid "_dialog" msgstr "ダイアログ(_n)" #: e2_toolbar.c:436 msgid "Bar i_tems" msgstr "バーのアイテム(_t)" #: e2_toolbar.c:719 msgid "_Rest" msgstr "残り(_R)" #: e2_toolbar.c:720 msgid "Show a menu of hidden items" msgstr "隠れているメニューを表示" #: e2_toolbar.c:1603 msgid "not" msgstr "not" #: e2_toolbar.c:1634 msgid "accessed" msgstr "アクセス" #: e2_toolbar.c:1634 msgid "changed" msgstr "変更" #: e2_toolbar.c:1634 msgid "modified" msgstr "修正" #: e2_toolbar.c:2712 msgid "show the " msgstr "次のものを表示 " #: e2_toolbar.c:2713 #, c-format msgid "This determines whether the %s is displayed or hidden" msgstr "%s を表示するか隠すかを決定する" #: e2_toolbar.c:2720 #, c-format msgid "show tooltips for %s buttons" msgstr "%s ボタンのツールチップを表示" #: e2_toolbar.c:2721 #, c-format msgid "If deactivated, tooltips will not be displayed for %s buttons" msgstr "無効にすると、%s ボタンのツールチップを表示しません" #: e2_toolbar.c:2727 #, c-format msgid "" "This determines the method for accessing %s elements that are hidden due to " "lack of screen-space" msgstr "" #: e2_toolbar.c:2729 msgid "use rest button" msgstr "残りのボタンを使う" #: e2_toolbar.c:2729 msgid "use scrollbars" msgstr "スクロールバーを使う" #: e2_toolbar.c:2730 msgid "space handling" msgstr "スペースの操作" #: e2_toolbar.c:2737 msgid " container" msgstr "内容" #: e2_toolbar.c:2738 #, c-format msgid "This determines the 'box' into which the %s is placed" msgstr "" #: e2_toolbar.c:2740 msgid "both panes" msgstr "両方のペイン" #: e2_toolbar.c:2740 msgid "file-pane 1" msgstr "ファイルペイン 1" #: e2_toolbar.c:2740 msgid "file-pane 2" msgstr "ファイルペイン 2" #: e2_toolbar.c:2740 msgid "main window" msgstr "メインウィンドウ" #: e2_toolbar.c:2748 msgid " horizontal" msgstr " 水平に並べる" #: e2_toolbar.c:2749 #, c-format msgid "This determines whether the %s is displayed horizontally or vertically" msgstr "" #: e2_toolbar.c:2756 msgid " priority" msgstr " 優先度" #: e2_toolbar.c:2757 msgid "This determines the order of toolbars (0 = left or top)" msgstr "" #: e2_toolbar.c:2766 msgid " buttons have relief" msgstr " ボタンを浮き出させる" #: e2_toolbar.c:2768 msgid "" "Buttons with relief show their outline continually, not just when 'moused'" msgstr "" #: e2_toolbar.c:2775 msgid " buttons are the same size" msgstr " 同じサイズのボタン" #: e2_toolbar.c:2777 msgid "" "Equal-width buttons look good on a vertical bar, bad on a horizontal bar" msgstr "同幅ボタンは垂直バーに適しています。水平バーには不向き" #: e2_toolbar.c:2787 msgid "button style" msgstr "ボタンのスタイル" #: e2_toolbar.c:2797 msgid "icon size" msgstr "アイコンのサイズ" #: e2_toolbar.c:2817 msgid "Copy item(s) selected in the active pane to the other one" msgstr "選んだアイテムを他のペインにコピー" #: e2_toolbar.c:2819 msgid "Move item(s) selected in the active pane to the other one" msgstr "選んだアイテムを他のペインに移動" #: e2_toolbar.c:2821 msgid "Symlink item(s) selected in the active pane to the other one" msgstr "選んだアイテムのsymlinkを他のペインに張る" #: e2_toolbar.c:2822 msgid "Re_name.." msgstr "リネーム(_n)" #: e2_toolbar.c:2823 msgid "Rename item(s) selected in the active pane" msgstr "選んだアイテムをリネーム" #: e2_toolbar.c:2825 msgid "Move item(s) selected in the active pane to a trashbin" msgstr "選んだアイテムをゴミ箱に移動" #: e2_toolbar.c:2826 msgid "Create new directory(ies)" msgstr "新しいディレクトリを作成" #: e2_toolbar.c:2832 msgid "Re_fresh" msgstr "更新(_f)" #: e2_toolbar.c:2832 msgid "Update pane contents" msgstr "ペインの内容を更新" #: e2_toolbar.c:2833 msgid "_Switch" msgstr "切替(_S)" #: e2_toolbar.c:2834 msgid "Toggle the active pane" msgstr "アクティブなペインを切り替える" #: e2_toolbar.c:2880 msgid "Full" msgstr "最大" #: e2_toolbar.c:2880 msgid "Maximize output pane" msgstr "出力ペインを最大化" #: e2_toolbar.c:2881 msgid "Shrink" msgstr "縮小" #: e2_toolbar.c:2881 msgid "Un-maximize output pane" msgstr "出力ペインを元に戻す" #: e2_toolbar.c:2882 msgid "Hide" msgstr "隠す" #: e2_toolbar.c:2882 msgid "Hide output pane" msgstr "出力ペインを隠す" #: e2_toolbar.c:2883 msgid "Show" msgstr "表示" #: e2_toolbar.c:2883 msgid "Show output pane" msgstr "出力ペインを表示" #: e2_toolbar.c:2884 msgid "Clear" msgstr "クリア" #: e2_toolbar.c:2884 msgid "Clear output pane" msgstr "出力ペインをクリア" #: e2_toolbar.c:2886 msgid "Clear command line" msgstr "コマンドラインをクリア" #: e2_toolbar.c:2886 msgid "cl" msgstr "cl" #: e2_toolbar.c:2887 msgid "Child processes" msgstr "子プロセス" #: e2_toolbar.c:2887 msgid "ps" msgstr "ps" #: e2_toolbar.c:2888 msgid "Calculate disk usage of selected item(s)" msgstr "選んだアイテムの容量を計算" #: e2_toolbar.c:2888 e2p_du.c:267 msgid "du" msgstr "du" #: e2_toolbar.c:2889 msgid "Find item in active pane, by name" msgstr "アクティブペインのアイテムを検索" #: e2_toolbar.c:2890 msgid "Open terminal at the active directory" msgstr "ターミナルを開く" #: e2_toolbar.c:2890 msgid "_X" msgstr "_X" #: e2_toolbar.c:2893 msgid "Run something as root" msgstr "rootで何かを実行" #: e2_toolbar.c:2893 msgid "su.." msgstr "su.." #: e2_toolbar.c:2895 msgid "Mount or unmount a device" msgstr "デバイスをマウント/アンマウントする" #: e2_toolbar.c:2895 msgid "mts.." msgstr "mts.." #: e2_toolbar.c:2898 msgid "View/change configuration settings for this program" msgstr "設定の参照と変更" #: e2_toolbar.c:2898 msgid "_Settings.." msgstr "設定(_S)" #: e2_toolbar.c:2899 msgid "Get information about this program" msgstr "このプログラムについて" #: e2_toolbar.c:2901 msgid "Close this program" msgstr "このプログラムを閉じる" #: e2_toolbar.c:2901 msgid "_Quit" msgstr "終了(_Q)" #: e2_toolbar.c:2950 e2_toolbar.c:3015 msgid "Hide other pane" msgstr "他のペインを隠す" #: e2_toolbar.c:2950 e2_toolbar.c:2952 e2_toolbar.c:2955 e2_toolbar.c:3015 #: e2_toolbar.c:3017 e2_toolbar.c:3020 msgid "_Panes" msgstr "ペイン(_P)" #: e2_toolbar.c:2953 e2_toolbar.c:2956 e2_toolbar.c:3018 e2_toolbar.c:3021 msgid "Show other pane" msgstr "他のペインを表示" #: e2_toolbar.c:2958 e2_toolbar.c:3023 msgid "Show _hidden" msgstr "隠しを表示(_h)" #: e2_toolbar.c:2959 e2_toolbar.c:3024 msgid "Display hidden items in this directory" msgstr "このディレクトリの隠しアイテムを表示" #: e2_toolbar.c:2960 e2_toolbar.c:3025 msgid "Hide _hidden" msgstr "隠しを非表示(_h)" #: e2_toolbar.c:2961 e2_toolbar.c:3026 msgid "Do not display hidden items in this directory" msgstr "このディレクトリの隠しアイテムを非表示" #: e2_toolbar.c:2962 e2_toolbar.c:2964 e2_toolbar.c:3027 e2_toolbar.c:3029 msgid "Fil_ters" msgstr "フィルタ(_t)" #: e2_toolbar.c:2963 e2_toolbar.c:3028 msgid "Set rules for the items to be displayed" msgstr "表示アイテムを絞る" #: e2_toolbar.c:2965 e2_toolbar.c:3030 msgid "Set/remove rules for the items to be displayed" msgstr "絞りこみルールを設定/削除" #: e2_toolbar.c:2967 e2_toolbar.c:3032 msgid "_VFS" msgstr "_VFS" #: e2_toolbar.c:2968 e2_toolbar.c:3033 msgid "Show a virtual directory in this pane" msgstr "このペインにバーチャルディレクトリを表示" #: e2_toolbar.c:2969 e2_toolbar.c:3034 msgid "_LocalFS" msgstr "_LocalFS" #: e2_toolbar.c:2977 e2_toolbar.c:3042 msgid "_Marks" msgstr "ブックマーク(_M)" #: e2_toolbar.c:2978 e2_toolbar.c:3043 msgid "Bookmarks" msgstr "ブックマーク" #: e2_toolbar.c:2980 e2_toolbar.c:3045 msgid "Add the current directory to the top of the bookmarks list" msgstr "現在のディレクトリをブックマークリストの一番上に追加" #: e2_toolbar.c:2984 e2_toolbar.c:3049 msgid "Add the current directory to the bottom of the bookmarks list" msgstr "現在のディレクトリをブックマークリストの一番下に追加" #: e2_toolbar.c:2987 e2_toolbar.c:3052 msgid "_Edit bookmarks" msgstr "ブックマークを編集(_E)" #: e2_toolbar.c:2988 e2_toolbar.c:3053 msgid "Open the bookmarks configuration dialog" msgstr "ブックマークの設定ダイアログを開く" #: e2_toolbar.c:2991 e2_toolbar.c:2994 e2_toolbar.c:3056 e2_toolbar.c:3062 msgid "Mi_rror" msgstr "同期(_r)" #: e2_toolbar.c:2992 e2_toolbar.c:2995 e2_toolbar.c:3057 e2_toolbar.c:3063 msgid "Go to directory shown in other pane" msgstr "他のペインが表示しているディレクトリに移動" #: e2_toolbar.c:2997 e2_toolbar.c:3058 e2_toolbar.c:3066 msgid "Go to previous directory in history" msgstr "ひとつ前のディレクトリに移動" #: e2_toolbar.c:2997 e2_toolbar.c:3058 e2_toolbar.c:3066 msgid "_Back" msgstr "戻る(_B)" #: e2_toolbar.c:2998 e2_toolbar.c:3059 e2_toolbar.c:3065 msgid "Go up to parent directory" msgstr "親ディレクトリに移動" #: e2_toolbar.c:2999 e2_toolbar.c:3060 e2_toolbar.c:3064 msgid "Go to next directory in history" msgstr "次のディレクトリに移動" #: e2_toolbar.c:2999 e2_toolbar.c:3060 e2_toolbar.c:3064 msgid "_Forward" msgstr "進む(_F)" #: e2_toolbar.c:3079 msgid "bar" msgstr "バー" #: e2_tree_dialog.c:1109 msgid "Copy selected path" msgstr "選んだパスをコピー" #: e2_tree_dialog.c:1113 msgid "Collapse all paths" msgstr "全てのパスを折り畳む" #: e2_tree_dialog.c:1118 msgid "_Strict hiding" msgstr "隠しを徹底(_S)" #: e2_tree_dialog.c:1127 msgid "" "If active, hidden ancestors of visible directories will not be displayed" msgstr "" #: e2_tree_dialog.c:1388 msgid "pane 1 navigator" msgstr "ペイン1 のナビゲーター" #: e2_tree_dialog.c:1388 msgid "pane 2 navigator" msgstr "ペイン2 のナビゲーター" #: e2_tree_dialog.c:1460 msgid "Toggle display of hidden directories" msgstr "隠しディレクトリの表示をトグル" #: e2_utils.c:109 msgid "Not enough memory! Things may not work as expected" msgstr "メモリが足りません。期待する動作をできません" #: e2_utils.c:159 msgid "Cannot read USAGE help document" msgstr "使いかたのドキュメントを読めません" #: e2_utils.c:845 msgid "No item selected" msgstr "アイテムを選んでいません" #: e2_utils.c:888 msgid "No item selected in other pane" msgstr "他のペインのアイテムを選んでいません" #: e2_utils.c:978 msgid "No matching '}' found in action text" msgstr "アクションテキストに '}' はありません" #: e2_utils.c:1070 msgid "No matching '$' found in action text" msgstr "アクションテキストに '$' はありません" #: e2_utils.c:1312 #, c-format msgid "Cannot access %s, going to %s instead" msgstr "%s にアクセスできません。代わりに·%s·に行きます" #: e2_vfs_dialog.c:563 msgid "You must specify the site to open" msgstr "開くサイトをしてしてください" #: e2_vfs_dialog.c:598 #, c-format msgid "Cannot open '%s'" msgstr "'%s' を開けません" #: e2_vfs_dialog.c:685 msgid "vfs data" msgstr "VFS データ" #: e2_vfs_dialog.c:708 msgid "You may provide a short-name for use in labels and matching" msgstr "ラベルとマッチングに使用するショートネームを指定できます" #: e2_vfs_dialog.c:721 msgid "protocol" msgstr "プロトコル" #: e2_vfs_dialog.c:726 e2_vfs_dialog.c:732 e2p_find.c:2999 msgid "type" msgstr "種類" #: e2_vfs_dialog.c:761 msgid "host name" msgstr "ホスト名" #: e2_vfs_dialog.c:761 msgid "parent identifier" msgstr "親の識別子" #: e2_vfs_dialog.c:767 msgid "Domain name of the site e.g. your.computer.net" msgstr "サイトのドメイン名 (例: your.computer.net)" #: e2_vfs_dialog.c:768 msgid "Full URI for the parent space, if any" msgstr "親スペースのフル URI (あれば)" #: e2_vfs_dialog.c:788 msgid "path to archive" msgstr "書庫へのパス" #: e2_vfs_dialog.c:794 msgid "initial directory" msgstr "初期ディレクトリ" #: e2_vfs_dialog.c:861 msgid "Absolute path of directory at the site to be initially displayed" msgstr "サイトの最初に表示するディレクトリのフルパス" #: e2_vfs_dialog.c:865 msgid "Absolute path of archive in this space, including archoive's full name" msgstr "このスペースでのアーカイブのフルパス (アーカイブ名もつける)" #: e2_vfs_dialog.c:870 msgid "Path to be initially displayed, if any" msgstr "最初に表示するパス (あれば)" #: e2_vfs_dialog.c:894 msgid "user" msgstr "ユーザ" #: e2_vfs_dialog.c:905 msgid "" "Username needed to log on to the specified site, or leave blank for anonymous" msgstr "" #: e2_vfs_dialog.c:920 msgid "password" msgstr "パスワード" #: e2_vfs_dialog.c:930 msgid "" "Password needed to log on to the specified site, or leave blank for email " "address" msgstr "" #: e2_vfs_dialog.c:934 msgid "Password, if any, needed to open the archive" msgstr "書庫を開くためのパスワード (あれば)" #: e2_vfs_dialog.c:963 msgid "port" msgstr "ポート" #: e2_vfs_dialog.c:973 msgid "Port number to access the specified site, or leave blank for default" msgstr "指定したサイトにアクセスするためのポート番号 (デフォルトでは空白)" #: e2_vfs_dialog.c:1005 msgid "_Previous" msgstr "前へ(_P)" #: e2_vfs_dialog.c:1006 msgid "Show data for previously-used place with same protocol/type" msgstr "以前に同じプロトコル/種類で使われた場所のデータを表示" #: e2_vfs_dialog.c:1009 msgid "_Next" msgstr "次へ(_N)" #: e2_vfs_dialog.c:1010 msgid "Show data for next-used place with same protocol/type" msgstr "" #: e2_vfs_dialog.c:1019 e2_vfs_dialog.c:1563 msgid "_Local" msgstr "ローカル(_L)" #: e2_vfs_dialog.c:1020 e2_vfs_dialog.c:1567 msgid "Revert to native filesystem" msgstr "ネイティブファイルシステムに戻す" #: e2_vfs_dialog.c:1026 msgid "Store this data but do not open the place" msgstr "このデータを保存するが場所は開かない" #: e2_vfs_dialog.c:1030 msgid "Open this place" msgstr "この場所を開く" #: e2_vfs_dialog.c:1413 msgid "vfs history" msgstr "vfs の履歴" #: e2_vfs_dialog.c:1427 msgid "Protocol" msgstr "プロトコル" #: e2_vfs_dialog.c:1428 msgid "Host" msgstr "ホスト" #: e2_vfs_dialog.c:1431 e2p_gvfs.c:652 msgid "Password" msgstr "パスワード" #: e2_vfs_dialog.c:1432 msgid "Port" msgstr "ポート" #: e2_vfs_dialog.c:1433 msgid "Alias" msgstr "エイリアス" #: e2_vfs_dialog.c:1553 msgid "Delete the selected row" msgstr "選んだ列を削除" #: e2_vfs_dialog.c:1561 msgid "Add a row after the selected one" msgstr "現在選択しているものの後に列を追加" #: e2_vfs_dialog.c:1574 msgid "Open the place described in the selected row" msgstr "選んだ列に記載されている場所を開く" #: e2_vfs_dialog.c:1580 msgid "Save and close" msgstr "保存して閉じる" #: e2_view_dialog.c:401 #, c-format msgid "Cannot read '%s'" msgstr "%s を読めません" #: e2_view_dialog.c:417 #, c-format msgid "'%s' is a directory" msgstr "'%s' はディレクトリです" #: e2_view_dialog.c:468 #, c-format msgid "Encoding conversion command '%s' failed" msgstr "エンコーディング変換コマンド '%s' は失敗しました" #: e2_view_dialog.c:508 #, c-format msgid "Conversion from %s encoding failed: \"%s\"" msgstr "%s エンコーディングからの変換は失敗しました: \"%s\"" #: e2_view_dialog.c:947 msgid "Copy selected text" msgstr "選んだテキストをコピー" #: e2_view_dialog.c:1004 msgid "Finds" msgstr "検索" #: e2_view_dialog.c:1018 msgid "If activated, text case does matter when searching" msgstr "有効にすると、検索時に大文字と小文字を区別する" #: e2_view_dialog.c:1018 msgid "_match case" msgstr "大文字小文字を区別(_m)" #: e2_view_dialog.c:1023 msgid "If activated, matches must be surrounded by word-separator characters" msgstr "" #: e2_view_dialog.c:1023 msgid "wh_ole words" msgstr "語句全体(_o)" #: e2_view_dialog.c:1027 msgid "If activated, searching proceeds toward document start" msgstr "有効にすると、ドキュメントの最初から検索します" #: e2_view_dialog.c:1027 msgid "_backward" msgstr "後方(_b)" #: e2_view_dialog.c:1030 msgid "If activated, searching cycles from either end to the other" msgstr "有効にすると、最後から最初を循環して検索します" #: e2_view_dialog.c:1030 msgid "_loop" msgstr "繰り返し(_l)" #: e2_view_dialog.c:1518 msgid "displaying file" msgstr "ファイルを表示" #: e2_view_dialog.c:1592 msgid "_wrap" msgstr "折り返す(_w)" #: e2_view_dialog.c:1605 msgid "Show the search options bar" msgstr "検索オプションバーを表示" #: e2_view_dialog.c:1848 msgid "wrap text" msgstr "テキストを wrap する" #: e2_view_dialog.c:1849 msgid "This causes the view window to open with text-wrapping enabled" msgstr "" #: e2_view_dialog.c:1853 msgid "window width" msgstr "ウィンドウの幅" #: e2_view_dialog.c:1854 msgid "" "The view window will default to showing this many characters per line (but " "the the displayed buttons may make it wider than this)" msgstr "" #: e2_view_dialog.c:1858 msgid "window height" msgstr "ウィンドウの高さ" #: e2_view_dialog.c:1859 msgid "The view window will default to showing this many lines of text" msgstr "" #: e2_view_dialog.c:1866 msgid "custom font for viewing files" msgstr "ファイル閲覧用のカスタムフォント" #: e2_view_dialog.c:1867 msgid "This is the font used for text in each view dialog" msgstr "ビューダイアログの文字のフォント" #: e2_view_dialog.c:1870 msgid "case sensitive searches" msgstr "大文字小文字を区別して検索" #: e2_view_dialog.c:1871 msgid "" "This causes the view window search-bar to first open with case-sensitive " "searching enabled" msgstr "" #: e2_view_dialog.c:1875 msgid "show last search string" msgstr "最後に検索した語句を表示" #: e2_view_dialog.c:1876 msgid "" "This shows the last search-string in the entry field, when the view window " "search-bar is displayed" msgstr "" #: e2_view_dialog.c:1880 msgid "keep search history" msgstr "検索履歴をキープ" #: e2_view_dialog.c:1881 msgid "This causes search strings to be remembered between sessions" msgstr "" #: e2_window.c:1148 #, c-format msgid "displayed & %d concealed " msgstr "個を表示 & %d 個を非表示" #: e2_window.c:1151 #, c-format msgid "%s%d selected item(s) of %d %sin %s" msgstr "%s%d 個のアイテムを選択 (%d %sin %s)" #: e2p_acl.c:222 msgid "Whole" msgstr "全体" #: e2p_acl.c:229 e2p_acl.c:2096 msgid "Mask" msgstr "マスク" #: e2p_acl.c:1102 e2p_acl.c:1135 e2p_acl.c:3374 e2p_acl.c:3545 msgid "Directory ACL" msgstr "ディレクトリの ACL" #: e2p_acl.c:1102 e2p_acl.c:1135 e2p_acl.c:3364 e2p_acl.c:3515 msgid "General ACL" msgstr "全体の ACL" #: e2p_acl.c:1107 #, c-format msgid "Cannot apply %s '%s' for %s" msgstr "%s を適用できません·'%s'·for·%s" #: e2p_acl.c:1140 #, c-format msgid "Cannot apply %s '%s' for %s - Invalid" msgstr "%s を適用できません·'%s'·for·%s·-·無効です" #: e2p_acl.c:3351 msgid "No directory-changes have been selected" msgstr "変更するディレクトリを選んでいません" #: e2p_acl.c:3361 #, c-format msgid "The specified %s is likely to ba a problem" msgstr "指定した %s·は問題がある可能性があります" #: e2p_acl.c:3461 msgid "extended permissions" msgstr "拡張された権限" #: e2p_acl.c:3514 msgid "unable to display" msgstr "表示できません" #: e2p_acl.c:3620 msgid "General" msgstr "一般" #: e2p_acl.c:3663 msgid "Data:" msgstr "データ:" #: e2p_acl.c:3672 msgid "S_hown" msgstr "表示(_h)" #: e2p_acl.c:3680 msgid "Changes will be based only on the data shown above" msgstr "変更は上に表示されているデータにのみ基づいて行われます" #: e2p_acl.c:3681 msgid "_Varied" msgstr "変化(_V)" #: e2p_acl.c:3690 msgid "" "Changes will be based on the standard permissions of the affected item as " "modified by the data shown above" msgstr "" #: e2p_acl.c:3692 msgid "S_ystem" msgstr "システム(_y)" #: e2p_acl.c:3701 msgid "" "Changes will be based only on the standard (non-ACL) permissions of the " "affected item" msgstr "" #: e2p_acl.c:3713 msgid "_Nuke" msgstr "削除(_N)" #: e2p_acl.c:3721 msgid "Clear as much of the item's ACL as possible" msgstr "アイテムの ACL を可能な限りクリア" #: e2p_acl.c:3732 msgid "_Whole" msgstr "全体(_W)" #: e2p_acl.c:3740 msgid "" "Conveniently sets all allowed 'whole' values. For those entries, the action " "will apply to the whole of the entry, Otherwise, the action affects only the " "permissions of that entry" msgstr "" #: e2p_acl.c:3764 msgid "dirs-_general" msgstr "ディレクトリ-一般(_g)" #: e2p_acl.c:3772 msgid "" "if activated, specified changes to the \"general\" ACL will be applied to " "any affected directory" msgstr "" #: e2p_acl.c:3774 msgid "dirs-_default" msgstr "ディレクトリ-デフォルト(_d)" #: e2p_acl.c:3782 msgid "" "if activated, specified changes to the \"directories-only\" ACL will be " "applied to any affected directory" msgstr "" #: e2p_acl.c:3801 msgid "Insert a row in the ACL" msgstr "ACL に列を挿入" #: e2p_acl.c:3805 msgid "De_lete" msgstr "削除(_l)" #: e2p_acl.c:3811 msgid "Delete the selected row from the ACL" msgstr "選んだ列を ACL から削除" #: e2p_acl.c:4303 msgid "acl" msgstr "ACL" #: e2p_acl.c:4307 msgid "_Access" msgstr "アクセス(_A)" #: e2p_acl.c:4310 msgid "_Access.." msgstr "アクセス(_A)" #: e2p_acl.c:4311 e2p_acl.c:4319 msgid "Change extended permissions of selected items" msgstr "選んだアイテムの拡張権限を変更" #: e2p_acl.c:4317 msgid "Change _ACLs.." msgstr "ACL を変更(_A)" #: e2p_acl.c:4321 msgid "_Replicate" msgstr "反復(_R)" #: e2p_acl.c:4323 msgid "" "Recursively apply ACLs of selected items to matching items in the other pane" msgstr "" "選んだアイテムの·ACL·を他のペイン内で合致するアイテムに対して再帰的に適用する" #: e2p_acl.c:4363 msgid "copy_acl" msgstr "ACL をコピー" #: e2p_clone.c:82 e2p_clone.c:160 msgid "clone" msgstr "複製" #: e2p_clone.c:163 msgid "C_lone.." msgstr "複製(_l)" #: e2p_clone.c:164 msgid "Copy selected item(s), each with new name, to the current directory" msgstr "選んだアイテムを新しい名前で現在のディレクトリにコピー" #: e2p_config.c:371 #, c-format msgid "Bad configuration data for %s, not installed" msgstr "%s の間違った設定データ。インストールしません" #: e2p_config.c:504 #, c-format msgid "Incompatible format - %s" msgstr "互換性のない形式 - %s" #: e2p_config.c:677 msgid "select configuration data file" msgstr "設定データのファイルを選んでください" #: e2p_config.c:751 msgid "save configuration data file" msgstr "設定データのファイルを保存" #: e2p_config.c:810 e2p_config.c:878 msgid "select icons directory" msgstr "アイコンのディレクトリを選んでください" #: e2p_config.c:1035 msgid "Save configuration data in" msgstr "設定データを次の場所に保存" #: e2p_config.c:1058 e2p_crypt.c:2968 msgid "backup" msgstr "バックアップ" #: e2p_config.c:1097 e2p_config.c:1157 e2p_config.c:1284 e2p_config.c:1315 msgid "Se_lect" msgstr "選択(_l)" #: e2p_config.c:1098 msgid "Select the file in which to store the config data" msgstr "設定データを保存するファイルを選択" #: e2p_config.c:1101 msgid "Save current config data in the specified file" msgstr "指定したファイルに現在の設定データを保存" #: e2p_config.c:1107 msgid "export" msgstr "エクスポート" #: e2p_config.c:1126 msgid "Get configuration data from" msgstr "設定データを次のところから" #: e2p_config.c:1158 msgid "Select the config file from which to get the data" msgstr "データを取得する設定ファイルを選択" #: e2p_config.c:1162 msgid "Import config data in accord with choices below" msgstr "選んだものから設定データをインポート" #: e2p_config.c:1171 msgid "_all options" msgstr "全てのオプション(_a)" #: e2p_config.c:1173 msgid "all '_non-group' options" msgstr "全ての非グループのオプション(_n)" #: e2p_config.c:1177 msgid "all 'g_roup' options" msgstr "全てのグループのオプション(_r)" #: e2p_config.c:1179 msgid "_specific group option(s)" msgstr "特定のグループのオプション(_s)" #: e2p_config.c:1180 msgid "_groups" msgstr "グループ(_g)" #: e2p_config.c:1235 msgid "import" msgstr "インポート" #: e2p_config.c:1254 msgid "Use icons in" msgstr "次のものにアイコンを使う" #: e2p_config.c:1285 msgid "Select the directory where the icons are" msgstr "アイコンのある場所を選択" #: e2p_config.c:1289 msgid "Apply the chosen icon directory" msgstr "選んだアイコンディレクトリを適用" #: e2p_config.c:1297 msgid "Copy current icons to" msgstr "現在のアイコンを次の場所にコピー" #: e2p_config.c:1316 msgid "Select the directory where the icons will be saved" msgstr "アイコンを保存するディレクトリを選択" #: e2p_config.c:1319 msgid "C_opy" msgstr "コピー(_o)" #: e2p_config.c:1320 msgid "Copy the icons to the chosen directory" msgstr "選んだディレクトリにアイコンをコピー" #: e2p_config.c:1341 msgid "manage configuration data" msgstr "設定データを管理" #: e2p_config.c:1376 msgid "manage" msgstr "管理" #: e2p_config.c:1378 msgid "_Configure.." msgstr "設定(_C)" #: e2p_config.c:1379 msgid "Export or import configuration data" msgstr "設定データをエクスポート/インポート" #: e2p_cpbar.c:402 #, c-format msgid "" "copying %s\n" "to %s\n" "this is item %s of %s" msgstr "" "%s を\n" "%s にコピー\n" "これは %s of %s のアイテムです" #: e2p_cpbar.c:413 e2p_mvbar.c:422 #, c-format msgid "%.2f MB of %.2f MB (%.0f\\%%)" msgstr "" #: e2p_cpbar.c:673 e2p_mvbar.c:715 #, c-format msgid "Cannot put anything in %s" msgstr "%s に何も置くことができません" #: e2p_cpbar.c:695 msgid "copying" msgstr "コピー中" #: e2p_cpbar.c:720 e2p_mvbar.c:794 msgid "_Resume" msgstr "再開(_R)" #: e2p_cpbar.c:721 msgid "Resume copying after pause" msgstr "一時停止したのちコピーを再開" #: e2p_cpbar.c:726 e2p_mvbar.c:800 msgid "_Pause" msgstr "貼り付け(_P)" #: e2p_cpbar.c:727 msgid "Suspend copying, after the current item" msgstr "現在のアイテムの後でコピーをサスペンドする" #: e2p_cpbar.c:732 e2p_cpbar.c:736 msgid "Abort the copying" msgstr "コピーを中止" #: e2p_cpbar.c:871 msgid "cpbar" msgstr "cpバー" #: e2p_cpbar.c:872 msgid "cpbar_with_time" msgstr "" #: e2p_cpbar.c:886 msgid "Copy selected item(s), with displayed progress details" msgstr "進行状況を表示しながら選んだアイテムをコピー" #: e2p_cpbar.c:888 msgid "Copy with _times" msgstr "時刻付きでコピー(_t)" #: e2p_cpbar.c:890 msgid "" "Copy selected item(s), with preserved time-properties and displayed progress " "details" msgstr "選んだアイテムを時刻プロパティを保持し進行状況を表示しながらコピー" #: e2p_crypt.c:515 #, c-format msgid "No LZO compression-library for file %s" msgstr "ファイル %s 用の LZO 圧縮ライブラリがありません" #: e2p_crypt.c:550 #, c-format msgid "No ZLIB compression-library for file %s" msgstr "ファイル %s 用の ZLIB 圧縮ライブラリがありません" #: e2p_crypt.c:585 #, c-format msgid "No BZIP compression-library for file %s" msgstr "ファイル %s 用の BZIP 圧縮ライブラリがありません" #: e2p_crypt.c:590 #, c-format msgid "Unknown compression-library for file %s" msgstr "ファイル %s 用の 圧縮ライブラリが不明です" #: e2p_crypt.c:913 e2p_crypt.c:1811 #, c-format msgid "Cannot open '%s' for writing" msgstr "書き込み用に '%s' を開けません" #: e2p_crypt.c:1131 #, c-format msgid "Wrong password for %s" msgstr "%s のパスワードが違います" #: e2p_crypt.c:1247 #, c-format msgid "Error decompressing file %s" msgstr "ファイル %s の解凍エラー" #: e2p_crypt.c:1960 #, c-format msgid "" "%s does not end with \"%s\".\n" "Process this file anyway?" msgstr "" "%s は \"%s\" で終わっていません。\n" "このファイルを続けますか?" #: e2p_crypt.c:2303 #, c-format msgid "Cannot process all of %s" msgstr "%s の全てを処理できません" #: e2p_crypt.c:2785 msgid "en/decrypt file" msgstr "ファイルを暗号化/暗号解除" #: e2p_crypt.c:2801 msgid "Symbolic link" msgstr "シンボリックリンク" #: e2p_crypt.c:2817 #, c-format msgid " to %s" msgstr "" #: e2p_crypt.c:2825 msgid "encrypt" msgstr "暗号化" #: e2p_crypt.c:2828 msgid "decrypt" msgstr "暗号解除" #: e2p_crypt.c:2832 msgid "encrypted file will have same name" msgstr "暗号化されたファイルは同じ名前になります" #: e2p_crypt.c:2837 msgid "append this to encrypted file name" msgstr "暗号化されたファイルの名前にこれを追加" #: e2p_crypt.c:2857 msgid "encrypted file name will be" msgstr "暗号化されたファイルの名前は" #: e2p_crypt.c:2876 msgid "decrypted file will have same name" msgstr "暗号解除したファイルを同じ名前にします" #: e2p_crypt.c:2880 msgid "decrypted file will have embedded name" msgstr "暗号解除されたファイルは内部名を持ちます" #: e2p_crypt.c:2885 msgid "strip this from end of decrypted file name" msgstr "暗号解除されたファイルの名前の最後からこれを削除" #: e2p_crypt.c:2904 msgid "decrypted file name will be" msgstr "暗号解除されたファイルの名前は" #: e2p_crypt.c:2948 msgid "decrypted file will have stored owners, permissions and dates" msgstr "暗号解除されたファイルは所有者・権限・日付を保持します" #: e2p_crypt.c:2948 msgid "restore properties" msgstr "プロパティを復元" #: e2p_crypt.c:2953 e2p_crypt.c:2957 msgid "compress" msgstr "圧縮" #: e2p_crypt.c:2953 e2p_crypt.c:2957 msgid "compress file before encryption" msgstr "暗号化する前に圧縮" #: e2p_crypt.c:2965 msgid "store current name, permissions etc in the encrypted file" msgstr "暗号化されたファイルの名前・権限などを保存" #: e2p_crypt.c:2965 msgid "store properties" msgstr "プロパティを保存" #: e2p_crypt.c:2968 msgid "backup an existing file with the same name as the processed file" msgstr "ファイルを処理する時に同じ名前で既存のファイルをバックアップ" #: e2p_crypt.c:2970 msgid "do not remove the original file, after processing it" msgstr "処理した後も元のファイルを削除しない" #: e2p_crypt.c:2970 msgid "keep original" msgstr "元のアイテムを保持" #: e2p_crypt.c:2973 msgid "if file is a symlink, process its target" msgstr "ファイルがシンボリックリンクの時はリンク先を処理する" #: e2p_crypt.c:2973 msgid "through links" msgstr "リンクを通して" #: e2p_crypt.c:2975 msgid "recurse directories" msgstr "下位ディレクトリを含める" #: e2p_crypt.c:3219 #, c-format msgid "You do not have authority to modify %s" msgstr "%s を変更する権限がありません" #: e2p_crypt.c:3336 msgid "crypt" msgstr "暗号" #: e2p_crypt.c:3339 msgid "_En/decrypt.." msgstr "暗号化/暗号解除(_E)" #: e2p_crypt.c:3340 msgid "Encrypt or decrypt selected items" msgstr "選んだアイテムを暗号化/暗号解除" #: e2p_dircmp.c:876 msgid "compare" msgstr "比較" #: e2p_dircmp.c:879 msgid "C_ompare" msgstr "比較(_o)" #: e2p_dircmp.c:880 msgid "Select active-pane items which are duplicated in the other pane" msgstr "他のペインと重複しているアクティブペインのアイテムを選択" #: e2p_du.c:165 msgid "total size: " msgstr "全体のサイズ: " #: e2p_du.c:181 msgid "kilobytes" msgstr "キロバイト" #: e2p_du.c:194 msgid "Megabytes" msgstr "メガバイト" #: e2p_du.c:207 msgid "gigabytes" msgstr "ギガバイト" #: e2p_du.c:222 e2p_rename.c:934 e2p_rename.c:1049 msgid "in" msgstr "in" #: e2p_du.c:224 msgid "(one or more are hidden)" msgstr "(1つ以上の隠しアイテム)" #: e2p_du.c:270 msgid "_Disk usage" msgstr "ディスク使用量(_D)" #: e2p_du.c:271 msgid "Calculate the disk usage of selected item(s)" msgstr "選んだアイテムのディスク使用量を計算" #: e2p_find.c:316 msgid "all files" msgstr "全てのファイル" #: e2p_find.c:319 msgid "images" msgstr "画像" #: e2p_find.c:320 msgid "music" msgstr "音楽" #: e2p_find.c:321 msgid "videos" msgstr "動画" #: e2p_find.c:322 msgid "text files" msgstr "テキストファイル" #: e2p_find.c:323 msgid "development files" msgstr "開発用ファイル" #: e2p_find.c:324 msgid "other files" msgstr "その他のファイル" #: e2p_find.c:334 msgid "conversations" msgstr "会話" #: e2p_find.c:336 msgid "applications" msgstr "アプリケーション" #: e2p_find.c:338 msgid "emails" msgstr "Eメール" #: e2p_find.c:339 msgid "email attachments" msgstr "Email の添付物" #: e2p_find.c:2623 e2p_rename.c:1460 msgid "Search for items:" msgstr "アイテムを検索:" #: e2p_find.c:2626 e2p_rename.c:1461 msgid "any_where" msgstr "どこでも(_w)" #: e2p_find.c:2629 e2p_rename.c:1464 msgid "in _active directory" msgstr "アクティブディレクトリ(_a)" #: e2p_find.c:2637 e2p_rename.c:1466 msgid "in _other directory" msgstr "他のディレクトリ(_o)" #: e2p_find.c:2645 e2p_rename.c:1468 msgid "in _this directory" msgstr "ディレクトリを指定(_t)" #: e2p_find.c:2669 msgid "Recurse subdirectories" msgstr "下位ディレクトリを含める" #: e2p_find.c:2712 msgid "name" msgstr "名前" #: e2p_find.c:2719 msgid "Find items whose name:" msgstr "この名前のアイテムを検索:" #: e2p_find.c:2724 e2p_find.c:3177 msgid "is" msgstr "一致" #: e2p_find.c:2726 e2p_find.c:3179 msgid "is like" msgstr "類似" #: e2p_find.c:2728 e2p_find.c:3181 msgid "matches this regex" msgstr "この正規表現に合致" #: e2p_find.c:2730 e2p_find.c:3183 msgid "ignore case" msgstr "大文字小文字の違いを無視" #: e2p_find.c:2754 msgid "size" msgstr "サイズ" #: e2p_find.c:2762 msgid "Find items whose size is:" msgstr "このサイズのアイテムを検索:" #: e2p_find.c:2767 msgid "less than:" msgstr "これより小さい:" #: e2p_find.c:2769 msgid "equal to:" msgstr "これに等しい:" #: e2p_find.c:2772 msgid "more than" msgstr "これより大きい:" #: e2p_find.c:2800 msgid "mime" msgstr "mime" #: e2p_find.c:2809 msgid "Find files whose mimetype is like this:" msgstr "この mimetype のファイルを検索:" #: e2p_find.c:2835 msgid "save" msgstr "保存" #: e2p_find.c:2840 msgid "Find items most-recently saved:" msgstr "最近保存したアイテムを検索:" #: e2p_find.c:2845 e2p_find.c:2879 e2p_find.c:2913 msgid "before:" msgstr "前:" #: e2p_find.c:2846 e2p_find.c:2880 e2p_find.c:2914 msgid "on/at:" msgstr "一致:" #: e2p_find.c:2848 e2p_find.c:2882 msgid "after:" msgstr "後:" #: e2p_find.c:2869 msgid "access" msgstr "アクセス" #: e2p_find.c:2874 msgid "Find items most-recently read or executed:" msgstr "最近読んだ/実行したアイテムを検索:" #: e2p_find.c:2908 msgid "Find items whose inode was last changed:" msgstr "最後に変更した inode のアイテムを検索:" #: e2p_find.c:2916 msgid "after" msgstr "後" #: e2p_find.c:2936 msgid "permission" msgstr "権限" #: e2p_find.c:2941 msgid "Find items whose permissions:" msgstr "この権限のアイテムを検索:" #: e2p_find.c:2945 e2p_find.c:3007 msgid "are" msgstr "一致" #: e2p_find.c:2947 msgid "include" msgstr "含む" #: e2p_find.c:2949 msgid "exclude" msgstr "含まない" #: e2p_find.c:2955 msgid "owner read" msgstr "所有者の読み込み" #: e2p_find.c:2957 msgid "group read" msgstr "グループの読み込み" #: e2p_find.c:2959 msgid "anyone read" msgstr "全員の読み込み" #: e2p_find.c:2963 msgid "owner write" msgstr "所有者の書き込み" #: e2p_find.c:2965 msgid "group write" msgstr "グループの書き込み" #: e2p_find.c:2967 msgid "anyone write" msgstr "全員の書き込み" #: e2p_find.c:2971 msgid "owner execute" msgstr "所有者の実行" #: e2p_find.c:2973 msgid "group execute" msgstr "グループの実行" #: e2p_find.c:2975 msgid "anyone execute" msgstr "全員の実行" #: e2p_find.c:2979 msgid "setuid" msgstr "setuid" #: e2p_find.c:2981 msgid "setgid" msgstr "setgid" #: e2p_find.c:2983 msgid "sticky" msgstr "sticky" #: e2p_find.c:3004 msgid "Find items which" msgstr "このアイテムを検索" #: e2p_find.c:3009 msgid "are not" msgstr "" #: e2p_find.c:3016 msgid "regular" msgstr "標準" #: e2p_find.c:3023 e2p_find.c:3046 msgid "block device" msgstr "ブロックデバイス" #: e2p_find.c:3049 msgid "raw device" msgstr "rawデバイス" #: e2p_find.c:3053 msgid "fifo" msgstr "fifo" #: e2p_find.c:3077 msgid "Find items with:" msgstr "このアイテムを検索:" #: e2p_find.c:3085 msgid "any user id" msgstr "全てのユーザの id" #: e2p_find.c:3087 msgid "specific user id" msgstr "特定のユーザの id" #: e2p_find.c:3090 msgid "current user's uid" msgstr "現在のユーザの uid" #: e2p_find.c:3093 msgid "this user id" msgstr "このユーザ ID" #: e2p_find.c:3100 msgid "unregistered user" msgstr "未登録のユーザ" #: e2p_find.c:3119 msgid "any group id" msgstr "全てのグループの id" #: e2p_find.c:3121 msgid "specific group id" msgstr "特定のグループの id" #: e2p_find.c:3124 msgid "current user's gid" msgstr "現在のユーザの gid" #: e2p_find.c:3127 msgid "this group id" msgstr "このグループ ID" #: e2p_find.c:3134 msgid "unregistered group" msgstr "未登録のグループ" #: e2p_find.c:3165 msgid "content" msgstr "内容" #: e2p_find.c:3173 msgid "Using grep, find files with content that:" msgstr "grep を使ってこの内容のファイルを検索:" #: e2p_find.c:3220 msgid "Using" msgstr "使用" #: e2p_find.c:3229 msgid "find files with content that is:" msgstr "この内容のファイルを検索:" #: e2p_find.c:3514 msgid "Day" msgstr "日" #: e2p_find.c:3522 msgid "Month" msgstr "月" #: e2p_find.c:3532 msgid "Year" msgstr "年" #: e2p_find.c:3543 msgid "Hour" msgstr "時" #: e2p_find.c:3551 msgid "Minute" msgstr "分" #: e2p_find.c:3595 msgid "find files" msgstr "ファイル検索" #: e2p_find.c:3614 msgid "get advice on search options on this page" msgstr "このページの検索オプションのアドバイスを得る" #: e2p_find.c:3621 msgid "stop the current search" msgstr "現在の検索を停止" #: e2p_find.c:3627 msgid "begin searching" msgstr "検索開始" #: e2p_find.c:3630 msgid "Clea_r" msgstr "クリア(_r)" #: e2p_find.c:3630 msgid "clear all search parameters" msgstr "全ての検索パラメータをクリア" #: e2p_find.c:3664 msgid "detfind" msgstr "detfind" #: e2p_find.c:3668 msgid "Find and list items, using detailed criteria" msgstr "条件を指定してアイテムを検索する" #: e2p_for_each.c:125 msgid "repeat action" msgstr "アクションを繰り返す" #: e2p_for_each.c:126 msgid "Action to run for each selected item:" msgstr "選んだアイテムへ個別に実行するアクション:" #: e2p_for_each.c:180 msgid "foreach" msgstr "個別実行" #: e2p_for_each.c:183 msgid "For _each.." msgstr "個別実行(_e)" #: e2p_for_each.c:184 msgid "Execute an entered command on each selected item separately" msgstr "選んだアイテムに対して個別にコマンドを実行する" #: e2p_glob.c:409 msgid "Select items:" msgstr "アイテムを選択:" #: e2p_glob.c:410 msgid "selection filter" msgstr "選択フィルタ" #: e2p_glob.c:416 msgid "Named like" msgstr "名前で絞る" #: e2p_glob.c:441 msgid "_Invert" msgstr "反転(_I)" #: e2p_glob.c:448 msgid "Select items that DO NOT match the given mask" msgstr "条件に合致しないファイルを選択" #: e2p_glob.c:449 msgid "Case _sensitive" msgstr "大文字小文字を区別(_s)" #: e2p_glob.c:567 msgid "glob" msgstr "glob" #: e2p_glob.c:570 msgid "_Glob.." msgstr "_Glob.." #: e2p_glob.c:571 msgid "Select items matching a specified pattern" msgstr "指定した条件に合致するアイテムを選択" #: e2p_gvfs.c:636 msgid "login details" msgstr "ログインの詳細" #: e2p_gvfs.c:642 msgid "Domain" msgstr "ドメイン" #: e2p_gvfs.c:769 #, c-format msgid "Cannot unmount %s - %s" msgstr "%s をアンマウントできません·-·%s" #: e2p_gvfs.c:799 e2p_gvfs.c:833 #, c-format msgid "Cannot mount %s - %s" msgstr "%s·をマウントできません - %s" #: e2p_gvfs.c:1002 e2p_gvfs.c:1164 #, c-format msgid "Cannot open file %s - %s" msgstr "ファイル %s を開けません - %s" #: e2p_gvfs.c:1087 #, c-format msgid "Error reading file %s - %s" msgstr "ファイル %s の読み込みエラー - %s" #: e2p_gvfs.c:1103 e2p_gvfs.c:1204 #, c-format msgid "Cannot close file %s - %s" msgstr "ファイル %s を閉じられません - %s" #: e2p_gvfs.c:1186 #, c-format msgid "Error writing file %s - %s" msgstr "ファイル %s の書き込みエラー - %s" #: e2p_gvfs.c:1888 msgid "Enable vfs functionality using gvfs library" msgstr "gvfs ライブラリを使って VFS 機能を有効にする" #: e2p_mvbar.c:411 #, c-format msgid "" "moving %s\n" "to %s\n" "this is item %s of %s" msgstr "" "%s を\n" "%s に移動\n" "これは %s of %s のアイテムです" #: e2p_mvbar.c:769 msgid "moving" msgstr "移動中" #: e2p_mvbar.c:795 msgid "Resume moving after pause" msgstr "一時停止したのち移動を再開" #: e2p_mvbar.c:801 msgid "Suspend moving, after the current item" msgstr "現在のアイテムの後で移動をサスペンド" #: e2p_mvbar.c:806 e2p_mvbar.c:810 msgid "Abort the moving" msgstr "移動を中止" #: e2p_mvbar.c:948 msgid "mvbar" msgstr "mvbar" #: e2p_mvbar.c:952 msgid "Move selected item(s), with displayed progress details" msgstr "進行状況を表示しながら選んだアイテムを移動" #: e2p_names_clip.c:106 msgid "copy_name" msgstr "名前をコピー" #: e2p_names_clip.c:109 msgid "Copy _names" msgstr "名前をコピー(_n)" #: e2p_names_clip.c:110 msgid "Copy path or name of each selected item to the clipboard" msgstr "選んだアイテムのパスもしくは名前をクリップボードにコピー" #: e2p_pack.c:51 msgid ".tar.gz" msgstr ".tar.gz" #: e2p_pack.c:52 msgid ".tar.bz2" msgstr ".tar.bz2" #: e2p_pack.c:53 msgid ".tar" msgstr ".tar" #: e2p_pack.c:54 msgid ".zip" msgstr ".zip" #: e2p_pack.c:55 msgid ".7z" msgstr ".7z" #: e2p_pack.c:56 msgid ".rar" msgstr ".rar" #: e2p_pack.c:57 msgid ".arj" msgstr ".arj" #: e2p_pack.c:58 msgid ".zoo" msgstr ".zoo" #: e2p_pack.c:195 msgid "Filename:" msgstr "ファイル名:" #: e2p_pack.c:196 msgid "archive creation" msgstr "書庫を作成" #: e2p_pack.c:251 msgid "pack" msgstr "圧縮" #: e2p_pack.c:254 msgid "_Pack.." msgstr "圧縮(_P)" #: e2p_pack.c:255 msgid "Build an archive containing the selected item(s)" msgstr "選んだアイテムを含むアーカイブを作成" #: e2p_rename.c:630 msgid "No current name pattern is specified" msgstr "名前のパターンを指定していません" #: e2p_rename.c:643 msgid "No replacement name pattern is specified" msgstr "名前の置換パターンを指定していません" #: e2p_rename.c:651 msgid "Replacement name pattern cannot have wildcard(s)" msgstr "名前の置換パターンにはワイルドカードを使えません" #: e2p_rename.c:757 #, c-format msgid "Error in regular expression %s" msgstr "正規表現 %s でエラー" #: e2p_rename.c:819 #, c-format msgid "Cannot find anything which matches %s" msgstr "%s に合致するものがありません" #: e2p_rename.c:934 msgid "Rename" msgstr "リネーム" #: e2p_rename.c:934 e2p_rename.c:1049 msgid "to" msgstr "to" #: e2p_rename.c:1049 msgid "Renamed" msgstr "リネーム後" #: e2p_rename.c:1453 msgid "rename items" msgstr "アイテムをリネーム" #: e2p_rename.c:1480 msgid "R_ecurse subdirectories" msgstr "下位ディレクトリを含める(_e)" #: e2p_rename.c:1485 msgid "_Selected item(s)" msgstr "選んだアイテム(_S)" #: e2p_rename.c:1490 msgid "Match _exact/wildcard" msgstr "完全一致/ワイルドカードに一致(_e)" #: e2p_rename.c:1491 msgid "Match regular e_xpression" msgstr "正規表現に合致(_x)" #: e2p_rename.c:1495 msgid "Current name is like this:" msgstr "現在の名前:" #: e2p_rename.c:1516 msgid "New name is _upper case" msgstr "新しい名前は大文字(_u)" #: e2p_rename.c:1518 msgid "New name is _lower case" msgstr "新しい名前は小文字(_l)" #: e2p_rename.c:1520 msgid "_New name is like this:" msgstr "新しい名前(_N):" #: e2p_rename.c:1532 msgid "Con_firm before each rename" msgstr "リネーム前に確認(_f)" #: e2p_rename.c:1539 msgid "Get advice on rename options" msgstr "リネームオプションのアドバイスを得る" #: e2p_rename.c:1542 msgid "Stop the current search" msgstr "現在の検索を停止" #: e2p_rename.c:1547 msgid "_Rename" msgstr "リネーム(_R)" #: e2p_rename.c:1548 msgid "Begin renaming" msgstr "リネーム開始" #: e2p_rename.c:1577 msgid "renext" msgstr "一括リネーム" #: e2p_rename.c:1581 msgid "Rename item(s), using wildcards or regular-expressions" msgstr "ワイルドカードか正規表現を使ってアイテムをリネーム" #: e2p_sort_by_ext.c:86 msgid "sort_by_ext" msgstr "拡張子でソート" #: e2p_sort_by_ext.c:89 msgid "Extension _sort" msgstr "拡張子でソート(_s)" #: e2p_sort_by_ext.c:90 msgid "Sort the active file pane by filename extension" msgstr "アクティブなファイルペインをファイルの拡張子でソート" #: e2p_thumbs.c:1106 msgid "Rotate _+" msgstr "時計回りに回転" #: e2p_thumbs.c:1107 msgid "Rotate selected images quarter-turn clockwise" msgstr "選んだ画像を時計回りに4分の1回転する" #: e2p_thumbs.c:1109 msgid "Rotate _-" msgstr "反時計回りに回転" #: e2p_thumbs.c:1110 msgid "Rotate selected images quarter-turn anti-clockwise" msgstr "選んだ画像を反時計回りに4分の1回転する" #: e2p_thumbs.c:1112 msgid "_Flip" msgstr "反転(_F)" #: e2p_thumbs.c:1113 msgid "Flip selected images top-to-bottom" msgstr "選んだ画像の上下を反転する" #: e2p_thumbs.c:1123 msgid "_Unselect all" msgstr "全ての選択を解除(_U)" #: e2p_thumbs.c:1129 msgid "Replicate _selection" msgstr "選択を複製(_s)" #: e2p_thumbs.c:1137 msgid "" "If activated, items selected in this window will also be selected in the " "associated filelist" msgstr "" #: e2p_thumbs.c:1140 #, fuzzy msgid "_Clamp size" msgstr "折り畳む(_o)" #: e2p_thumbs.c:1148 msgid "" "If activated, thumbnails will be scaled up or down if needed, into the range " "32 to 128 pixels" msgstr "" #: e2p_thumbs.c:1337 msgid "Ascending" msgstr "昇順" #: e2p_thumbs.c:1345 msgid "If activated, items are displayed in ascending order" msgstr "有効にすると、アイテムを昇順で表示する" #: e2p_thumbs.c:1477 msgid "pane 1 images" msgstr "ペイン1 の画像" #: e2p_thumbs.c:1477 msgid "pane 2 images" msgstr "ペイン2 の画像" #: e2p_thumbs.c:1607 msgid "_Sort" msgstr "ソート(_S)" #: e2p_thumbs.c:1696 msgid "thumbs" msgstr "サムネール" #: e2p_thumbs.c:1699 msgid "_Thumbnail.." msgstr "サムネール(_T)" #: e2p_thumbs.c:1700 msgid "Display thumbnails of image files in the active pane" msgstr "アクティブペインに画像ファイルのサムネールを表示" #: e2p_times.c:212 #, c-format msgid "Cannot change times of %s" msgstr "%s の時刻を変更できません" #: e2p_times.c:383 #, c-format msgid "Cannot get current times of %s" msgstr "%s の現在の時刻を取得できません" #: e2p_times.c:442 #, c-format msgid "Cannot interpret date-time %s" msgstr "日付/時刻 %s を解釈できません" #: e2p_times.c:534 msgid "" "Changing 'ctime' requires temporary changes to the system clock. That is " "normally unwise, as typically, other things rely on system time. Click 'ok' " "to proceed." msgstr "" #: e2p_times.c:689 msgid "times" msgstr "時刻" #: e2p_times.c:712 msgid "Current values" msgstr "現在の値" #: e2p_times.c:713 msgid "New date" msgstr "新しい日付" #: e2p_times.c:714 msgid "New time" msgstr "新しい時刻" #: e2p_times.c:717 msgid "Content Modified" msgstr "内容を変更しました" #: e2p_times.c:718 msgid "Inode Changed" msgstr "Inode 変更" #: e2p_times.c:898 #, c-format msgid "You do not have authority to change time(s) for %s" msgstr "%s の時刻を変更する権限がありません" #: e2p_times.c:1004 msgid "timeset" msgstr "時刻セット" #: e2p_times.c:1007 msgid "Change _times.." msgstr "時刻を変更(_t)" #: e2p_times.c:1008 msgid "Change any of the time properties of selected items" msgstr "選んだアイテムの時刻プロパティを変更" #: e2p_unpack.c:327 msgid "What do you want to do with the unpacked item(s) ?" msgstr "解凍したアイテムに何を行いますか?" #: e2p_unpack.c:331 msgid "Re_pack" msgstr "再圧縮(_p)" #: e2p_unpack.c:333 msgid "_Retain" msgstr "保持(_R)" #: e2p_unpack.c:418 msgid "Selected item is not a supported archive" msgstr "選択したアイテムはサポート外の書庫です" #: e2p_unpack.c:432 msgid "Recursive unpack is not supported" msgstr "再帰的な解凍はサポートしていません" #: e2p_unpack.c:521 msgid "unpack_with_plugin" msgstr "プラグインで解凍" #: e2p_unpack.c:527 msgid "" "Unpack archive (tar, tar.gz, tar.bz2, zip, 7z, rar, arj, zoo) into a " "temporary directory" msgstr "" "アーカイブ (tar, tar.gz, tar.bz2, zip, 7z, rar, arj, zoo) を一時ディレクトリ" "に解凍" #: e2p_upgrade.c:42 #, c-format msgid "" "Configuration arrangements for this version %s of %s are considerably " "different from those of old versions. To reliably ensure access to the " "program's current features, it is best to start with fresh settings.\n" "If you proceed, the superseded configuration files in\n" " %s will have '.save' appended to their names.\n" "Feel free to delete them." msgstr "" #: e2p_upgrade.c:50 #, c-format msgid "" "Several default configuration settings of this version %s of %s are " "different from those of recent versions (see changelog).\n" "If you click OK, those settings will be updated where possible.\n" "Or else you can Cancel, and later, via the configuration dialog, " "manuallychange individual settings, or change all settings to current " "defaults." msgstr "" #: e2p_upgrade.c:132 msgid "update information" msgstr "更新情報" #: e2p_upgrade.c:806 msgid "" msgstr "<リスト>" #: e2p_view.c:80 msgid "view_with_plugin" msgstr "プラグインで見る" #: e2p_view.c:84 #, c-format msgid "Open the first selected item with the %s text-file viewer" msgstr "最初に選んだアイテムを %s テキストファイルビューアで開く" #~ msgid "_file selection" #~ msgstr "ファイル選択(_f)" #~ msgid "_stock icons" #~ msgstr "ストックアイコン(_s)" #~ msgid "_Mounted directory" #~ msgstr "マウント済ディレクトリ(_M)" #~ msgid "_Remote directory" #~ msgstr "リモートディレクトリ(_R)" #~ msgid "_Archive" #~ msgstr "アーカイブ(_A)" #~ msgid "No password provided" #~ msgstr "パスワードが指定されていません" #~ msgid "match unknown users" #~ msgstr "不明なユーザに合致" #~ msgid "this gid" #~ msgstr "この gid" #~ msgid "match unknown groups" #~ msgstr "不明なグループに合致" #~ msgid "this" #~ msgstr "一致" #~ msgid "like this" #~ msgstr "類似" #~ msgid "like this regex" #~ msgstr "正規表現" #, fuzzy #~ msgid "choose query" #~ msgstr "言語を選択" #, fuzzy #~ msgid "_Search for" #~ msgstr "アイテムを検索" #, fuzzy #~ msgid "Search for items whose _mimetype is any of:" #~ msgstr "この mimetype のファイルを検索:" #, fuzzy #~ msgid "Search for items using this rdf query:" #~ msgstr "アイテムを検索:" #, fuzzy #~ msgid "Open query-selection dialog" #~ msgstr "色選択ダイアログを表示" #, fuzzy #~ msgid "C_lear" #~ msgstr "クリア" #, fuzzy #~ msgid "Clear the current query" #~ msgstr "現在の検索を停止" #, fuzzy #~ msgid "track" #~ msgstr "圧縮" #, fuzzy #~ msgid "_Track.." #~ msgstr "圧縮(_P)" #, fuzzy #~ msgid "Find items in the tracker database" #~ msgstr "アクティブペインのアイテムを検索" emelfm2-0.4.1/plugins/0000700000175000017500000000000011015120161013452 5ustar cairocairoemelfm2-0.4.1/plugins/e2p_for_each.c0000600000175000017500000001451310657051426016162 0ustar cairocairo/* $Id: e2p_for_each.c 590 2007-08-10 11:59:18Z tpgww $ Copyright (C) 2003-2007 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file plugins/e2p_glob.c @brief plugin for running a command on each selecting item, individually */ #include "emelfm2.h" #include #include "e2_plugins.h" #include "e2_task.h" #include "e2_dialog.h" #include "e2_filelist.h" //hackish mechanism to make each command non-static //FIXME find a way to do this which eliminates _any_ chance of a race static GList *each_command_list = NULL; //mutex for added security with static list of commands static GStaticRecMutex eachcmd_mutex; /** @brief run a command on each selected item This is called from a "run immediately" task thread @param qed pointer to action data for the task @return TRUE if task completed successfully, else FALSE */ static gboolean _e2p_foreachQ (E2_ActionTaskData *qed) { g_static_rec_mutex_lock (&eachcmd_mutex); if (each_command_list == NULL) { g_static_rec_mutex_unlock (&eachcmd_mutex); return FALSE; //possible race-condition ?? } //get the command to run from the back of the commands list //(possible race if another foreach was initiated while this thread was being established !) GList *this = g_list_last (each_command_list); each_command_list = g_list_remove_link (each_command_list, this); g_static_rec_mutex_unlock (&eachcmd_mutex); gint res; guint count; gboolean retval = TRUE; gchar *this_cmd, *utf; gchar *each_cmd = (gchar *) this->data; gchar *utfd = F_FILENAME_FROM_LOCALE (qed->currdir); GString *fullpath = g_string_sized_new (PATH_MAX+NAME_MAX); GPtrArray *names = qed->names; E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata; e2_filelist_disable_refresh (); for (count = 0; count < names->len; count++, iterator++) { utf = F_FILENAME_FROM_LOCALE ((*iterator)->filename); g_string_printf (fullpath, "%s%s", utfd, utf); //separator comes with dir this_cmd = e2_utils_replace_name (each_cmd, fullpath->str); if (this_cmd != NULL) { gdk_threads_enter (); #ifdef E2_COMMANDQ res = e2_command_run (this_cmd, E2_COMMAND_RANGE_DEFAULT, TRUE); #else res = e2_command_run (this_cmd, E2_COMMAND_RANGE_DEFAULT); #endif gdk_threads_leave (); g_free (this_cmd); } else res = 1; //force error, quit F_FREE (utf); if (res != 0) { //FIXME advise user retval = FALSE; break; } } e2_filelist_enable_refresh (); g_free (each_cmd); g_list_free (this); g_string_free (fullpath, TRUE); return retval; } /** @brief initate repeated command from action data or user choice This action takes its command from action runtime data, or if the latter is NULL, will ask for the command here @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if action was initiated, else FALSE */ static gboolean _e2p_foreach (gpointer from, E2_ActionRuntime *art) { DialogButtons result; gchar *each_command; gpointer saved_command = NULL; if (art->data == NULL) { //we don't yet know what to run for each item result = e2_dialog_line_input (_("repeat action"), _("Action to run for each selected item:"), "", 0, FALSE, &each_command); //a blank entry will not return OK } else { //the action has been initiated from the commandline each_command = g_strdup ((gchar *)art->data); //but we do NOT want the command to be considered as names of items ! saved_command = art->data; art->data = NULL; result = OK; } if (result == OK) { //FIXME support macros other than with english letters if (strstr (each_command, "%f") == NULL && strstr (each_command, "%p") == NULL) { gchar *freeme = each_command; each_command = g_strconcat (each_command, " %f", NULL); g_free (freeme); } g_static_rec_mutex_lock (&eachcmd_mutex); each_command_list = g_list_append (each_command_list, each_command); g_static_rec_mutex_unlock (&eachcmd_mutex); gboolean success = e2_task_do_task (E2_TASK_FOREACH, art, from, _e2p_foreachQ, NULL); if (saved_command != NULL) art->data = saved_command; if (!success) { g_free (each_command); g_static_rec_mutex_lock (&eachcmd_mutex); each_command_list = g_list_delete_link (each_command_list, g_list_last (each_command_list)); g_static_rec_mutex_unlock (&eachcmd_mutex); } return success; } return FALSE; } //aname must be confined to this module static gchar *aname; /** @brief plugin initialization function, called by main program @param p ptr to plugin data struct @return TRUE if the initialization succeeds, else FALSE */ gboolean init_plugin (Plugin *p) { #define ANAME "for_each" aname = _("foreach"); p->signature = ANAME VERSION; p->menu_name = _("For _each.."); p->description = _("Execute an entered command on each selected item separately"); p->icon = "plugin_"ANAME"_"E2IP".png"; //use icon file pathname if appropriate if (p->action == NULL) { //no need to free this gchar *action_name = g_strconcat (_A(5),".",aname,NULL); p->action = e2_plugins_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2p_foreach, NULL, TRUE, 0, NULL); //setup mutex to protect threaded access to foreach functionality g_static_rec_mutex_init (&eachcmd_mutex); return TRUE; } return FALSE; } /** @brief cleanup transient things for this plugin @param p pointer to data struct for the plugin @return TRUE if all cleanups were completed */ gboolean clean_plugin (Plugin *p) { gchar *action_name = g_strconcat (_A(5),".",aname,NULL); gboolean ret = e2_plugins_action_unregister (action_name); g_free (action_name); if (ret && each_command_list != NULL) { g_static_rec_mutex_lock (&eachcmd_mutex); e2_list_free_with_data (&each_command_list); g_static_rec_mutex_unlock (&eachcmd_mutex); } return ret; } emelfm2-0.4.1/plugins/e2p_clone.c0000600000175000017500000001322310655523144015510 0ustar cairocairo/* $Id: e2p_clone.c 574 2007-08-06 04:41:08Z tpgww $ Copyright (C) 2003-2007 tooar Portions copyright (C) 1999 Michael Clark. This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file plugins/e2p_clone.c @brief plugin for copying selected items, each with a new name, to their orignal location */ #include "emelfm2.h" //#include #include "e2_plugins.h" #include "e2_dialog.h" #include "e2_task.h" #include "e2_filelist.h" static gboolean _e2p_cloneQ (E2_ActionTaskData *qed); /** @brief copy selected item(s) each with a new name @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if task completed successfully, else FALSE */ static gboolean _e2p_clone (gpointer from, E2_ActionRuntime *art) { return (e2_task_enqueue_task (E2_TASK_CLONE, art, from, _e2p_cloneQ, e2_task_refresh_lists)); } static gboolean _e2p_cloneQ (E2_ActionTaskData *qed) { GPtrArray *names = qed->names; gchar *curr_local = qed->currdir; GString *prompt = g_string_sized_new (NAME_MAX+64); #ifdef E2_VFS VPATH sdata; VPATH ddata; sdata.spacedata = qed->currspace; ddata.spacedata = qed->currspace; #endif GString *src = g_string_sized_new (PATH_MAX+NAME_MAX); GString *dest = g_string_sized_new (PATH_MAX+NAME_MAX); gchar *converted, *new_name; //curr_view->dir is utf-8 gboolean check = e2_option_bool_get ("confirm-overwrite"); guint count; E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata; //setup for tailoring over-write dialog gboolean multisrc = (check) ? names->len > 1 : FALSE; OW_ButtonFlags extras = (multisrc) ? NOALL : NONE; e2_filelist_disable_refresh (); e2_task_advise (); for (count=0; count < names->len; count++, iterator++) { //".." entries filtered when names compiled converted = F_FILENAME_FROM_LOCALE ((*iterator)->filename); g_string_printf (prompt, "%s: %s", _("Enter new name for"), converted); DialogButtons result = 0; e2_filelist_enable_refresh (); //allow updates while we wait gdk_threads_enter (); DialogButtons result2 = e2_dialog_line_input (_("clone"), prompt->str, converted, extras, FALSE, &new_name); gdk_threads_leave (); F_FREE (converted); e2_filelist_disable_refresh (); if (result2 == OK) { g_string_printf (src, "%s%s", curr_local, (*iterator)->filename); converted = F_FILENAME_TO_LOCALE (new_name); g_string_printf (dest, "%s%s", curr_local, converted); g_free (new_name); F_FREE (converted); if (g_str_equal (src->str, dest->str)) continue; #ifdef E2_VFS ddata.localpath = dest->str; if (check && e2_fs_access2 (&ddata E2_ERR_NONE()) == 0) #else if (check && e2_fs_access2 (dest->str E2_ERR_NONE()) == 0) #endif { //item with the new name already exists e2_filelist_enable_refresh (); //allow updates while we wait gdk_threads_enter (); *qed->status = E2_TASK_PAUSED; result = e2_dialog_ow_check (NULL, dest->str, extras); *qed->status = E2_TASK_RUNNING; gdk_threads_leave (); e2_filelist_disable_refresh (); if (result == OK) { #ifdef E2_VFS sdata.localpath = src->str; e2_task_backend_copy (&sdata, &ddata, E2_FTM_NORMAL); #else e2_task_backend_copy (src->str, dest->str, E2_FTM_NORMAL); #endif } /* else if (result == YES_TO_ALL) { do something smart about multiple-renames } */ else if (result == NO_TO_ALL) break; } else { #ifdef E2_VFS sdata.localpath = src->str; e2_task_backend_copy (&sdata, &ddata, E2_FTM_NORMAL); #else e2_task_backend_copy (src->str, dest->str, E2_FTM_NORMAL); #endif } } else if (result2 == NO_TO_ALL) break; } e2_window_clear_status_message (); e2_filelist_request_refresh (curr_view->dir, TRUE); //other pane refreshed normally g_string_free (prompt,TRUE); g_string_free (src,TRUE); g_string_free (dest,TRUE); e2_filelist_enable_refresh (); return TRUE; } //aname must be confined to this module static gchar *aname; /** @brief plugin initialization function, called by main program @param p ptr to plugin data struct @return TRUE if the initialization succeeds, else FALSE */ gboolean init_plugin (Plugin *p) { #define ANAME "clone" aname = _("clone"); p->signature = ANAME VERSION; p->menu_name = _("C_lone.."); p->description = _("Copy selected item(s), each with new name, to the current directory"); p->icon = "plugin_"ANAME"_"E2IP".png"; //use icon file pathname if appropriate // p->icon = ""; add icon file pathname if appropriate if (p->action == NULL) { //no need to free this gchar *action_name = g_strconcat (_A(5),".",aname,NULL); p->action = e2_plugins_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2p_clone, NULL, FALSE, 0, NULL); return TRUE; } return FALSE; } /** @brief cleanup transient things for this plugin @param p pointer to data struct for the plugin @return TRUE if all cleanups were completed */ gboolean clean_plugin (Plugin *p) { gchar *action_name = g_strconcat (_A(5),".",aname,NULL); gboolean ret = e2_plugins_action_unregister (action_name); g_free (action_name); return ret; } emelfm2-0.4.1/plugins/e2p_unpack.c0000600000175000017500000004343410776374631015711 0ustar cairocairo/* $Id: e2p_unpack.c 853 2008-04-07 10:38:17Z tpgww $ Copyright (C) 2003-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file plugins/e2p_unpack.c @brief plugin for interfacting with several archive managers, to unpack selected item(s) */ #include "emelfm2.h" #include #include "e2_plugins.h" #include "e2_dialog.h" #include "e2_task.h" #include "e2_filelist.h" //same enum as in pack plugin enum { TAR_GZ, TAR_BZ2, TAR, ZIP, Z7Z, RAR, ARJ, ZOO, MAXTYPES }; typedef struct _E2P_Unpackdata { gchar *package; //absolute path of source archive, utf8 string gchar *workdir; //absolute path of dir used to unpack this archive, //== unpack_tmp or a tmp variant of that, utf-8, no trailer gchar *last_dir; //dir to go back to after starting a repack glong thispid; //id of process which is re-packing an archive // guint chdir_id; //id of timer which checks for whether to ask user what to do guint pack_id; //id of timer which checks for repack completion E2_CDType cd_completed; //flag set when cd to temp dir is completed gchar *command; //the upack command to be run gboolean departing; //TRUE when the temp dir is waiting to be processed after //the user selects from a what-to-do dialog //also used to block reentrant use of the hook function } E2P_Unpackdata; static gboolean _e2p_unpack_delete_dir (E2P_Unpackdata *data); static gboolean _e2p_unpack_change_dir_hook (gchar *path, E2P_Unpackdata *data); static gchar *unpack_tmp; //absolute path of 'base working' directory (no trailer), utf-8 string /** @brief cleanup plugin data This is executed with BGL open or closed @param data pointer to plugin data @return */ static void _e2p_unpack_cleanup (E2P_Unpackdata *data) { g_free (data->package); g_free (data->workdir); g_free (data->command); if (data->last_dir != NULL) g_free (data->last_dir); DEALLOCATE (E2P_Unpackdata, data); } /** @brief timer callback to resume idle checking @param data pointer to unpack data struct @return FALSE always */ static gboolean _e2p_unpack_pause (E2P_Unpackdata *data) { data->pack_id = g_idle_add_full (G_PRIORITY_LOW, (GSourceFunc)_e2p_unpack_delete_dir, data, NULL); return FALSE; } /** @brief idle callback to check whether temp-dir deletion can proceed safely We do this check at an idle time to reduce risk of conflict, but interpose a timer between idle callbacks to reduce their frequency @param data pointer to unpack data struct @return FALSE always */ static gboolean _e2p_unpack_delete_dir (E2P_Unpackdata *data) { LISTS_LOCK //conservative approach - no deletion while any chance of the temp-dir being used if (curr_view->listcontrols.cd_working || curr_view->listcontrols.refresh_working || ( #ifdef E2_VFS curr_view->spacedata == NULL && //local temp dirs #endif g_str_has_prefix (curr_view->dir, data->workdir))) { LISTS_UNLOCK //wait before checking for an idle again data->pack_id = g_timeout_add (500, (GSourceFunc)_e2p_unpack_pause, data); return FALSE; } if (other_view->listcontrols.cd_working || other_view->listcontrols.refresh_working || ( #ifdef E2_VFS other_view->spacedata == NULL && //local temp dirs #endif g_str_has_prefix (other_view->dir, data->workdir))) { LISTS_UNLOCK //wait before checking for an idle again data->pack_id = g_timeout_add (500, (GSourceFunc)_e2p_unpack_pause, data); return FALSE; } LISTS_UNLOCK //kill the idle now // if (data->pack_id > 0) // g_source_remove (data->pack_id); // else // while (g_source_remove_by_user_data (data)) {} //now we're ready for cleanup e2_filelist_disable_refresh (); gchar *local = F_FILENAME_TO_LOCALE (data->workdir); #ifdef E2_VFS VPATH ddata = { local, NULL }; //local unpacking only if (e2_fs_access2 (&ddata E2_ERR_NONE()) == 0) e2_task_backend_delete (&ddata); #else if (e2_fs_access2 (local E2_ERR_NONE()) == 0) e2_task_backend_delete (local); #endif e2_filelist_enable_refresh (); F_FREE (local); _e2p_unpack_cleanup (data); //FIXME different refresh approach with E2_ASYNC //in case a pane shows parent of temp dir #ifdef E2_FAM e2_filelist_request_refresh (curr_view->dir, FALSE); e2_filelist_request_refresh (other_view->dir, TRUE); #else e2_filelist_check_dirty (GINT_TO_POINTER (1)); #endif return FALSE; } /** @brief delete the temp dir This is executed with BGL open or closed @param data pointer to plugin data @return */ static void _e2p_unpack_clear (E2P_Unpackdata *data) { //ensure BGL is open and manage any race with refresh function data->pack_id = g_idle_add_full (G_PRIORITY_LOW, (GSourceFunc)_e2p_unpack_delete_dir, data, NULL); } /** @brief timer callback to periodically check whether a repack is completed @param data pointer to plugin data struct @return TRUE if the repack process is still running */ static gboolean _e2p_unpack_clean_dir (E2P_Unpackdata *data) { if (e2_command_find_process ((guint)data->thispid)) return TRUE; //wait some more //now we're ready for cleanup // g_source_remove (data->pack_id); // if (_e2p_unpack_delete_dir (data)) //keep trying until the deletion is done g_idle_add_full (G_PRIORITY_LOW, (GSourceFunc)_e2p_unpack_delete_dir, data, NULL); return FALSE; } /** @brief repack the temp dir This is executed inside a callback with BGL closed @param data pointer to plugin data @return */ static void _e2p_unpack_repack (E2P_Unpackdata *data) { #ifdef E2_VFSTMP if (curr_view->spacedata != NULL) //CHECKME fuse ok? return; #endif guint index; gchar *package = data->package; static gchar *cmd_str [MAXTYPES] = { //these command strings are in same order as enum ">tar cf - . | gzip - > \"%s\"", ">tar cf - . | bzip2 - > \"%s\"", "tar cf \"%s\" .", "zip -r \"%s\" .", "7za a -t7z \"%s\" .", //CHECKME "rar u -ol \"%s\" .", "arj u -al \"%s\" .", "zoo ahP \"%s\" ." //FIXME replace, not add ? }; /* compress ANSI files: 7za a -tzip archive.zip file1 file2 ... fileN compress ANSI dir: 7za a -tzip archive.zip dirnametocompress\ compress UNICODE files: 7za a -t7z archive.7z file1 file2 ... fileN compress UNICODE dir: 7za a -t7z archive.7z dirnametocompress\ */ //command strings are all designed to be executed from //the temp dir, on all its contents //CHECKME package is assumed to have ascii-coded extension - ok ? //FIXME a better way to distinguish filetypes eg magic number if ((strstr (package, ".tar.gz") != NULL) || (strstr (package, ".tgz") != NULL)) index = TAR_GZ; else if (strstr (package, ".tar.bz2") != NULL) index = TAR_BZ2; else if (strstr (package, ".tar") != NULL) index = TAR; else if (strstr (package, ".zip") != NULL) index = ZIP; else if (strstr (package, ".7z") != NULL) index = Z7Z; else if (strstr (package, ".rar") != NULL) index = RAR; else if (strstr (package, ".arj") != NULL) index = ARJ; else //if (strstr (package, ".zoo") != NULL) index = ZOO; g_free (data->command); data->command = g_strdup_printf (cmd_str [index], package); gint res = e2_command_run_at (data->command, data->workdir, E2_COMMAND_RANGE_DEFAULT); //FIXME race here if something else is run at a bad time, so find the pid //with matching command string instead if (res == 0) { E2_TaskRuntime *td = e2_task_find_last_running_child (TRUE); data->thispid = (td != NULL) ? td->pid : 0; } else data->thispid = 0; //CHECKME refreshing may move CWD away from the temp dir while the repack is underway //periodically check whether re-build finished, when so, cleanup the temp dir //CHECKME make this timer cancellable at session end data->pack_id = g_timeout_add (500, (GSourceFunc) _e2p_unpack_clean_dir, data); } /** @brief callback for "what-to-do" dialog's "response" signal @param dialog UNUSED the dialog where the response was triggered @param response the response for the clicked button @param rt pointer to data for dialog @return */ static void _e2p_unpack_response_decode_cb (GtkDialog *dialog, gint response, E2P_Unpackdata *data) { gtk_widget_destroy (GTK_WIDGET(dialog)); //do this outside of current hook func, as we need to check both panes and data e2_hook_unregister (&app.pane1.hook_change_dir, _e2p_unpack_change_dir_hook, data, TRUE); e2_hook_unregister (&app.pane2.hook_change_dir, _e2p_unpack_change_dir_hook, data, TRUE); switch (response) { case E2_RESPONSE_USER1: //repack the temp dir _e2p_unpack_repack (data); break; case E2_RESPONSE_USER2: //keep the unpacked archive _e2p_unpack_cleanup (data); // case GTK_RESPONSE_CANCEL: break; //case E2_RESPONSE_REMOVE: default: //this will pick up GTK_RESPONSE_NONE or GTK_RESPONSE_DELETE_EVENT _e2p_unpack_clear (data); break; } } /** @brief hook function for cd in either pane (app.paneX.hook_change_dir) This is initiated from a cd thread, and with BGL open/off @param path UNUSED path of an opened directory, utf-8 string @param data pointer to operation data struct @return TRUE always so hook remains active */ static gboolean _e2p_unpack_change_dir_hook (gchar *path, E2P_Unpackdata *data) { if (data->departing) return TRUE; //a callback has begun, ignore this other cd data->departing = TRUE; //temp block on nested checking /* this hookfunc is called toward the end of a cd process, there's no need to check for various "busy" flags before proceeding the first cd will be into the temp dir, so path will be that dir with trailing separator */ if ( #ifdef E2_VFSTMP //FIXME dirs when not mounted local curr_view->spacedata == NULL //local temp dirs other_view->spacedata == NULL //local temp dirs #else ( g_str_has_prefix (curr_view->dir, data->workdir) || g_str_has_prefix (other_view->dir, data->workdir)) #endif ) { data->departing = FALSE; //unblock return TRUE; } //user changed dir, now neither pane is for anywhere in unpack-dir tree printd (DEBUG, "ready to cleanup unpack dir"); //ask user what to do with the unpacked items GtkWidget *dialog = e2_dialog_create (GTK_STOCK_DIALOG_QUESTION, _("What do you want to do with the unpacked item(s) ?"), NULL, _e2p_unpack_response_decode_cb, data); e2_dialog_add_undefined_button (dialog, GTK_STOCK_CLEAR, _("Re_pack"), E2_RESPONSE_USER1); e2_dialog_add_undefined_button (dialog, GTK_STOCK_APPLY, _("_Retain"), E2_RESPONSE_USER2); GtkWidget *button = e2_dialog_add_undefined_button (dialog, GTK_STOCK_DELETE, _("_Delete"), E2_RESPONSE_REMOVE); e2_dialog_setup (dialog, app.main_window); gdk_threads_enter (); e2_dialog_run (dialog, NULL, 0); gtk_widget_grab_focus (button); gtk_window_present (GTK_WINDOW (dialog)); gdk_threads_leave (); return TRUE; //no hook cleanup here //done in response cb, as we need to check both panes and data } /** @brief unpack plugin action : unpack a supported archive into a temp dir @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if action completed successfully, else FALSE */ static gboolean _e2p_unpack (gpointer from, E2_ActionRuntime *art) { gchar *package, *workdir, *converted; FileInfo *info; guint index; //these unpack-command strings are in same order as enum //all are executed from the temp dir (as some can only do that) //all are ascii (no conversion to utf8 before execution) static gchar *cmd_str [MAXTYPES] = { //FIXME -z option to tar is a GNU extension //FIXME -j option ditto //or -C \"%s\" for tar's #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(darwin) "tar -xpzf \"%s\"", "tar -xpjf \"%s\"", "tar -xpf \"%s\"", #else //note: an --atime-preserve in these tar commands prevents the //file-monitoring process from noticing anything in the temp dir "tar --overwrite -xpzf \"%s\"", "tar --overwrite -xpjf \"%s\"", "tar --overwrite -xpf \"%s\"", #endif //.deb command (for what version ?) provided by Martin Zelaia //"mkdir ./DEBIAN ./CONTENTS;>ar -x \"%s\" | tar -xfz control.tar.gz -C ./DEBIAN | tar -xfz data.tar.gz -C ./CONTENTS; rm control.tar.gz data.tar.gz;cp ./DEBIAN/control ./INFO;rm ./debian-binary"; "unzip -o \"%s\"", //or "unzip -o -d \"%s\" \"%s\"" /* decompress ANSI: 7za x archive.zip -odirname -aoa decompress UNICODE: 7za x archive.7z -odirname -aoa */ "7za x \"%s\" -aoa", //or ?? "rar x -o+ \"%s\"", //rar will only extract to current dir "arj x -y \"%s\"", //or "arj x -y \"%s\" \"%s\"", //NOTE swapped order of archive & path "zoo xO \"%s\"" //zoo will only extract to current dir }; info = e2_fileview_get_selected_first_local (curr_view, FALSE); if (info == NULL) return FALSE; //nothing selected if ((strstr (info->filename, ".tar.gz") != NULL) || (strstr (info->filename, ".tgz") != NULL)) index = TAR_GZ; else if (strstr (info->filename, ".tar.bz2") != NULL) index = TAR_BZ2; else if (strstr (info->filename, ".tar") != NULL) index = TAR; //.deb command (for what version ?) provided by Martin Zelaia // else if (strstr(info->filename, ".deb") != NULL) // index = ? else if (strstr (info->filename, ".zip") != NULL) index = ZIP; else if (strstr (info->filename, ".7z") != NULL) index = Z7Z; else if (strstr(info->filename, ".rar") != NULL) index = RAR; else if (strstr(info->filename, ".arj") != NULL) index = ARJ; else if (strstr(info->filename, ".zoo") != NULL) index = ZOO; else { e2_output_print_error (_("Selected item is not a supported archive"), FALSE); return FALSE; } //CHECKME recursive unpacking may be ok now, with different temp dirs #ifdef E2_VFSTMP //FIXME handle space-change too if (curr_view->spacedata == ? || strstr (curr_view->dir, unpack_tmp) != NULL) #else if (strstr (curr_view->dir, unpack_tmp) != NULL) #endif { //the temp dir may be deleted when the plugin function is //completed, so it would be very bad to open an archive //inside the temp dir e2_output_print_error (_("Recursive unpack is not supported"), FALSE); return FALSE; } converted = F_FILENAME_TO_LOCALE (unpack_tmp); workdir = e2_utils_get_tempname (converted); #ifdef E2_VFS VPATH ddata = { workdir, NULL }; //local unpacking only #endif F_FREE (converted); //(re)make it #ifdef E2_VFS if (e2_fs_recurse_mkdir (&ddata, 0777 E2_ERR_NONE())) #else if (e2_fs_recurse_mkdir (workdir, 0777 E2_ERR_NONE())) #endif { converted = F_DISPLAYNAME_FROM_LOCALE (workdir); gchar *msg = g_strdup_printf ("Could not create working directory '%s'", converted); e2_output_print_error (msg, TRUE); F_FREE (converted); g_free (workdir); return FALSE; } E2P_Unpackdata *data = ALLOCATE0 (E2P_Unpackdata); CHECKALLOCATEDWARN (data, return FALSE;) data->workdir = D_FILENAME_FROM_LOCALE (workdir); g_free (workdir); converted = F_FILENAME_FROM_LOCALE (info->filename); #ifdef E2_VFSTMP //FIXME dir when not mounted local #else package = g_strdup_printf ("%s%s", curr_view->dir, converted); //dir has trailing / #endif data->package = package; F_FREE (converted); //no conversion of command encoding data->command = g_strdup_printf (cmd_str [index], package); e2_window_set_cursor (GDK_WATCH); //unpack the archive into the temp directory gint result = e2_command_run_at (data->command, data->workdir, E2_COMMAND_RANGE_DEFAULT); e2_window_set_cursor (GDK_LEFT_PTR); if (result != 0) { workdir = F_FILENAME_TO_LOCALE (data->workdir); #ifdef E2_VFS ddata.localpath = workdir; e2_task_backend_delete (&ddata); #else e2_task_backend_delete (workdir); #endif F_FREE (workdir); _e2p_unpack_cleanup (data); return FALSE; } //now go see it e2_pane_change_dir (NULL, data->workdir); //setup to clean when temp dir not open anymore e2_hook_register (&app.pane1.hook_change_dir, _e2p_unpack_change_dir_hook, data); e2_hook_register (&app.pane2.hook_change_dir, _e2p_unpack_change_dir_hook, data); return TRUE; } //aname must be confined to this module static gchar *aname; /** @brief plugin initialization function, called by main program @param p ptr to plugin data struct @return TRUE if the initialization succeeds, else FALSE */ gboolean init_plugin (Plugin *p) { //once-only, setup the working dir name unpack_tmp = e2_utils_get_temp_path ("-unpack"); //strip the trailing ".tmp~0" as we will append a suffix like that for each unpack gchar *s = strrchr (unpack_tmp, '.'); *s = '\0'; #define ANAME "unpack" #ifdef E2_VFS aname = _("unpack_with_plugin"); #else aname = _A(100); #endif p->signature = ANAME VERSION; p->menu_name = _("_Unpack"); p->description = _("Unpack archive (tar, tar.gz, tar.bz2, zip, 7z, rar, arj, zoo) into a temporary directory"); p->icon = "plugin_"ANAME"_"E2IP".png"; //use icon file pathname if appropriate if (p->action == NULL) { //no need to free this gchar *action_name = g_strconcat (_A(5),".",aname,NULL); p->action = e2_plugins_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2p_unpack, NULL, FALSE, 0, NULL); return TRUE; } return FALSE; } /** @brief cleanup transient things for this plugin @param p pointer to data struct for the plugin @return TRUE if all cleanups were completed */ gboolean clean_plugin (Plugin *p) { //FIXME prevent unload when an unpack is underway //clear any current hook(s) while (e2_hook_unregister (&app.pane1.hook_change_dir, _e2p_unpack_change_dir_hook, NULL, FALSE)) {} while (e2_hook_unregister (&app.pane2.hook_change_dir, _e2p_unpack_change_dir_hook, NULL, FALSE)) {} gchar *action_name = g_strconcat (_A(5),".",aname,NULL); gboolean ret = e2_plugins_action_unregister (action_name); g_free (action_name); g_free (unpack_tmp); return ret; } emelfm2-0.4.1/plugins/e2p_find.c0000600000175000017500000033317510751445056015345 0ustar cairocairo/* $Id: e2p_find.c 799 2008-02-03 23:11:42Z tpgww $ Copyright (C) 2005-2008 tooar Portions copyright (C) 1999 Matthew Grossman This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ // much of this code was originally sourced from gtkfind 1.1 by Matthew Grossman /** @file plugins/e2p_find.c @brief file-find plugin This file contains functions related to creation of a file-find dialog, and execution of a find task in accord with options selected in that dialog. Back-end capability is provided by shell commands, specifically GNU grep and/or file. If available, some tracker search funtionality is provided. This will not work on any virtual file system, unless that is mounted */ /* TODO FIXME's when API is stable and used for a while, remove checks for correct no. of cached entry-strings check tracker usage */ //mimetypes listed at www.iana.org/assignments/media-types #include "emelfm2.h" #include #include #include #include #include #include #include // reported needed on a debian system //#include #include #ifdef TM_IN_SYS_TIME # include #endif #include "e2_plugins.h" #include "e2_dialog.h" #include "e2_task.h" //support mime-type search criteria #define MIMEFIND //miminum similarity % which is acceptable as a fuzzy match #define E2_ENOUGHLIKE 50.0 //support finding content using tracker database //not enabled as tracker is still too unstable #define TRACKERFIND typedef enum { SEARCH_ALL_P = 0, SEARCH_ALLACTIVE_P, SEARCH_ALLINACTIVE_P, //for variable namespaces SEARCH_CURRENT_P, SEARCH_OTHER_P, SEARCH_THIS_P, SEARCH_SUBDIRS_P, STRING_FILENAME_P, WILDCARD_FILENAME_P, REGEXP_FILENAME_P, NOT_FILENAME_P, ANYCASE_FILENAME_P, #ifdef MIMEFIND STRING_MIME_P, WILDCARD_MIME_P, NOT_MIME_P, #endif STRING_CONTENT_P, WILDCARD_CONTENT_P, REGEXP_CONTENT_P, ANYCASE_CONTENT_P, TRACK_CONTENT_P, MODE_IS_P, MODE_OR_P, MODE_NOT_P, //for decoding, these need to be in the same order as the flags in the permissions dialog //"u+s", "g+s", "o+t", "u+r", "u+w", "u+x", "g+r", "g+w", "g+x", "o+r", "o+w", "o+x" SETUID_P, SETGID_P, STICKY_P, OWNER_READ_P, OWNER_WRITE_P, OWNER_EXEC_P, GROUP_READ_P, GROUP_WRITE_P, GROUP_EXEC_P, WORLD_READ_P, WORLD_WRITE_P, WORLD_EXEC_P, REGULAR_P, DIRECTORY_P, RAW_DEVICE_P, BLOCK_DEVICE_P, SYMLINK_P, SOCKET_P, FIFO_P, TYPE_IS_P, TYPE_NOT_P, UID_ANY_P, UID_SPECIFIC_P, UID_NONE_P, UID_LOGIN_P, UID_NOT_LOGIN_P, GID_ANY_P, GID_SPECIFIC_P, GID_NONE_P, GID_LOGIN_P, GID_NOT_LOGIN_P, FSIZE_LT_P, FSIZE_EQ_P, FSIZE_GT_P, FSIZE_B_P, FSIZE_KB_P, FSIZE_MB_P, MTIME_LT_P, MTIME_EQ_P, MTIME_GT_P, ATIME_LT_P, ATIME_EQ_P, ATIME_GT_P, CTIME_LT_P, CTIME_EQ_P, CTIME_GT_P, MAX_FLAGS //no. of flags in the array } findflag_t; //this is the value which starts the permissions flags //used for decoding the flags to a mode number #define PERMISSIONS1 SETUID_P //we don't bother to cache the start-directory entry enum { NAME_ENTRY, CONTENT_ENTRY, #ifdef TRACKERFIND CONTENT_ENTRY2, #endif MIME_ENTRY, SIZE_ENTRY, UID_ENTRY, GID_ENTRY, MAX_ENTRIES }; typedef enum { ISNA, ISLT, ISLE, ISEQ, ISGE, ISGT, ISNE, WILD, LIKE, REGX, TRAK } findoperator; typedef struct _E2P_FindTargets { gchar *nametarget; //localised string for various uses #ifdef MIMEFIND gchar *mimetarget; #endif gchar *contenttarget; guint64 sizetarget; mode_t permtarget; uid_t usertarget; gid_t grouptarget; time_t mtimtarget; time_t atimtarget; time_t ctimtarget; guint typetarget;//can't store choices in a mode_t, that has shared bits findoperator nameop; #ifdef MIMEFIND findoperator mimeop; #endif findoperator contentop; findoperator sizeop; findoperator permop; findoperator userop; findoperator groupop; findoperator mtimop; findoperator atimop; findoperator ctimop; findoperator typeop; regex_t compiledname; // regex_t compiledcontent; guint preplen; //length of nametarget when doing a fuzzy match gboolean name_anycase; //for grep searches only gboolean content_anycase; //ditto gboolean content_only; //not looking for anything else gint searchdepth; #ifdef TRACKERFIND gint tracker_service; //index of service in cmd_str[] #endif #ifdef E2_VFS VPATH sdata; #else gchar *localstartpath; #endif GList *dirdata; #ifdef E2_VFS GError **operr; #endif pthread_t findID; gboolean aborted; //TRUE after STOP button clicked } findtargets; typedef struct _E2P_Spinners { GtkWidget *day_spin; GtkWidget *month_spin; GtkWidget *year_spin; GtkWidget *hour_spin; GtkWidget *minute_spin; } spinners; typedef struct _E2_FindDialogRuntime { GtkWidget *dialog; GtkWidget *notebook; //notebook, gui for the various search criteria GtkWidget *active_button;//radio-button for searching in current directory GtkWidget *thisdir_button; //radio-button for searching in directory-entry path GtkWidget *directory; //entry with text for directory to search in/from GtkWidget *pattern; //entry with text for file name to search for #ifdef MIMEFIND GtkWidget *mime_entry; //entry with user-specified mimetype #endif GtkWidget *content_pattern; //entry with text for grepped file content #ifdef TRACKERFIND GtkWidget *content_pattern2; //entry with text for tracked file content GtkWidget *service_combo; //tracker-service picker #endif GtkWidget *curr_user; GtkWidget *choose_user; GtkWidget *user_entry; //entry with user-specified uid GtkWidget *curr_group; GtkWidget *choose_group; GtkWidget *group_entry; //entry with user-specified uid GtkWidget *size_entry; //entry with user-specified filesize GtkWidget *stop_button; //button widget, remebered for changing sensitivity GtkWidget *start_button; //ditto GtkWidget *help_button; //ditto // gboolean stop_flag; //TRUE when stop button clicked in matchdata //spin buttons for file date/time parameters spinners mtime; spinners atime; spinners ctime; GSList *groups; //list of lists of grouped toggle btns findtargets *matchdata; //now static gboolean flags[MAX_FLAGS]; //cache for toggle values } E2_FindDialogRuntime; typedef struct _E2P_TimeOffset { glong days; gint minutes; } offset; typedef struct _E2P_DateTime { gfloat day; gfloat month; gfloat year; gfloat hour; gfloat minute; // gfloat second; } local_dt; //tracker stuff, from various tracker 0.6.0 headers /* enum { SERVICE_FILES, SERVICE_FOLDERS, SERVICE_DOCUMENTS, SERVICE_IMAGES, SERVICE_MUSIC, SERVICE_VIDEOS, SERVICE_TEXT_FILES, SERVICE_DEVELOPMENT_FILES, SERVICE_OTHER_FILES, SERVICE_VFS_FILES, SERVICE_VFS_FOLDERS, SERVICE_VFS_DOCUMENTS, SERVICE_VFS_IMAGES, SERVICE_VFS_MUSIC, SERVICE_VFS_VIDEOS, SERVICE_VFS_TEXT_FILES, SERVICE_VFS_DEVELOPMENT_FILES, SERVICE_VFS_OTHER_FILES, SERVICE_CONVERSATIONS, SERVICE_PLAYLISTS, SERVICE_APPLICATIONS, SERVICE_CONTACTS, SERVICE_EMAILS, SERVICE_EMAILATTACHMENTS, SERVICE_APPOINTMENTS, SERVICE_TASKS, SERVICE_BOOKMARKS, SERVICE_HISTORY, SERVICE_PROJECTS, MAXSERVICES }; */ #ifdef TRACKERFIND #define ACTIVE_TRACKER_SERVICES 12 static gchar *cmd_str [ACTIVE_TRACKER_SERVICES] = { //these service names are hardcoded in tracker-files, and not translated //same order here as enum "Files", //we're not really interested in files, this is used as a proxy for "anywhere" // NULL, //not interested in "Folders", "Documents", "Images", "Music", "Videos", "Text", "Development", "Other", // NULL, //SERVICE_VFS_FILES, // NULL, //SERVICE_VFS_FOLDERS, // NULL, //SERVICE_VFS_DOCUMENTS, // NULL, //SERVICE_VFS_IMAGES, // NULL, //SERVICE_VFS_MUSIC, // NULL, //SERVICE_VFS_VIDEOS, // NULL, //SERVICE_VFS_TEXT_FILES, // NULL, //SERVICE_VFS_DEVELOPMENT_FILES, // NULL, //SERVICE_VFS_OTHER_FILES, "Conversations", //"GaimConversations", // NULL, //SERVICE_PLAYLISTS, "Applications", // NULL, //SERVICE_CONTACTS, "Emails", //"EvolutionEmails" "KMailEmails", "EmailAttachments", // "EvolutionAttachments" "KMailAttachments", // NULL, //SERVICE_APPOINTMENTS, // NULL, //SERVICE_TASKS, // NULL, //SERVICE_BOOKMARKS, // NULL, //SERVICE_HISTORY, // NULL, //SERVICE_PROJECTS, }; static gchar *object_names [ACTIVE_TRACKER_SERVICES] = { //these service strings are in same order as enum N_("all files"), //not actually interested in "Files", this is a proxy // NULL, //not interested in "Folders", N_("office documents"), N_("images"), N_("music"), N_("videos"), N_("text files"), N_("development files"), N_("other files"), // NULL, //SERVICE_VFS_FILES, // NULL, //SERVICE_VFS_FOLDERS, // NULL, //SERVICE_VFS_DOCUMENTS, // NULL, //SERVICE_VFS_IMAGES, // NULL, //SERVICE_VFS_MUSIC, // NULL, //SERVICE_VFS_VIDEOS, // NULL, //SERVICE_VFS_TEXT_FILES, // NULL, //SERVICE_VFS_DEVELOPMENT_FILES, // NULL, //SERVICE_VFS_OTHER_FILES, N_("conversations"), // NULL, //N_("playlists") SERVICE_PLAYLISTS, N_("applications"), // NULL, //N_("people") SERVICE_CONTACTS, N_("emails"), //"EvolutionEmails" "KMailEmails", N_("email attachments"), //"EvolutionAttachments" "KMailAttachments", // NULL, //N_("appointments") SERVICE_APPOINTMENTS, // NULL, //N_("tasks") SERVICE_TASKS, // NULL, //N_("bookmarks") SERVICE_BOOKMARKS, // NULL, //N_("notes")SERVICE_HISTORY, // NULL, //N_("projects") SERVICE_PROJECTS, }; static gint service_index = -1; //undefined selection //static GList *query_history = NULL; #endif //def TRACKERFIND //mutex to prevent racing when dialog is closed pthread_mutex_t find_mutex; static E2_FindDialogRuntime *find_rt; static local_dt current; static gboolean nocacheflags; //session-cache for last-focused notebook page static gint page_store; //ditto for toggle values static gboolean flags[MAX_FLAGS]; //ditto for entries' text (except the start-directory entry) static gchar *entries[MAX_ENTRIES]; //real-cache data for entries' text static GList *strings = NULL; static gboolean _e2p_find_match1 (VPATH *localpath, const struct stat *statptr, findtargets *data); static gpointer _e2p_find_dofind (E2_FindDialogRuntime *rt); static void _e2p_find_cleanfind (E2_FindDialogRuntime *rt); static void _e2p_find_make_name_tab (GtkWidget *notebook, E2_FindDialogRuntime *rt); static void _e2p_find_make_size_tab (GtkWidget *notebook, E2_FindDialogRuntime *rt); #ifdef MIMEFIND static void _e2p_find_make_mimetype_tab (GtkWidget *notebook, E2_FindDialogRuntime *rt); #endif static void _e2p_find_make_content_tab (GtkWidget *notebook, E2_FindDialogRuntime *rt); static void _e2p_find_make_atime_tab (GtkWidget *notebook, E2_FindDialogRuntime *rt); static void _e2p_find_make_ctime_tab (GtkWidget *notebook, E2_FindDialogRuntime *rt); static void _e2p_find_make_mtime_tab (GtkWidget *notebook, E2_FindDialogRuntime *rt); static void _e2p_find_make_mode_tab (GtkWidget *notebook, E2_FindDialogRuntime *rt); static void _e2p_find_make_type_tab (GtkWidget *notebook, E2_FindDialogRuntime *rt); static void _e2p_find_make_owner_tab (GtkWidget *notebook, E2_FindDialogRuntime *rt); #define _e2p_find_create_vbox(box) e2_widget_add_box(box,TRUE,0,TRUE,FALSE,E2_PADDING) #define _e2p_find_create_label(box,text) e2_widget_add_mid_label(box,text,0.5,FALSE,E2_PADDING_XSMALL) static GtkWidget *_e2p_find_create_hbox (GtkWidget *box); static GtkWidget *_e2p_find_create_entry (GtkWidget *box, gchar *text); //static GtkWidget *_e2p_find_create_button (GtkWidget *container, // void (*callback) (), gpointer callback_data); static GtkWidget *_e2p_find_create_toggle_grouped_button (GtkWidget *container, findflag_t f, gboolean state, gchar *label, GtkWidget *leader, E2_FindDialogRuntime *rt); static GtkWidget *_e2p_find_create_toggle_button (GtkWidget *box, findflag_t f, gboolean state, gchar *label, E2_FindDialogRuntime *rt); static GtkWidget *_e2p_find_create_radio_button (GtkWidget *box, GtkWidget *leader, findflag_t f, gboolean state, gchar *label, E2_FindDialogRuntime *rt); //static GtkWidget *_e2p_find_create_radio_grouped_button (GtkWidget *box, GtkWidget *leader, // findflag_t f, gboolean state, gchar *label, E2_FindDialogRuntime *rt); static GtkWidget *_e2p_find_create_spin_button (gfloat *default_value, gfloat min_value, gfloat max_value); static gboolean _e2p_find_check_leapyear (gint year); static gboolean _e2p_find_get_datetime (time_t *result, spinners *times); static void _e2p_find_get_current_datetime (local_dt *current); static gboolean _e2p_find_get_flag (findflag_t f); //, E2_FindDialogRuntime *rt); static void _e2p_find_set_flag (findflag_t f, gboolean value); //, E2_FindDialogRuntime *rt); static void _e2p_find_set_toggle_button_off (GtkWidget *widget); static void _e2p_find_set_toggle_button_on (GtkWidget *widget); static void _e2p_find_make_all_spinners (GtkWidget *box, spinners *times); static void _e2p_find_reset_all_widgets (GtkWidget *widget, gpointer data); static void _e2p_find_reset_flags (void); //E2_FindDialogRuntime *rt); static void _e2p_find_reset_entry (GtkWidget *widget); static void _e2p_find_reset_spin_button (GtkWidget *widget); /*******************/ /**** utilities ****/ /*******************/ #ifdef TRACKERFIND /** @brief conduct a search using tracker data @param startpath localised absolute path of directory in which to "start searching" @param searchdepth 1 when just looking in @a startpath, -1 otherwise @param data pointer to match-data @return */ static void _e2p_find_tracker_find (VPATH *startpath, gint searchdepth, findtargets *data) { gpointer matches; gchar *s, *e, *command; /*tracker-files -s ServiceType gets all files in that type*/ /*tracker-meta-folder path returns the list of files indexed by tracker and stored in path*/ /*tracker-query searches documents using a query file (in rdf format) provided as parameter, and returns the documents matching the criteria of the query file*/ /* tracker-search searches for the expression provided as parameter in the list of documents indexed by trackerd*/ if (data->contentop == TRAK) command = e2_utils_strcat ("tracker-search ", data->contenttarget); //encoding ? else //data->typeop == TRAK command = e2_utils_strcat ("tracker-files -s ", cmd_str [data->tracker_service]); if (e2_fs_get_command_output (command, &matches)) { E2_ERR_DECLARE; struct stat sb; #ifdef E2_VFS VPATH ddata; ddata.spacedata = startpath->spacedata; #endif //for checking whether or not matched items are in startpath gint skip = (searchdepth == 1) ? strlen (VPSTR (startpath)) + sizeof (gchar):-1; s = (gchar *)matches; while (*s != '\0' && (e = strchr (s, '\n')) != NULL) { *e = '\0'; if (g_str_has_prefix (s, VPSTR (startpath)) && (skip == -1 || strchr (s+skip, G_DIR_SEPARATOR) == NULL)) { #ifdef E2_VFS ddata.localpath = s; if (!e2_fs_lstat (&ddata, &sb E2_ERR_PTR())) #else if (!e2_fs_lstat (s, &sb E2_ERR_PTR())) #endif { if (S_ISREG (sb.st_mode)) #ifdef E2_VFS _e2p_find_match1 (&ddata, &sb, data); #else _e2p_find_match1 (s, &sb, data); #endif } else if (!E2_ERR_IS (ENOENT)) #ifdef E2_VFS _e2p_find_match1 (&ddata, NULL, data); #else _e2p_find_match1 (s, NULL, data); #endif } s = e + sizeof (gchar); } g_free (matches); } /* //make a temporary rdf file gchar *localpath = "FIXME"; E2_FILE *stream = e2_fs_open_writestream (localpath E2_ERR_NONE()); gchar *rdfstring = "RULES"; //write it e2_fs_put_stream (stream, rdfstring, localpath, E2_ERR_NONE()); //close it e2_fs_close_stream (stream); //run it gchar *command = e2_utils_strcat ("tracker-query ", utfpath); gchar *matches; if (e2_fs_get_command_output (command, &matches)) { struct stat sb; while (can get another matching filepath) { if (filepath has good prefix) { if (!e2_fs_lstat (filepath, &sb E2_ERR_PTR())) { if (S_ISREG (sb.st_mode)) _e2p_find_match1 (filepath, &sb, data); } else if (!E2_ERR_IS (ENOENT)) _e2p_find_match1 (filepath, NULL, data); } } g_free (matches); } //cleanup gchar *localpath = "FIXME"; e2_task_backend_delete (localpath); F_FREE (localpath); g_free (utfpath); g_free (rdfstring); */ g_free (command); } #endif /** @brief convert @a string to a form suitable for n-gram scanning Changes upper-case to lower-case, punctuation-marks and some special characters to spaces @param string string to be processed, if not utf-8 it's parsed as ascii @return newly-allocated converted string */ static gchar *_e2p_find_prepare_like_string (gchar *string) { gchar *tmp, *retval; if (g_utf8_validate (string, -1, NULL)) { glong j, len; gunichar ch; gunichar *converted; tmp = g_utf8_strdown (string, -1); converted = g_utf8_to_ucs4_fast (tmp, -1, &len); g_free (tmp); for (j = 0; j < len; j++) { ch = converted [j]; if (!g_unichar_isalnum (ch) #ifdef USE_GLIB2_14 && !g_unichar_ismark (ch) #endif ) ch = (gunichar)' '; else if (g_unichar_isupper (ch)) ch = g_unichar_tolower (ch); else { switch ((guint8)ch) { default: break; case 196: ch = (gunichar)228; break; //ANSI-umlaut case 214: ch = (gunichar)246; break; case 220: ch = (gunichar)252; break; case 142: ch = (gunichar)132; break; //ASCII-umlaut case 153: ch = (gunichar)148; break; case 154: ch = (gunichar)129; break; } } } tmp = g_ucs4_to_utf8 (converted, -1, NULL, NULL, NULL); g_free (converted); } else //not utf8, treat as ascii anyway { guchar c; gchar *s; tmp = g_strdup (string); s = tmp; while ((c = (guchar)*s) != '\0') { if (c < '0' || (c > '9' && c < 'A') || (c > 'Z' && c < 'a')) //'_' ok ?? *s = ' '; else if (g_ascii_isupper (c)) *s = g_ascii_tolower (c); else { switch (c) { default: break; case 196: *s = 228; break; //ANSI-umlaut case 214: *s = 246; break; case 220: *s = 252; break; case 142: *s = 132; break; //ASCII-umlaut case 153: *s = 148; break; case 154: *s = 129; break; } } } } //surround by "space marks" retval = g_strdup_printf (" %s ", tmp); g_free (tmp); return retval; } /** @brief count matching and total n-grams in @a haystack @param prepared_hay pre-processed haystack string @param prepared_ndl pre-processed needle string @param needlelen byte-length of @a prepared_ndl @param ngramlen byte-length of n-gram for this scan, 2, 3 or 5 (must be < needlelen and < 8) @param maxmatches pointer to store for count of weighted no. of n-grams @return weighted count of matching n-grams */ static guint _e2p_find_match_ngrams (gchar *prepared_hay, gchar *prepared_ndl, guint needlelen, guint ngramlen, guint *maxmatches) { //FIXME use chars, not bytes #ifdef __USE_GNU gchar NGram [ngramlen + 1]; #else gchar NGram [8]; #endif NGram [ngramlen] = '\0'; // *maxmatch = 0; needed if not using all n-grams //search in the target string for each n-gram in the search string guint i, matches = 0; guint ngrams = needlelen - ngramlen + 1; for (i = 0; i < ngrams; i++) { memcpy (NGram, &prepared_ndl[i], ngramlen); //FIXME chars, not bytes //ignore included whitespace ?? CHECKME // if (NGram [ngramlen - 2] == ' ' && NGram [0] != ' ') // i += ngramlen - 3; // else // { // *maxmatch += ngramlen; //weighted n-gram count needed if not using all n-grams if (strstr (prepared_hay, NGram)) //FIXME chars matches++; // } } *maxmatches = ngrams * ngramlen; return matches * ngramlen; //more significance for longer n-grams } /** @brief perform a fuzzy search on @a haystack using pre-compiled search data @param haystack candidate string to check against the needle @param needle the pre-converted target string to match in @a haystack @param needlelen byte-length of @a needle @return TRUE if a match was found */ static gboolean _e2p_find_fuzzy_match (gchar *haystack, gchar *needle, gint needlelen) { gchar *prepared = _e2p_find_prepare_like_string (haystack); if (needlelen < 3) //short strings only match exactly { gboolean match = g_str_equal (prepared, needle); g_free (prepared); return match; } guint maxmatches1, maxmatches2; //length of n-grams depends on the search string guint matchcount1 = _e2p_find_match_ngrams (prepared, needle, needlelen, (needlelen < 7) ? 2 : 3, &maxmatches1); /*short n-grams can be too indiscriminate, and long ones decrease the tolerance. It's best to use > 1 short n-grams with different lengths */ guint matchcount2 = _e2p_find_match_ngrams (prepared, needle, needlelen, (needlelen < 7) ? 3 : 5, &maxmatches2); gfloat similarity = 100.0 * (gfloat)(matchcount1 + matchcount2) / (maxmatches1 + maxmatches2); printd (DEBUG, "similarity between%sand%sis %f percent", prepared, needle, similarity); g_free (prepared); return (similarity >= E2_ENOUGHLIKE); } /** @brief determine whether @a localpath matches all relevant search paramters in @a data Uses file(1) to get mime string, uses grep(1) to check content The path of each item that passes all tests is printed @param localpath absolute localised path of an item as provided to the twcb funtion @param statptr ptr to buffer as provided to the twcb funtion, or NULL for _NS items @param data ptr to search parameters @return TRUE if a match was found */ static gboolean _e2p_find_match1 (VPATH *localpath, const struct stat *statptr, findtargets *data) { gboolean matched = FALSE; //name checking if (data->nameop != ISNA) { gchar *basename = g_path_get_basename (VPSTR(localpath)); if (data->name_anycase) { gchar *tmp = basename; basename = g_ascii_strdown (tmp, -1); //FIXME ascii may not be enough g_free (tmp); } switch (data->nameop) { case ISEQ: matched = g_str_equal (data->nametarget, basename); break; case ISNE: matched = !g_str_equal (data->nametarget, basename); break; case WILD: matched = (fnmatch (data->nametarget, basename, FNM_PERIOD) == 0); break; case LIKE: matched = _e2p_find_fuzzy_match (basename, data->nametarget, data->preplen); break; case REGX: //CHECKME encoding ok ? matched = (regexec (&data->compiledname, basename, 0, NULL, 0) == 0); default: break; } g_free (basename); if (!matched) return FALSE; } //statbuf-related checking - easiest, done first if (statptr == NULL) { //fail if we're trying to do any of them if ( data->sizeop != ISNA || data->permop != ISNA || data->mtimop != ISNA || data->atimop != ISNA || data->ctimop != ISNA || data->userop != ISNA || data->groupop != ISNA || data->typeop != ISNA ) return FALSE; } else //we can check the statbuf-related parameters { if (data->sizeop != ISNA) { if (data->sizeop == ISLT && data->sizetarget <= (guint64) statptr->st_size) return FALSE; if (data->sizeop == ISGT && data->sizetarget >= (guint64) statptr->st_size) return FALSE; if (data->sizeop == ISEQ && data->sizetarget != (guint64) statptr->st_size) return FALSE; } if (data->permop != ISNA) { mode_t masked = statptr->st_mode & ALLPERMS; if (data->permop == ISEQ && data->permtarget != masked) return FALSE; if (data->permop == ISNE && data->permtarget == masked) return FALSE; if (data->permop == ISGE && (masked | data->permtarget) != masked) return FALSE; } if (data->mtimop != ISNA) { if (data->mtimop == ISLT && data->mtimtarget >= statptr->st_mtime) return FALSE; if (data->mtimop == ISGE && data->mtimtarget < statptr->st_mtime) //times are rounded up to next minute return FALSE; if (data->mtimop == ISEQ && data->mtimtarget != statptr->st_mtime) return FALSE; } if (data->atimop != ISNA) { if (data->atimop == ISLT && data->atimtarget >= statptr->st_atime) return FALSE; if (data->atimop == ISGE && data->atimtarget < statptr->st_atime) //times are rounded up to next minute return FALSE; if (data->atimop == ISEQ && data->atimtarget != statptr->st_atime) return FALSE; } if (data->ctimop != ISNA) { if (data->ctimop == ISLT && data->ctimtarget >= statptr->st_ctime) return FALSE; if (data->ctimop == ISGE && data->ctimtarget < statptr->st_ctime) //times are rounded up to next minute return FALSE; if (data->ctimop == ISEQ && data->ctimtarget != statptr->st_ctime) return FALSE; } if (data->userop != ISNA) { if (data->userop == ISEQ && data->usertarget != statptr->st_uid) return FALSE; if (data->userop == ISNE && data->usertarget == statptr->st_uid) return FALSE; if (data->userop == ISLT //this item's user is not registered && getpwuid (statptr->st_uid) != NULL) return FALSE; } if (data->groupop != ISNA) { if (data->groupop == ISEQ && data->grouptarget != statptr->st_gid) return FALSE; if (data->groupop == ISNE && data->grouptarget == statptr->st_gid) return FALSE; if (data->groupop == ISLT //this item's group is not registered && getgrgid (statptr->st_gid) != NULL) return FALSE; } if (data->typeop != ISNA) { guint i, c; //flag-order is //REGULAR_P, DIRECTORY_P, RAW_DEVICE_P, BLOCK_DEVICE_P, SYMLINK_P, SOCKET_P, FIFO_P guint masks [7] = { S_IFREG, S_IFDIR, S_IFCHR, S_IFBLK, #ifdef S_IFLNK S_IFLNK, #else 0, #endif #ifdef S_IFSOCK S_IFSOCK, #else 0, #endif #ifdef S_IFIFO S_IFIFO, #else 0, #endif }; mode_t masked = statptr->st_mode & S_IFMT; for (i = 0; i < 7 ; i++) { c = 1 << i; if (data->typetarget & c) { if (data->typeop == ISEQ && masked != masks [i]) return FALSE; if (data->typeop == ISNE && masked == masks [i]) return FALSE; } } } } //end of statbuf-related checks if (data->mimeop != ISNA) { gpointer mime; gchar *command = e2_utils_strcat ("file -biNprs ", VPSTR(localpath)); if (e2_fs_get_command_output (command, &mime)) { gchar *s; //output is a string like application/x-bzip2 or text/plain; charset=us-ascii if ((s = strchr ((gchar *)mime, ';')) != NULL) *s = '\0'; g_strstrip ((gchar *)mime); matched = TRUE; if (data->mimeop == ISEQ && !g_str_equal ((gchar *)mime, data->mimetarget)) matched = FALSE; else if (data->mimeop == ISNE && g_str_equal ((gchar *)mime, data->mimetarget)) matched = FALSE; else if (data->mimeop == WILD) { s = strchr ((gchar *)mime, G_DIR_SEPARATOR); if (s != NULL) { //separately check strings before and after separator *s = '\0'; s++; //temporarily split target gchar *p = strchr (data->mimetarget, G_DIR_SEPARATOR); if (p != NULL) *p = '\0'; matched = !fnmatch (data->mimetarget, (gchar *)mime, FNM_LEADING_DIR | FNM_FILE_NAME | FNM_CASEFOLD); if (matched) matched = !fnmatch ((p == NULL) ? data->mimetarget : p+sizeof(gchar), s, FNM_FILE_NAME | FNM_CASEFOLD); if (p != NULL) *p = G_DIR_SEPARATOR; } else matched = !fnmatch (data->mimetarget, (gchar *)mime, FNM_FILE_NAME | FNM_CASEFOLD); } g_free (command); g_free (mime); if (!matched) return FALSE; } else { //FIXME warning g_free (command); return FALSE; } } if (data->contentop != ISNA) { gchar *tmp3 = (data->content_anycase) ? "i":""; gchar *command = g_strdup_printf ( "grep -El%ssZ -d skip -D skip -e '%s' \"%s\"", tmp3, data->contenttarget, VPSTR(localpath)); gpointer output; if (e2_fs_get_command_output (command, &output)) { g_free (command); //this will probably always succeed, mismatches should be failed commands matched = (*(gchar *)output != '\0'); g_free (output); if (!matched) return FALSE; } else { g_free (command); return FALSE; } } gchar *utf = F_FILENAME_FROM_LOCALE (VPSTR(localpath)); gdk_threads_enter (); e2_output_print (&app.tab, utf, NULL, TRUE, NULL); gdk_threads_leave (); F_FREE (utf); return TRUE; } /* * @brief cleanup find parameters in @a data, and @a data itself This grabs find_mutex lock @param data ptr to search parameters data struct, may be NULL @return */ /* now inline static void _e2p_find_clear_match_data (findtargets *data) { pthread_mutex_lock (&find_mutex); if (data != NULL) { if (data->nametarget != NULL) g_free (data->nametarget); if (data->nameop == REGX) regfree (&data->compiledname); #ifdef MIMEFIND if (data->mimetarget != NULL) g_free (data->mimetarget); #endif if (data->contenttarget != NULL) g_free (data->contenttarget); // if (data->contentop == REGX) // regfree (&data.compiledcontent); #ifdef E2_VFS g_free (VPSTR(data->sdata)); #else g_free (data->localstartpath); #endif DEALLOCATE (findtargets, data); } pthread_mutex_unlock (&find_mutex); } */ /** @brief populate find parameters in @a data with settings from dialog represented by @a rt @param data ptr to search parameters @param rt pointer to dialog data struct @return */ static void _e2p_find_get_match_data (findtargets *data, E2_FindDialogRuntime *rt) { const gchar *tmp; gchar *tmp2, *tmp3; memset (data, 0, sizeof (findtargets)); //ITEMNAME tmp = gtk_entry_get_text (GTK_ENTRY (rt->pattern)); if (*tmp != '\0') { FileInfo *info; if (g_str_equal (tmp, "%f")) { info = e2_fileview_get_selected_first_local (curr_view, FALSE); if (info != NULL) tmp2 = g_strdup (info->filename); else return; } else if (g_str_equal (tmp, "%F")) { info = e2_fileview_get_selected_first_local (other_view, FALSE); if (info != NULL) tmp2 = g_strdup (info->filename); else return; } else { tmp2 = D_FILENAME_TO_LOCALE (tmp); g_strstrip (tmp2); if (*tmp2 == '\0') { g_free (tmp2); return; } } data->name_anycase = _e2p_find_get_flag (ANYCASE_FILENAME_P); //, rt) if (data->name_anycase) { tmp3 = tmp2; tmp2 = g_ascii_strdown (tmp3, -1); //FIXME ascii may not be enough g_free (tmp3); } data->nametarget = tmp2; if (_e2p_find_get_flag (STRING_FILENAME_P)) //, rt)) { if (strchr (tmp2, '*') != NULL || strchr (tmp2, '?') != NULL) data->nameop = WILD; else data->nameop = ISEQ; } else if (_e2p_find_get_flag (WILDCARD_FILENAME_P)) //, rt)) { if (strchr (tmp2, '*') != NULL || strchr (tmp2, '?') != NULL) data->nameop = WILD; else { data->nameop = LIKE; data->nametarget = _e2p_find_prepare_like_string (tmp2); data->preplen = strlen (data->nametarget); g_free (tmp2); } } else //regex { tmp3 = (*tmp2 == '^') ? tmp2 : g_strconcat ("^", tmp2, NULL); gint cflags = REG_EXTENDED | REG_NOSUB; if (data->name_anycase) cflags |= REG_ICASE; if (regcomp (&data->compiledname, tmp3, cflags)) { //FIXME warn user data->nameop = ISNA; } else { g_free (tmp2); data->nameop = REGX; data->nametarget = NULL; } if (tmp3 != tmp2) g_free (tmp3); } } else data->nameop = ISNA; #ifdef MIMEFIND //MIME tmp = gtk_entry_get_text (GTK_ENTRY (rt->mime_entry)); if (*tmp != '\0') { //mime strings should be lower-case tmp2 = g_utf8_strdown (tmp, -1); g_strstrip (tmp2); if (*tmp2 != '\0') { gchar *local; //matcher compares file(1) output, CHECKME that's localised text local = g_locale_from_utf8 (tmp2, -1, NULL, NULL, NULL); //FIXME support exact match tmp3 = strchr (local, G_DIR_SEPARATOR); if (tmp3 == NULL) { tmp3 = local; local = e2_utils_strcat ("*/", local); g_free (tmp3); } else if (tmp3 == local) { tmp3 = local; local = e2_utils_strcat ("*", local); g_free (tmp3); } data->mimetarget = local; //FIXME support not WILD //STRING_MIME_P, WILDCARD_MIME_P, NOT_MIME_P if (strchr (local, '*') != NULL || strchr (local, '?') != NULL) data->mimeop = WILD; else { //FIXME support ISNE in UI if (!_e2p_find_get_flag (NOT_MIME_P)) //, rt)) data->mimeop = ISEQ; else data->mimeop = ISNE; } } else data->mimeop = ISNA; g_free (tmp2); } else data->mimeop = ISNA; #endif //CONTENT #ifdef TRACKERFIND if (rt->content_pattern2 != NULL //maybe want search by tracker && _e2p_find_get_flag (TRACK_CONTENT_P)) { tmp = gtk_entry_get_text (GTK_ENTRY (rt->content_pattern2)); if (*tmp != '\0') { data->contentop = TRAK; data->contenttarget = D_FILENAME_TO_LOCALE (tmp); //CHECKME encoding } else data->contentop = ISNA; } else { #endif tmp = gtk_entry_get_text (GTK_ENTRY (rt->content_pattern)); if (*tmp != '\0') { data->content_anycase = _e2p_find_get_flag (ANYCASE_CONTENT_P); //FIXME support fuzzy content matching //FIXME what encoding for content ? //(matcher uses grep(1) which expects encoding determined from environment variables) tmp2 = g_locale_from_utf8 (tmp, -1, NULL, NULL, NULL); if (_e2p_find_get_flag (STRING_CONTENT_P)) data->contentop = ISEQ; //no need to check for entered wildcard, grep handles that anyway else if (_e2p_find_get_flag (WILDCARD_CONTENT_P)) { if (strchr (tmp2, '*') == NULL && strchr (tmp2, '?') == NULL) { tmp3 = tmp2; tmp2 = g_strconcat ("*", tmp2, "*", NULL); g_free (tmp3); } data->contentop = ISEQ; //WILD not needed, grep is ok with wildcards } else { data->contenttarget = tmp2; data->contentop = REGX; } if (data->contentop == ISEQ) //exact or wild { //matcher grep(1) does comparison using regular expressions //adjust the find pattern, by replacing all '.', '*' and '?' with //their extended regex equivalents gchar **split = g_strsplit (tmp2, ".", -1); g_free (tmp2); tmp2 = g_strjoinv ("\\.", split); g_strfreev (split); split = g_strsplit (tmp2, "?", -1); g_free (tmp2); tmp2 = g_strjoinv (".", split); g_strfreev (split); split = g_strsplit (tmp2, "*", -1); g_free (tmp2); data->contenttarget = g_strjoinv (".*", split); //freeme later lazy-star to prevent greedy matching ? g_strfreev (split); } } else data->contentop = ISNA; #ifdef TRACKERFIND } #endif //MTIME if (_e2p_find_get_flag (MTIME_LT_P)) //, rt) data->mtimop = ISLT; else if (_e2p_find_get_flag (MTIME_EQ_P)) //, rt) data->mtimop = ISEQ; else if (_e2p_find_get_flag (MTIME_GT_P)) //, rt) data->mtimop = ISGT; else data->mtimop = ISNA; if (data->mtimop != ISNA) { if (_e2p_find_get_datetime (&data->mtimtarget, &rt->mtime)) { if (data->mtimop == ISGT) { data->mtimtarget += 60; //compensate for minutes-rounding data->mtimop = ISGE; } } else { //FIXME warn user data->mtimop = ISNA; } } //ATIME if (_e2p_find_get_flag (ATIME_LT_P)) //, rt) data->atimop = ISLT; else if (_e2p_find_get_flag (ATIME_EQ_P)) //, rt) data->atimop = ISEQ; else if (_e2p_find_get_flag (ATIME_GT_P)) //, rt) data->atimop = ISGT; else data->atimop = ISNA; if (data->atimop != ISNA) { if (_e2p_find_get_datetime (&data->atimtarget, &rt->atime)) { if (data->atimop == ISGT) { data->atimtarget += 60; //compensate for minutes-rounding data->atimop = ISGE; } } else { //FIXME warn user data->atimop = ISNA; } } //CTIME if (_e2p_find_get_flag (CTIME_LT_P)) //, rt) data->ctimop = ISLT; else if (_e2p_find_get_flag (CTIME_EQ_P)) //, rt) data->ctimop = ISEQ; else if (_e2p_find_get_flag (CTIME_GT_P)) //, rt) data->ctimop = ISGT; else data->ctimop = ISNA; if (data->ctimop != ISNA) { if (_e2p_find_get_datetime (&data->ctimtarget, &rt->ctime)) { if (data->ctimop == ISGT) { data->ctimtarget += 60; //compensate for minutes-rounding data->ctimop = ISGE; } } else { //FIXME warn user data->ctimop = ISNA; } } //USER if (_e2p_find_get_flag (UID_ANY_P)) { data->usertarget = 0; data->userop = ISNA; } else if (_e2p_find_get_flag (UID_LOGIN_P)) //, rt)) { data->usertarget = getuid (); data->userop = ISEQ; } else if (_e2p_find_get_flag (UID_SPECIFIC_P)) //, rt)) { tmp = gtk_entry_get_text (GTK_ENTRY (rt->user_entry)); if (*tmp != '\0') { struct passwd *pwptr; tmp2 = g_locale_from_utf8 (tmp, -1, NULL, NULL, NULL); //, rt)) //errno = 0; pwptr = getpwnam (tmp2); if (pwptr != NULL) { data->usertarget = pwptr->pw_uid; data->userop = ISEQ; } else { //FIXME warn user data->usertarget = 0; data->userop = ISNA; } g_free (tmp2); } else { data->usertarget = 0; data->userop = ISNA; } } else if (_e2p_find_get_flag (UID_NONE_P)) //, rt)) { data->usertarget = 0; data->userop = ISLT; //special use of this } //GROUP if (_e2p_find_get_flag (GID_ANY_P)) { data->grouptarget = 0; data->groupop = ISNA; } else if (_e2p_find_get_flag (GID_LOGIN_P)) //, rt)) { data->grouptarget = getgid (); data->groupop = ISEQ; } else if (_e2p_find_get_flag (GID_SPECIFIC_P)) //, rt)) { tmp = gtk_entry_get_text (GTK_ENTRY (rt->group_entry)); if (*tmp != '\0') { struct group *grptr; tmp2 = g_locale_from_utf8 (tmp, -1, NULL, NULL, NULL); //, rt)) grptr = getgrnam (tmp2); if (grptr != NULL) { data->grouptarget = grptr->gr_gid; data->groupop = ISEQ; } else { data->grouptarget = 0; data->groupop = ISNA; } g_free (tmp2); } else { data->grouptarget = 0; data->groupop = ISNA; } } else if (_e2p_find_get_flag (GID_NONE_P)) //, rt)) { data->grouptarget = 0; data->groupop = ISLT; //special use of this } //SIZE tmp = gtk_entry_get_text (GTK_ENTRY (rt->size_entry)); if (*tmp != '\0') { if (_e2p_find_get_flag (FSIZE_LT_P)) //, rt)) data->sizeop = ISLT; else if (_e2p_find_get_flag (FSIZE_GT_P)) //, rt)) data->sizeop = ISGT; else data->sizeop = ISEQ; tmp2 = g_locale_from_utf8 (tmp, -1, NULL, NULL, NULL); gchar *endptr; data->sizetarget = g_ascii_strtoull (tmp2, &endptr, 10); if (*endptr == '\0') { if (!_e2p_find_get_flag (FSIZE_B_P)) //, rt)) { data->sizetarget *= 1024; //bump up the accuracy for finding big files with a fractional size if (_e2p_find_get_flag (FSIZE_MB_P)) //, rt)) data->sizetarget *= 1024; } } else { data->sizetarget = 0; data->sizeop = ISNA; } g_free (tmp2); } else { data->sizetarget = 0; data->sizeop = ISNA; } //PERMISSIONS if (_e2p_find_get_flag (MODE_NOT_P)) //, rt)) data->permop = ISNE; else if (_e2p_find_get_flag (MODE_IS_P)) //, rt)) data->permop = ISEQ; else data->permop = ISGE; //"include" mode mode_t mask[12] = {S_ISUID, S_ISGID, S_ISVTX, S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, S_IXGRP, S_IROTH, S_IWOTH, S_IXOTH}; mode_t mode = 0; gint i; for (i = 0; i < 12; i++) { if (_e2p_find_get_flag (i + (gint) PERMISSIONS1)) //, rt)) mode |= mask[i]; data->permtarget = mode; } if (data->permop == ISGE && mode == 0) data->permop = ISNA; //include nothing is the "ignore" setting //FILETYPES //FIXME support negation //can't store choices in a mode_t, that has shared bits guint c = 0; gint p = (gint) REGULAR_P; //REGULAR_P, DIRECTORY_P, RAW_DEVICE_P, BLOCK_DEVICE_P, SYMLINK_P, SOCKET_P, FIFO_P for (i = 0; i < 7 ; i++) { if (_e2p_find_get_flag (i + p)) //, rt)) c |= 1 << i; } data->typetarget = c; if (c > 0) { #ifdef TRACKERFIND data->tracker_service = -1; if (c == 1) //only want regular files { data->tracker_service = gtk_combo_box_get_active (GTK_COMBO_BOX (rt->service_combo)); if (data->tracker_service != -1 && data->tracker_service != 0) //all files setting { data->typeop = TRAK; } } if (data->tracker_service <= 0) { #endif if (_e2p_find_get_flag (TYPE_NOT_P)) data->typeop = ISNE; else data->typeop = ISEQ; #ifdef TRACKERFIND } #endif } else { data->typeop = ISNA; } } /* * @brief add surrounding quotes to @a original if needed @a original must be freeable, as it is replaced if need be @param original the string to check @return @a original, or a newly-allocated replacement */ /*static gchar *_e2p_find_quote_string (gchar *original) { gboolean gap = (e2_utils_find_whitespace (original) != NULL); if (!gap) return original; if (*original == '"' && *(original + strlen (original) - 1) == '"') return original; gchar *quoted = g_strconcat ("\"", original, "\"", NULL); g_free (original); return quoted; } */ /** @brief check if a specified year is a leap year This was 'imported' from utils @param year gint value of year to be checked @return TRUE if @a year is a leap year */ static gboolean _e2p_find_check_leapyear (gint year) { return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)); } /** @brief set specified flag to T/F The relevant array value is set @param f enumerated value of flag to be set @param value new value for the flag @param rt UNUSED pointer to dialog data struct @return */ static void _e2p_find_set_flag (findflag_t f, gboolean value) //, E2_FindDialogRuntime *rt) { if (f < MAX_FLAGS) flags [(gint) f] = value; } /** @brief return the value of a specified flag @param f enumerated value of flag to be interrogated @param rt UNUSED pointer to dialog data struct @return flag value, T/F, or FALSE if the value is not recognised */ static gboolean _e2p_find_get_flag (findflag_t f) //, E2_FindDialogRuntime *rt) { if (f < MAX_FLAGS) return (flags[(gint) f]); else return (FALSE); } /** @brief set all flags to FALSE @param rt UNUSED pointer to dialog data struct @return */ static void _e2p_find_reset_flags (void) //E2_FindDialogRuntime *rt) { gint i; for (i = 0; i < MAX_FLAGS; i++) { /* if (i == (gint) SEARCH_SUBDIRS_P || i == (gint) PRINT_TO_WINDOW_P || i == (gint) SHELL_COMMAND_P || i == (gint) PRINT_TO_STDOUT_P || i == (gint) PRINT_FILENAME_ANYWAY_P || i == (gint) WARNING_WINDOW_P || i == (gint) LONG_OUTPUT_P ) ; // do nothing else */ flags[i] = FALSE; } } /** @brief determine date/time value from a specified set of spinners @param choice ptr to store for the calculated timeval @param times ptr to spinners data struct, with time to be interrogated @return */ static gboolean _e2p_find_get_datetime (time_t *choice, spinners *times) { gboolean retval; GDate *target = g_date_new (); g_date_set_dmy (target, gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (times->day_spin)), gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (times->month_spin)), gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (times->year_spin)) ); if (g_date_valid (target)) { struct tm tm; g_date_to_struct_tm (target, &tm); tm.tm_hour = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (times->hour_spin)); tm.tm_min = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (times->minute_spin)); tm.tm_sec = 0; *choice = mktime (&tm); retval = TRUE; } else { *choice = 0; retval = FALSE; } g_date_free (target); return retval; } /** @brief create local store of current date parameters @param current ptr to struct for storage of current date/time data @return */ static void _e2p_find_get_current_datetime (local_dt *current) { /* portable, but buggy, version (hours not correct) g_date_clear (&cur_date, 1); g_date_set_time (&cur_date, time (NULL)); g_get_current_time (&cur_time); current->day = (gfloat) g_date_get_day (&cur_date); current->month = (gfloat) g_date_get_month (&cur_date); current->year = (gfloat) g_date_get_year (&cur_date); gint h1 = cur_time.tv_sec/3600; gint m1 = (cur_time.tv_sec - h1*3600)/60; gint s1 = cur_time.tv_sec - h1*3600 - m1 *60; current->hour = (gfloat) h1; current->minute = (gfloat) m1; current->second = (gfloat) s1; */ time_t now = time (NULL); struct tm *tm = localtime (&now); current->day = (gfloat) tm->tm_mday; current->month = (gfloat) tm->tm_mon + 1; current->year = (gfloat) tm->tm_year + 1900; current->hour = (gfloat) tm->tm_hour; current->minute = (gfloat) tm->tm_min; // current->second = (gfloat) tm->tm_sec; } /** @brief reset each resettable widget in the dialog This applies recursively to all container widgets The reset fn is determined from the widget's associated "reset_yourself" data, if any @param widget to be processed @param data UNUSED data for the foreach fn @return */ static void _e2p_find_reset_all_widgets (GtkWidget *widget, gpointer data) { if (GTK_IS_CONTAINER (widget)) gtk_container_foreach (GTK_CONTAINER (widget), _e2p_find_reset_all_widgets, NULL); gpointer (*reset_function) () = g_object_get_data (G_OBJECT (widget), "reset_yourself"); if (reset_function != NULL) (*reset_function) (widget); } /** @brief reset an entry widget content to "" @param widget the entry to be cleared @return */ static void _e2p_find_reset_entry (GtkWidget *widget) { gtk_entry_set_text (GTK_ENTRY (widget), ""); } #ifdef TRACKERFIND /** @brief reset a combobox widget content to indefined (-1) history-item @param widget the entry to be changed @return */ static void _e2p_find_reset_combo (GtkWidget *widget) { gtk_combo_box_set_active (GTK_COMBO_BOX (widget), -1); } #endif /** @brief set toggle-button widget state to FALSE @param widget the widget to be changed @return */ static void _e2p_find_set_toggle_button_off (GtkWidget *widget) { gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), FALSE); } /** @brief set toggle-button widget state to TRUE @param widget the widget to be changed @return */ static void _e2p_find_set_toggle_button_on (GtkWidget *widget) { gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), TRUE); } /** @brief set spin-button widget state to its default value The value is determined from the widget's associated "default_value" data @param widget the widget to be changed @return */ static void _e2p_find_reset_spin_button (GtkWidget *widget) { gfloat *value = g_object_get_data (G_OBJECT (widget), "default_value"); gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), *value); } /* * @brief @param w @param filesel @return */ /*OMIT? static void save_search_ok (GtkWidget *w, GtkFileSelection *filesel) { const gchar *s = gtk_file_selection_get_filename (GTK_FILE_SELECTION (filesel)); if (strlen (s) > 0) { save_search_command (s); gtk_widget_destroy (GTK_WIDGET (filesel)); } } */ /* * @brief @param w @param filesel @return */ /*OMIT ? static void save_search_cancel (GtkWidget *w, GtkFileSelection *filesel) { gtk_widget_destroy (GTK_WIDGET (filesel)); } */ /*****************/ /*** callbacks ***/ /*****************/ /** @brief helper function for recursive directory finds The tree is being scanned breadth-first, no link-through. Dirs are made accessible if not already so and it's permitted, Altered dirs are added to a list to be reverted after all the tree has been traversed, other items are changed as requested (if possible) Error messages expect BGL open @param localpath absolute path of item to change, localised string @param statptr pointer to struct stat with info about @a localpath @param status code from the walker, indicating what type of report it is @param user_data pointer to user-specified data for for the operation @return completion code: E2TW_CONTINUE if succeeds, others as appropriate */ static E2_TwResult _e2p_find_twcb (VPATH *localpath, const struct stat *statptr, E2_TwStatus status, findtargets *user_data) { if (user_data->aborted) return E2TW_STOP; //probably irrelevant now, with thread cancellation E2_TwResult retval; E2_ERR_DECLARE retval = E2TW_CONTINUE; //default error code = none switch (status) { E2_DirEnt *dirfix; GList *member; case E2TW_DP: //dir completed //chown and revert dir's permissions, cleanup for (member = g_list_last (user_data->dirdata); member != NULL; member = member->prev) { dirfix = (E2_DirEnt *)member->data; if (dirfix != NULL) { if (g_str_equal (dirfix->path, localpath)) { if (e2_fs_chmod (localpath, dirfix->mode E2_ERR_PTR()) && E2_ERR_ISNOT (ENOENT)) { e2_fs_error_local (_("Cannot change permissions of %s"), localpath E2_ERR_MSGL()); retval = E2TW_FIXME; } g_free (dirfix->path); DEALLOCATE (E2_DirEnt, dirfix); user_data->dirdata = g_list_delete_link (user_data->dirdata, member); break; } } // else //should never happen CHECKME ok when walking list ? // user_data->dirdata = g_list_delete_link (user_data->dirdata, member); } break; case E2TW_DRR: //directory now readable case E2TW_D: //before changing permissions //(though they've already been changed for a DRR case) _e2p_find_match1 (localpath, statptr, user_data); //ensure dir is writable, if we can if (e2_fs_tw_adjust_dirmode (localpath, statptr, (W_OK | X_OK)) == 0) { //FIXME warn user about failure retval = E2TW_SKIPSUB; //don't try to do any descendant } else //dir can be processed { //add this dir to list of items to revert afterwards dirfix = ALLOCATE (E2_DirEnt); CHECKALLOCATEDWARNT (dirfix, retval=E2TW_STOP;break;) dirfix->path = g_strdup (VPSTR(localpath)); dirfix->mode = statptr->st_mode & ALLPERMS; //want to restore the original value user_data->dirdata = g_list_append (user_data->dirdata, dirfix); } break; case E2TW_DM: //dir not opened (reported upstream) case E2TW_DL: //ditto case E2TW_DNR: //unreadable directory (for which, error is reported upstream) case E2TW_F: case E2TW_SL: //valid and invalid links case E2TW_SLN: //broken links (CHECKME not reported as E2TW_PHYS is used) _e2p_find_match1 (localpath, statptr, user_data); break; case E2TW_NS: //un-statable item (for which, error is reported upstream) //(note - this is a physical walk, no link-through problem here) _e2p_find_match1 (localpath, NULL, user_data); retval = E2TW_FIXME; break; default: retval = E2TW_STOP; break; } #ifdef E2_VFS if (user_data->operr != NULL && *(user_data->operr) == NULL) *(user_data->operr) = E2_ERR_NAME; else E2_ERR_CLEAR #endif if (user_data->aborted) return E2TW_STOP; //probably irrelevant now, with thread cancellation // if (retval & E2TW_SKIPSUB) // user_data->continued_after_problem = TRUE; if (retval & E2TW_FIXME) { // user_data->continued_after_problem = TRUE; retval &= ~E2TW_FIXME; //continue after bad item } return retval; } /** @brief adjust directory string in @a entry After a keypress, this clears any selection and completes the path. If the current content is not an absolute path, the active-pane directory is referenced for completion. @param entry the entry widget for directory data @param event pointer to event data struct @param data UNUSED data specified when callback was connnected @return TRUE if the key was non-modified and a textkey */ static gboolean _e2p_find_key_press_cb (GtkWidget *entry, GdkEventKey *event, gpointer data) { if ((event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)) == 0 //#ifdef USE_GTK2_10 | GDK_SUPER_MASK | GDK_HYPER_MASK | GDK_META_MASK && (event->keyval < 0xF000 || event->keyval > 0xFFFF) && e2_fs_complete_dir (entry, event->keyval, 0)) //default is active pane return TRUE; return FALSE; } /** @brief idle-callack for deferred cleanup of runtime data @param rt pointer to data to be cleaned @return FALSE after cleanup has been done */ static gboolean _e2p_find_deferclean (E2_FindDialogRuntime *rt) { if (rt->matchdata != NULL) //search thread still going { usleep (50000); return TRUE; } DEALLOCATE (E2_FindDialogRuntime, rt); return FALSE; } /** @brief cleanup after cancel button is pressed. or window has been closed Dialog destruction requires BGL closed upon arrival here Note that when closing the dialog during a running search, this func can operate in parallel with _e2p_find_cleanfind() @param widget clicked button, UNUSED @param rt ptr to dialog data struct @return */ static void _e2p_find_quit_cb (GtkWidget *widget, E2_FindDialogRuntime *rt) { //clear current strings array and cache data list e2_list_free_with_data (&strings); guint i; for (i = 0; i < MAX_ENTRIES; i++) g_free (entries[i]); //update array and list entries [NAME_ENTRY] = g_strdup (gtk_entry_get_text (GTK_ENTRY (rt->pattern))); entries [CONTENT_ENTRY] = g_strdup (gtk_entry_get_text (GTK_ENTRY (rt->content_pattern))); #ifdef TRACKERFIND entries [CONTENT_ENTRY2] = (rt->content_pattern2 != NULL) ? g_strdup (gtk_entry_get_text (GTK_ENTRY (rt->content_pattern2))): g_strdup (""); //must survive freeing inside loop #endif entries [MIME_ENTRY] = g_strdup (gtk_entry_get_text (GTK_ENTRY (rt->mime_entry))); entries [SIZE_ENTRY] = g_strdup (gtk_entry_get_text (GTK_ENTRY (rt->size_entry))); entries [UID_ENTRY] = g_strdup (gtk_entry_get_text (GTK_ENTRY (rt->user_entry))); entries [GID_ENTRY] = g_strdup (gtk_entry_get_text (GTK_ENTRY (rt->group_entry))); gchar *cachethis; for (i = 0; i < MAX_ENTRIES; i++) { //empty or space-only strings don't get saved if (i != NAME_ENTRY) g_strstrip (entries[i]); if (*entries[i] == '\0') cachethis = g_strdup ("."); //so we use this instead else cachethis = g_strdup (entries[i]); strings = g_list_append (strings, cachethis); } if (rt->groups != NULL) { //cleanup data stored with some buttons GSList *tmp, *members; for (tmp = rt->groups; tmp != NULL; tmp=tmp->next) { members = (GSList *) g_object_get_data (G_OBJECT (tmp->data), "group_members"); g_slist_free (members); } g_slist_free (rt->groups); } pthread_mutex_lock (&find_mutex); if (GTK_IS_WIDGET (rt->dialog)) //maybe the dialog window has been closed already gtk_widget_destroy (rt->dialog); if (rt->matchdata == NULL) //search thread has finished cleaning up { DEALLOCATE (E2_FindDialogRuntime, rt); } else g_idle_add ((GSourceFunc) _e2p_find_deferclean, rt); //cleanup later // rt = NULL; useless find_rt = NULL; pthread_mutex_unlock (&find_mutex); // gtk_widget_grab_focus (curr_view->treeview); } /** @brief dialog response callback @param dialog the dialog where the response was initiated, UNUSED @param response the number assigned to the widget which triggered the response @param rt ptr to dialog data struct @return */ static void _e2p_find_response_cb (GtkDialog *dialog, gint response, E2_FindDialogRuntime *rt) { // printd (DEBUG, "Find plugin dialog response cb, response %d", response); switch (response) { case GTK_RESPONSE_CLOSE: case GTK_RESPONSE_DELETE_EVENT: case GTK_RESPONSE_NONE: pthread_mutex_lock (&find_mutex); //cancel running search, if any, no buttons update if (rt->matchdata != NULL) { //a search is in progress rt->matchdata->aborted = TRUE; //probably irrelevant now, with thread cancellation pthread_cancel (rt->matchdata->findID); } pthread_mutex_unlock (&find_mutex); _e2p_find_quit_cb (NULL, rt); //cleanup rt data default: break; } } /** @brief toggle specified option flag after a radio or normal toggle button is clicked @param widget clicked button, UNUSED @param flagnum pointerized number of the flag to be toggled @return */ static void _e2p_find_toggle_cb (GtkWidget *widget, gpointer flagnum) { //if this if this is during setup, before a widget is created ... if (!GTK_WIDGET_MAPPED (find_rt->dialog)) return; findflag_t flg = (findflag_t) flagnum; gboolean newflag = ! _e2p_find_get_flag (flg); //, find_rt); _e2p_find_set_flag (flg, newflag); //, find_rt); //handle here all the 'special cases', if any if (flg == SEARCH_THIS_P) gtk_widget_set_sensitive (find_rt->directory, newflag); #ifdef TRACKERFIND else if (flg == REGULAR_P) { if (find_rt->service_combo != NULL && _e2p_find_get_flag (TYPE_IS_P)) gtk_widget_set_sensitive (find_rt->service_combo, newflag); } #endif else if (newflag) { switch (flg) { case UID_ANY_P: case UID_NONE_P: case UID_LOGIN_P: gtk_widget_set_sensitive (find_rt->curr_user, FALSE); gtk_widget_set_sensitive (find_rt->choose_user, FALSE); gtk_widget_set_sensitive (find_rt->user_entry, FALSE); break; case UID_SPECIFIC_P: gtk_widget_set_sensitive (find_rt->curr_user, TRUE); gtk_widget_set_sensitive (find_rt->choose_user, TRUE); gtk_widget_set_sensitive (find_rt->user_entry, _e2p_find_get_flag (UID_NOT_LOGIN_P)); //, find_rt)); break; case GID_ANY_P: case GID_NONE_P: case GID_LOGIN_P: gtk_widget_set_sensitive (find_rt->curr_group, FALSE); gtk_widget_set_sensitive (find_rt->choose_group, FALSE); gtk_widget_set_sensitive (find_rt->group_entry, FALSE); break; case GID_SPECIFIC_P: gtk_widget_set_sensitive (find_rt->curr_group, TRUE); gtk_widget_set_sensitive (find_rt->choose_group, TRUE); gtk_widget_set_sensitive (find_rt->group_entry, _e2p_find_get_flag (GID_NOT_LOGIN_P)); //, find_rt)); #ifdef TRACKERFIND case TYPE_IS_P: if (find_rt->service_combo != NULL && _e2p_find_get_flag (REGULAR_P)) gtk_widget_set_sensitive (find_rt->service_combo, TRUE); break; case TYPE_NOT_P: if (find_rt->service_combo != NULL) gtk_widget_set_sensitive (find_rt->service_combo, FALSE); #endif default: break; } } } /** @brief toggle specified option flag after a grouped toggle button is clicked @param widget clicked button @param flagnum pointerized number of the flag to be toggled @return */ static void _e2p_find_grouptoggle_cb (GtkWidget *widget, gpointer flagnum) { findflag_t flg = (findflag_t) flagnum; gboolean newflag = ! _e2p_find_get_flag (flg); //, find_rt); _e2p_find_set_flag (flg, newflag); //, find_rt); if (newflag) { //clear all other members of the group GtkWidget *tmp = g_object_get_data (G_OBJECT (widget), "group_leader"); GSList *members = g_object_get_data (G_OBJECT (tmp), "group_members"); for (;members != NULL; members = members->next) { tmp = members->data; if (tmp != widget) _e2p_find_set_toggle_button_off (tmp); } } //handle here all the 'special cases', if any switch (GPOINTER_TO_INT(flagnum)) { case UID_LOGIN_P: newflag = (newflag) ? FALSE : ! _e2p_find_get_flag (UID_NOT_LOGIN_P); //, find_rt) ; gtk_widget_set_sensitive (find_rt->user_entry, newflag); break; case UID_NOT_LOGIN_P: gtk_widget_set_sensitive (find_rt->user_entry, newflag); break; case GID_LOGIN_P: newflag = (newflag) ? FALSE : ! _e2p_find_get_flag (GID_NOT_LOGIN_P); //, find_rt) ; gtk_widget_set_sensitive (find_rt->group_entry, newflag); break; case GID_NOT_LOGIN_P: gtk_widget_set_sensitive (find_rt->group_entry, newflag); default: break; } } /* * @brief bring up a system find-file window to choose the directory from which to search @param w activated widget, UNUSED @param rt ptr to dialog data struct @return */ /*static void _e2p_find_choose_directory_cb (GtkWidget *w, E2_FindDialogRuntime *rt) { GtkWidget *dialog = gtk_file_chooser_dialog_new (NULL, GTK_WINDOW (rt->dialog), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_OK, NULL); e2_dialog_setup_chooser (dialog, _("choose directory"), (utf-8) const gchar *filename, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, TRUE, //show hidden FALSE, //single-selection GTK_RESPONSE_OK); //default response gint response; while ((response = gtk_dialog_run (GTK_DIALOG (dialog))) == E2_RESPONSE_USER1) {} if (response == GTK_RESPONSE_OK) { gchar *local = gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (dialog)); if (strlen (local) > 0) { gchar *opendir = F_FILENAME_FROM_LOCALE (local); gtk_entry_set_text (GTK_ENTRY (rt->directory), opendir); F_FREE (opendir); } g_free (local); } gtk_widget_destroy (dialog); } */ /** @brief callback for find clear action the "clear" button will reset the search pattern (ie the notebook stuff), and not change anything else @param w activated widget, UNUSED @param rt ptr to dialog data struct @return */ static void _e2p_find_clear_find_cb (GtkWidget *w, E2_FindDialogRuntime *rt) { _e2p_find_reset_all_widgets (rt->dialog, NULL); } /** @brief revert dialog widgets ready for a new search Expects BGL open on arrival here @param rt ptr to dialog data struct @return */ static void _e2p_find_reset_widgets (E2_FindDialogRuntime *rt) { if (GTK_IS_WIDGET (rt->dialog)) //sometimes we're cleaning up after a close-button click { gdk_threads_enter (); e2_dialog_set_cursor (rt->dialog, GDK_LEFT_PTR); gtk_widget_set_sensitive (rt->help_button, TRUE); gtk_widget_set_sensitive (rt->start_button, TRUE); gtk_widget_set_sensitive (rt->stop_button, FALSE); gdk_threads_leave (); } } /** @brief callback for find button click Parses the selected options and initiates search in a separate thread @param button clicked widget @param rt ptr to search dialog data struct (in case of a bad race, rt could be NULL) @return */ static void _e2p_find_find_cb (GtkWidget *button, E2_FindDialogRuntime *rt) { gchar *tmp2, *dlocal; findtargets data; pthread_mutex_lock (&find_mutex); if (rt == NULL) { pthread_mutex_unlock (&find_mutex); return; } //setup search parameters _e2p_find_get_match_data (&data, rt); if ( data.contentop == ISNA && data.nameop == ISNA #ifdef MIMEFIND && data.mimeop == ISNA #endif && data.sizeop == ISNA && data.permop == ISNA && data.mtimop == ISNA && data.atimop == ISNA && data.ctimop == ISNA && data.userop == ISNA && data.groupop == ISNA && data.typeop == ISNA ) { pthread_mutex_unlock (&find_mutex); return; } #ifdef E2_VFSTMP FIXME decide how to handle namespaces esp. when startpath is not active or inactive dir confirm walker is ok for v-dirs warn about content finding for v-dirs #endif //decide where to start the search if (_e2p_find_get_flag (SEARCH_ALL_P)) //, rt)) { dlocal = g_strdup (G_DIR_SEPARATOR_S); #ifdef E2_VFS data.sdata.spacedata = curr_view->spacedata; #endif } else { if (_e2p_find_get_flag (SEARCH_CURRENT_P)) //, rt)) { dlocal = D_FILENAME_TO_LOCALE (curr_view->dir); #ifdef E2_VFS data.sdata.spacedata = curr_view->spacedata; #endif } else if (_e2p_find_get_flag (SEARCH_OTHER_P)) //, rt)) { dlocal = D_FILENAME_TO_LOCALE (other_view->dir); #ifdef E2_VFS sdata.spacedata = other_view->spacedata; #endif } else { const gchar *tmp = gtk_entry_get_text (GTK_ENTRY (rt->directory)); if (*tmp == '\0') return; dlocal = D_FILENAME_TO_LOCALE (tmp); #ifdef E2_VFS data.sdata.spacedata = curr_view->spacedata; #endif } //scrub any trailing separator from the start path tmp2 = dlocal + strlen (dlocal) - sizeof (gchar); if (tmp2 > dlocal && *tmp2 == G_DIR_SEPARATOR) *tmp2 = '\0'; #ifdef E2_VFS data.sdata.localpath = dlocal; #endif } #ifndef E2_VFS data.localstartpath = dlocal; #endif if (_e2p_find_get_flag (SEARCH_SUBDIRS_P)) //, rt)) data.searchdepth = -1; //unlimited else data.searchdepth = 1; pthread_mutex_unlock (&find_mutex); rt->matchdata = ALLOCATE (findtargets); CHECKALLOCATEDWARN (rt->matchdata, return;) *rt->matchdata = data; gtk_widget_set_sensitive (rt->help_button, FALSE); gtk_widget_set_sensitive (rt->start_button, FALSE); gtk_widget_set_sensitive (rt->stop_button, TRUE); e2_dialog_set_cursor (rt->dialog, GDK_WATCH); //do it - separate thread allows the search to proceed without blocking the UI pthread_attr_t attr; pthread_attr_init (&attr); pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); pthread_create (&rt->matchdata->findID, &attr, (gpointer) _e2p_find_dofind, rt); } /** @brief thread function to perform search Uses grep and/or ?? for mimetype, content @param rt ptr to search dialog data struct (in case of a bad race, rt could be NULL) @return NULL */ static gpointer _e2p_find_dofind (E2_FindDialogRuntime *rt) { if (rt == NULL) return NULL; pthread_cleanup_push ((gpointer)_e2p_find_cleanfind, (gpointer)rt); pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL); e2_utils_block_thread_signals (); //block all allowed signals to this thread findtargets *fdata = rt->matchdata; //get a copy in case rt goes during search #ifdef TRACKERFIND if (fdata->contentop == TRAK || fdata->typeop == TRAK) { //tracker can't do place-specific matching _e2p_find_tracker_find ( #ifdef E2_VFS fdata->sdata, #else fdata->localstartpath, #endif fdata->searchdepth, fdata); } else { #endif e2_fs_tw ( #ifdef E2_VFS fdata->sdata, #else fdata->localstartpath, #endif _e2p_find_twcb, fdata, fdata->searchdepth, E2TW_XQT | E2TW_NODIR | E2TW_PHYS E2_ERR_NONE()); if (fdata->dirdata != NULL) { //cleanup chmod data left over after a stop or tw problem GList *member; E2_DirEnt *dirfix; for (member = g_list_last (fdata->dirdata); member != NULL; member = member->prev) { dirfix = (E2_DirEnt *)member->data; if (dirfix != NULL) { E2_ERR_DECLARE; #ifdef E2_VFS fdata->sdata.localpath = dirfix->path; if (e2_fs_chmod (fdata->sdata, dirfix->mode E2_ERR_PTR()) #else if (e2_fs_chmod (dirfix->path, dirfix->mode E2_ERR_PTR()) #endif && E2_ERR_ISNOT (ENOENT)) { e2_fs_error_local (_("Cannot change permissions of %s"), #ifdef E2_VFS fdata->sdata E2_ERR_MSGL()); #else dirfix->path E2_ERR_MSGL()); #endif } g_free (dirfix->path); DEALLOCATE (E2_DirEnt, dirfix); } } g_list_free (fdata->dirdata); } #ifdef TRACKERFIND } #endif gdk_threads_enter (); e2_output_print_end (&app.tab, FALSE); gdk_threads_leave (); _e2p_find_reset_widgets (rt); //if thread not aborted, get the buttons etc back to normal pthread_cleanup_pop (1); //cleanup match data return NULL; } /** @brief end-of-thread function to cleanup match data Note that when closing the dialog during a running search, this func can operate in parallel with _e2p_find_quit_cb() @param rt ptr to search dialog data struct (in case of a bad race, rt could be NULL) @return */ static void _e2p_find_cleanfind (E2_FindDialogRuntime *rt) { printd (DEBUG, "cleanup after find thread finish"); pthread_mutex_lock (&find_mutex); if (rt != NULL) { //dialog not closed during the search //clear data ptr ASAP, minimise misuse of data by cleanup function findtargets *fdata = rt->matchdata; rt->matchdata = NULL; // _e2p_find_clear_match_data (fdata); if (fdata != NULL) { if (fdata->nametarget != NULL) g_free (fdata->nametarget); if (fdata->nameop == REGX) regfree (&fdata->compiledname); #ifdef MIMEFIND if (fdata->mimetarget != NULL) g_free (fdata->mimetarget); #endif if (fdata->contenttarget != NULL) g_free (fdata->contenttarget); // if (fdata->contentop == REGX) // regfree (&fdata->compiledcontent); #ifdef E2_VFS g_free (VPSTR(fdata->sdata)); #else g_free (fdata->localstartpath); #endif DEALLOCATE (findtargets, fdata); } } pthread_mutex_unlock (&find_mutex); } /** @brief callback for stop find button click @param w activated widget, UNUSED @param rt ptr to dialog data struct @return */ static void _e2p_find_stop_find_cb (GtkWidget *w, E2_FindDialogRuntime *rt) { pthread_mutex_lock (&find_mutex); if (rt->matchdata != NULL) { //a search is in progress rt->matchdata->aborted = TRUE; //probably irrelevant now, with thread cancellation pthread_cancel (rt->matchdata->findID); } gdk_threads_leave (); _e2p_find_reset_widgets (rt); gdk_threads_enter (); pthread_mutex_unlock (&find_mutex); } /** @brief callback for help button executes external command "man gtkfind" @param w activated widget, UNUSED @param rt ptr to dialog data struct @return */ static void _e2p_find_help_cb (GtkWidget *w, E2_FindDialogRuntime *rt) { //these are components of help-file headings //_I( no translation until help docs are translated static gchar *msg[10] = { "name", //page 0 "content", //page 1 #ifdef MIMEFIND "mime", //page 2 #endif "mtime", //page 3 "atime", //page 4 "ctime", //page 5 "size", //page 6 "permission", //page 7 "owner", //page 8 "type" //page 9 }; gint page = gtk_notebook_get_current_page (GTK_NOTEBOOK (rt->notebook)); //_I( no translation until help docs are translated gchar *title = g_strconcat ("find by ", msg[page], NULL); e2_utils_show_help (title); g_free (title); } /* * @brief @return */ /*static void _e2p_find_save_search_cb (E2_FindDialogRuntime *rt) { // GtkWidget *filesel = _e2p_find_create_filesel (_("Save Search"), save_search_ok, save_search_cancel, rt); } */ /** @brief callback for year changed signal This is needed to handle any February in a leap year @param widget year spin-button widget @param callback_data ptr to one of the day_spin widgets @return */ static void _e2p_find_year_changed_cb (GtkWidget *widget, spinners *times) { gint max_day; gint month = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (times->month_spin)); if (month == 2) { //we only need to do something about February if (_e2p_find_check_leapyear (gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (widget)))) max_day = 29; else max_day = 28; gint day = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (times->day_spin)); if (day > max_day) { day = max_day; gtk_spin_button_set_value (GTK_SPIN_BUTTON (times->day_spin), (gfloat) day); } GtkAdjustment* nadj = (GtkAdjustment *) gtk_adjustment_new ((gfloat) day, 1, max_day, 1.0, 2.0, 0.0); gtk_spin_button_set_adjustment (GTK_SPIN_BUTTON (times->day_spin), nadj); } } /** @brief callback for month changed signal This is needed to handle any February in a leap year @param widget month spin-button widget @param callback_data ptr to one of the day_spin widgets @return */ static void _e2p_find_month_changed_cb (GtkWidget *widget, spinners *times) { gint nvalue; gint max_date; gint month = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (widget)); gint ovalue = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (times->day_spin)); if (month == 2) { if (_e2p_find_check_leapyear (gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (times->year_spin)))) max_date = 29; else max_date = 28; } else if (month == 4 || month == 6 || month == 9 || month == 11) max_date = 30; else max_date = 31; if (ovalue > max_date) { nvalue = max_date; gtk_spin_button_set_value (GTK_SPIN_BUTTON (times->day_spin), (gfloat) nvalue); } else nvalue = ovalue; GtkAdjustment *nadj = (GtkAdjustment *) gtk_adjustment_new ( (gfloat) nvalue, 1, max_date, 1.0, 2.0, 0.0); gtk_spin_button_set_adjustment (GTK_SPIN_BUTTON (times->day_spin), nadj); } /** @brief callback for notebook page-switched signal @param notebook UNUSED the book whose page has changed @param page UNUSED the new page @param page_num the index of the new page @param page_store pointer to store for @a page_num @return */ static void _e2p_find_pagechange_cb (GtkNotebook *notebook, GtkNotebookPage *page, guint page_num, gint *page_store) { *page_store = page_num; } /***********************/ /*** widget creation ***/ /***********************/ #ifdef USE_SHELL /** @brief make the widgets associated with running shell commands @param parent the widget into which the new widgets wll be packed @return */ static void _e2p_find_make_shell_widgets (GtkWidget *parent) { // output format selection GtkWidget *hbox = _e2p_find_create_hbox (parent); GtkWidget *radio = _e2p_find_create_radio_button (hbox, FIXME //no _() SHORT_OUTPUT_P, TRUE, _("Print only filename")); // GSList *list = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio)); //no _() radio = gtk_radio_button_new_with_label (list, _("Print extra data")); g_signal_connect (G_OBJECT (radio), "toggled", G_CALLBACK (_e2p_find_toggle_cb), (gpointer) LONG_OUTPUT_P); //need a reset for radio ?? gtk_container_add (GTK_CONTAINER (hbox), radio); gtk_widget_show (radio); // shell command stuff _e2p_find_create_toggle_button (parent, //no _() SHELL_COMMAND_P, FALSE, _("Run a shell command?")); hbox = _e2p_find_create_hbox (parent); //no _() GtkWidget *label = _e2p_find_create_label (hbox, _("Shell command:")); shell_command = _e2p_find_create_entry (hbox. ""); #ifdef E2_ASSISTED // e2_widget_set_label_relations (GTK_LABEL (label2), shell_command); #endif hbox = _e2p_find_create_hbox (parent); _e2p_find_create_toggle_button (hbox, //no _() PRINT_TO_STDOUT_P, FALSE, _("Print to stdout")); _e2p_find_create_toggle_button (hbox, //no _() PRINT_TO_WINDOW_P, TRUE, _("Print to window")); _e2p_find_create_toggle_button (parent, //no _() PRINT_FILENAME_ANYWAY_P, FALSE, _("Always print filename")); } #endif //def USE_SHELL /** @brief make the widgets involved with choosing the directory to search @param box the widget into which the new widgets wll be packed @param rt ptr to dialog data struct @return */ static void _e2p_find_make_directory_widgets (GtkWidget *box, E2_FindDialogRuntime *rt) { #ifdef E2_ASSISTED GtkWidget *label2 = #endif e2_widget_add_mid_label (box, _("Search for items:"), 0.0, FALSE, E2_PADDING_SMALL); GtkWidget *radio = _e2p_find_create_radio_button (box, NULL, SEARCH_ALL_P, FALSE, _("any_where"), rt); rt->active_button = _e2p_find_create_radio_button (box, radio, SEARCH_CURRENT_P, TRUE, _("in _active directory"), rt); #ifdef E2_VFSTMP if (curr_view->spacedata != NULL) gtk_widget_set_sensitive (rt->active_button, FALSE); //FIXME later, change sensitivity if curr_view->fs changes rt->something = #endif _e2p_find_create_radio_button (box, radio, SEARCH_OTHER_P, FALSE, _("in _other directory"), rt); #ifdef E2_VFSTMP if (other_view->spacedata != NULL) gtk_widget_set_sensitive (rt->something, FALSE); //FIXME later, change sensitivity if other_view->fs changes #endif rt->thisdir_button = _e2p_find_create_radio_button (box, radio, SEARCH_THIS_P, FALSE, _("in _this directory"), rt); // GtkWidget *button = _e2p_find_create_button (hbox, _e2p_find_choose_directory_cb, rt); // _e2p_find_create_label (button, _("Choose Directory")); rt->directory = _e2p_find_create_entry (box, ""); //#ifdef E2_VFSTMP //FIXME dir when not mounted local //#else // gtk_entry_set_text (GTK_ENTRY (rt->directory), curr_view->dir); //#endif #ifdef E2_ASSISTED e2_widget_set_label_relations (GTK_LABEL (label2), rt->directory); #endif // if (nocacheflags) // gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (rt->active_button), TRUE); // gboolean state = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rt->thisdir_button)); // gtk_widget_set_sensitive (rt->directory, state); //need to interpret keypresses for completion g_signal_connect (G_OBJECT (rt->directory), "key-press-event", G_CALLBACK (_e2p_find_key_press_cb), NULL); // _e2p_find_create_toggle_button (box, // SEARCH_SUBDIRS_P, TRUE, _("Recurse subdirectories"), rt); _e2p_find_create_toggle_button (box, SEARCH_SUBDIRS_P, TRUE, _("Recurse subdirectories"), rt); } /** @brief make the file search-criteria notebook and its subwidgets @param box the widget into which the notebook wll be packed @param rt ptr to dialog data struct @return */ static void _e2p_find_make_notebook (GtkWidget *box, E2_FindDialogRuntime *rt) { rt->notebook = e2_widget_get_notebook (_e2p_find_pagechange_cb, &page_store); gtk_notebook_set_tab_pos (GTK_NOTEBOOK (rt->notebook), GTK_POS_LEFT); gtk_box_pack_start (GTK_BOX (box), rt->notebook, TRUE, TRUE, 0); gtk_widget_show (rt->notebook); _e2p_find_make_name_tab (rt->notebook, rt); //page 0 _e2p_find_make_content_tab (rt->notebook, rt); //page 1 #ifdef MIMEFIND _e2p_find_make_mimetype_tab (rt->notebook, rt); //page 2 #endif _e2p_find_get_current_datetime (¤t); _e2p_find_make_mtime_tab (rt->notebook, rt); //page 3 _e2p_find_make_atime_tab (rt->notebook, rt); //page 4 _e2p_find_make_ctime_tab (rt->notebook, rt); //page 5 _e2p_find_make_size_tab (rt->notebook, rt); //page 6 _e2p_find_make_mode_tab (rt->notebook, rt); //page 7 _e2p_find_make_owner_tab (rt->notebook, rt); //page 8 _e2p_find_make_type_tab (rt->notebook, rt); //page 9 } /** @brief make notebook tab with itemname search options @param notebook the widget to which the tab wiil be added @param rt ptr to dialog data struct @return */ static void _e2p_find_make_name_tab (GtkWidget *notebook, E2_FindDialogRuntime *rt) { GtkWidget *label = gtk_label_new (_("name")); gtk_widget_show (label); GtkWidget *vbox = gtk_vbox_new (FALSE, E2_PADDING); gtk_widget_show (vbox); #ifdef E2_ASSISTED GtkWidget *label2 = #endif _e2p_find_create_label (vbox, _("Find items whose name:")); GtkWidget *hbox = _e2p_find_create_hbox (vbox); GtkWidget *radio = _e2p_find_create_radio_button (hbox, NULL, STRING_FILENAME_P, TRUE, _("is"), rt); _e2p_find_create_radio_button (hbox, radio, WILDCARD_FILENAME_P, FALSE, _("is like"), rt); _e2p_find_create_radio_button (hbox, radio, REGEXP_FILENAME_P, FALSE, _("matches this regex"), rt); _e2p_find_create_toggle_button (hbox, ANYCASE_FILENAME_P, FALSE, _("ignore case"), rt); hbox = _e2p_find_create_hbox (vbox); rt->pattern = _e2p_find_create_entry (hbox, entries [NAME_ENTRY]); #ifdef E2_ASSISTED e2_widget_set_label_relations (GTK_LABEL (label2), rt->pattern); #endif // hbox = _e2p_find_create_hbox (vbox); // _e2p_find_create_toggle_button (hbox, // ANYCASE_FILENAME_P, FALSE, _("ignore case"), rt); gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, label); } /** @brief make notebook tab with itemsize search options @param notebook notebook widget to which the tab wiil be added @param rt ptr to dialog data struct @return */ static void _e2p_find_make_size_tab (GtkWidget *notebook, E2_FindDialogRuntime *rt) { GtkWidget *label = gtk_label_new (_("size")); gtk_widget_show (label); GtkWidget *vbox = gtk_vbox_new (FALSE, E2_PADDING); gtk_widget_show (vbox); #ifdef E2_ASSISTED GtkWidget *label2 = #endif _e2p_find_create_label (vbox, _("Find items whose size is:")); GtkWidget *hbox = _e2p_find_create_hbox (vbox); // _e2p_find_create_toggle_button (hbox, GtkWidget *leader = _e2p_find_create_toggle_grouped_button (hbox, FSIZE_LT_P, FALSE, _("less than:"), NULL, rt); _e2p_find_create_toggle_button (hbox, FSIZE_EQ_P, FALSE, _("equal to:"), rt); // _e2p_find_create_toggle_button (hbox, _e2p_find_create_toggle_grouped_button (hbox, FSIZE_GT_P, TRUE, _("more than"), leader, rt); hbox = _e2p_find_create_hbox (vbox); rt->size_entry = _e2p_find_create_entry (hbox, entries [SIZE_ENTRY]); #ifdef E2_ASSISTED e2_widget_set_label_relations (GTK_LABEL (label2), rt->size_entry); #endif GtkWidget *radio = _e2p_find_create_radio_button (hbox, NULL, FSIZE_B_P, TRUE, _("bytes"), rt); _e2p_find_create_radio_button (hbox, radio, FSIZE_KB_P, FALSE, _("kbytes"), rt); _e2p_find_create_radio_button (hbox, radio, FSIZE_MB_P, FALSE, _("Mbytes"), rt); gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, label); } #ifdef MIMEFIND /** @brief make notebook tab with mimetype search options @param notebook notebook widget to which the tab wiil be added @param rt ptr to dialog data struct @return */ static void _e2p_find_make_mimetype_tab (GtkWidget *notebook, E2_FindDialogRuntime *rt) { GtkWidget *label = gtk_label_new (_("mime")); gtk_widget_show (label); GtkWidget *vbox = gtk_vbox_new (FALSE, E2_PADDING); gtk_widget_show (vbox); // GtkWidget *hbox = _e2p_find_create_hbox (vbox); #ifdef E2_ASSISTED GtkWidget *label2 = #endif _e2p_find_create_label (vbox, _("Find files whose mimetype is like this:")); GtkWidget *hbox = _e2p_find_create_hbox (vbox); rt->mime_entry = _e2p_find_create_entry (hbox, entries [MIME_ENTRY]); #ifdef E2_ASSISTED e2_widget_set_label_relations (GTK_LABEL (label2), rt->mime_entry); #endif // FIXME no lookup etc widgets ?? // GtkWidget *hbox = _e2p_find_create_hbox (vbox); gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, label); } #endif //def MIMEFIND /** @brief make notebook tab with file mtime search options Options are: earlier than and/or equal to, later than and/or equal to. By default, nothing is selected, so all mtimes will be matched @param notebook notebook widget to which the tab wiil be added @param rt ptr to dialog data struct @return */ static void _e2p_find_make_mtime_tab (GtkWidget *notebook, E2_FindDialogRuntime *rt) { GtkWidget *label = gtk_label_new (_("save")); gtk_widget_show (label); GtkWidget *vbox = gtk_vbox_new (FALSE, E2_PADDING); gtk_widget_show (vbox); _e2p_find_create_label (vbox, _("Find items most-recently saved:")); GtkWidget *hbox = _e2p_find_create_hbox (vbox); GtkWidget *leader = _e2p_find_create_toggle_grouped_button (hbox, MTIME_LT_P, FALSE, _("before:"), NULL, rt); _e2p_find_create_toggle_button (hbox, MTIME_EQ_P, FALSE, _("on/at:"), rt); _e2p_find_create_toggle_grouped_button (hbox, MTIME_GT_P, FALSE, _("after:"), leader, rt); _e2p_find_make_all_spinners (vbox, &rt->mtime); // .day_spin, &rt->mtime.month_spin, &rt->mtime.year_spin, // &rt->mtime.hour_spin, &rt->mtime.minute_spin); //, &rt->mtime_second_spin); gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, label); } /** @brief make notebook tab with file atime search options Options are: earlier than and/or equal to, later than and/or equal to. By default, nothing is selected, so all atimes will be matched @param notebook widget to which the tab wiil be added @param rt ptr to dialog data struct @return */ static void _e2p_find_make_atime_tab (GtkWidget *notebook, E2_FindDialogRuntime *rt) { GtkWidget *label = gtk_label_new (_("access")); gtk_widget_show (label); GtkWidget *vbox = gtk_vbox_new (FALSE, E2_PADDING); gtk_widget_show (vbox); _e2p_find_create_label (vbox, _("Find items most-recently read or executed:")); GtkWidget *hbox = _e2p_find_create_hbox (vbox); GtkWidget *leader = _e2p_find_create_toggle_grouped_button (hbox, ATIME_LT_P, FALSE, _("before:"), NULL, rt); _e2p_find_create_toggle_button (hbox, ATIME_EQ_P, FALSE, _("on/at:"), rt); _e2p_find_create_toggle_grouped_button (hbox, ATIME_GT_P, FALSE, _("after:"), leader, rt); _e2p_find_make_all_spinners (vbox, &rt->atime); // .day_spin, &rt->atime.month_spin, &rt->atime.year_spin, // &rt->atime.hour_spin, &rt->atime.minute_spin); //, &rt->atime.second_spin); gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, label); } /** @brief make notebook tab with filen ctime search options Options are: earlier than and/or equal to, later than and/or equal to. By default, nothing is selected, so all ctimes will be matched @param notebook notebook widget to which the tab wiil be added @param rt ptr to dialog data struct @return */ static void _e2p_find_make_ctime_tab (GtkWidget *notebook, E2_FindDialogRuntime *rt) { GtkWidget *label = gtk_label_new (_("change")); gtk_widget_show (label); GtkWidget *vbox = gtk_vbox_new (FALSE, E2_PADDING); gtk_widget_show (vbox); _e2p_find_create_label (vbox, _("Find items whose inode was last changed:")); GtkWidget *hbox = _e2p_find_create_hbox (vbox); GtkWidget *leader = _e2p_find_create_toggle_grouped_button (hbox, CTIME_LT_P, FALSE, _("before:"), NULL, rt); _e2p_find_create_toggle_button (hbox, CTIME_EQ_P, FALSE, _("on/at:"), rt); _e2p_find_create_toggle_grouped_button (hbox, CTIME_GT_P, FALSE, _("after"), leader, rt); _e2p_find_make_all_spinners (vbox, &rt->ctime); // .day_spin, &rt->ctime.month_spin, &rt->ctime.year_spin, // &rt->ctime.hour_spin, &rt->ctime.minute_spin); //, &rt->ctime_second_spin); gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, label); } /** @brief make notebook tab with file permissions search options By default, nothing is selected, so any permissions will be matched @param notebook widget to which the tab wiil be added @param rt ptr to dialog data struct @return */ static void _e2p_find_make_mode_tab (GtkWidget *notebook, E2_FindDialogRuntime *rt) { GtkWidget *label = gtk_label_new (_("permission")); gtk_widget_show (label); GtkWidget *vbox = gtk_vbox_new (FALSE, E2_PADDING); gtk_widget_show (vbox); _e2p_find_create_label (vbox, _("Find items whose permissions:")); GtkWidget *hbox = _e2p_find_create_hbox (vbox); GtkWidget *radio = _e2p_find_create_radio_button (hbox, NULL, MODE_IS_P, FALSE, _("are"), rt); _e2p_find_create_radio_button (hbox, radio, MODE_OR_P, TRUE, _("include"), rt); //include nothing = default setting _e2p_find_create_radio_button (hbox, radio, MODE_NOT_P, FALSE, _("exclude"), rt); hbox = _e2p_find_create_hbox (vbox); // read GtkWidget *vbox2 = _e2p_find_create_vbox (hbox); _e2p_find_create_toggle_button (vbox2, OWNER_READ_P, FALSE, _("owner read"), rt); _e2p_find_create_toggle_button (vbox2, GROUP_READ_P, FALSE, _("group read"), rt); _e2p_find_create_toggle_button (vbox2, WORLD_READ_P, FALSE, _("anyone read"), rt); // write vbox2 = _e2p_find_create_vbox (hbox); _e2p_find_create_toggle_button (vbox2, OWNER_WRITE_P, FALSE, _("owner write"), rt); _e2p_find_create_toggle_button (vbox2, GROUP_WRITE_P, FALSE, _("group write"), rt); _e2p_find_create_toggle_button (vbox2, WORLD_WRITE_P, FALSE, _("anyone write"), rt); // exec vbox2 = _e2p_find_create_vbox (hbox); _e2p_find_create_toggle_button (vbox2, OWNER_EXEC_P, FALSE, _("owner execute"), rt); _e2p_find_create_toggle_button (vbox2, GROUP_EXEC_P, FALSE, _("group execute"), rt); _e2p_find_create_toggle_button (vbox2, WORLD_EXEC_P, FALSE, _("anyone execute"), rt); // extra bits vbox2 = _e2p_find_create_vbox (hbox); _e2p_find_create_toggle_button (vbox2, SETUID_P, FALSE, _("setuid"), rt); _e2p_find_create_toggle_button (vbox2, SETGID_P, FALSE, _("setgid"), rt); _e2p_find_create_toggle_button (vbox2, STICKY_P, FALSE, _("sticky"), rt); gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, label); } /** @brief make notebook tab with file type search options By default, nothing is selected, so all types will be matched @param notebook widget to which the tab wiil be added @param rt ptr to dialog data struct @return */ static void _e2p_find_make_type_tab (GtkWidget *notebook, E2_FindDialogRuntime *rt) { GtkWidget *label = gtk_label_new (_("type")); gtk_widget_show (label); GtkWidget *vbox = gtk_vbox_new (FALSE, E2_PADDING); gtk_widget_show (vbox); _e2p_find_create_label (vbox, _("Find items which")); GtkWidget *hbox = _e2p_find_create_hbox (vbox); GtkWidget *radio = _e2p_find_create_radio_button (hbox, NULL, TYPE_IS_P, TRUE, _("are"), rt); _e2p_find_create_radio_button (hbox, radio, TYPE_NOT_P, FALSE, _("are not"), rt); hbox = _e2p_find_create_hbox (vbox); GtkWidget *subvbox = _e2p_find_create_vbox (hbox); //FIXME use a table to align items horizontally as well _e2p_find_create_toggle_button (subvbox, REGULAR_P, FALSE, _("regular"), rt); _e2p_find_create_toggle_button (subvbox, DIRECTORY_P, FALSE, _("directory"), rt); _e2p_find_create_toggle_button (subvbox, SYMLINK_P, FALSE, _("symlink"), rt); #ifdef TRACKERFIND _e2p_find_create_toggle_button (subvbox, BLOCK_DEVICE_P, FALSE, _("block device"), rt); #endif subvbox = _e2p_find_create_vbox (hbox); #ifdef TRACKERFIND if (rt->content_pattern2 != NULL) { //tracker's presence was successfully tested when content tab was created rt->service_combo = e2_combobox_add (subvbox, FALSE, E2_PADDING, NULL, NULL, NULL, E2_COMBOBOX_MENU_STYLE); guint i; for (i = 0; i < ACTIVE_TRACKER_SERVICES; i++) gtk_combo_box_append_text (GTK_COMBO_BOX (rt->service_combo), object_names[i]); gtk_combo_box_set_active (GTK_COMBO_BOX (rt->service_combo), service_index); g_object_set_data (G_OBJECT (rt->service_combo), "reset_yourself", _e2p_find_reset_combo); if (_e2p_find_get_flag (TYPE_NOT_P) || !_e2p_find_get_flag (REGULAR_P)) gtk_widget_set_sensitive (rt->service_combo, FALSE); } else rt->service_combo = NULL; #else _e2p_find_create_toggle_button (subvbox, BLOCK_DEVICE_P, FALSE, _("block device"), rt); #endif _e2p_find_create_toggle_button (subvbox, RAW_DEVICE_P, FALSE, _("raw device"), rt); _e2p_find_create_toggle_button (subvbox, SOCKET_P, FALSE, _("socket"), rt); _e2p_find_create_toggle_button (subvbox, FIFO_P, FALSE, _("fifo"), rt); gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, label); } /** @brief make notebook tab with file user/group search options By default, any user and any group are selected @param notebook widget to which the tab wiil be added @param rt ptr to dialog data struct @return */ static void _e2p_find_make_owner_tab (GtkWidget *notebook, E2_FindDialogRuntime *rt) { GtkWidget *label = gtk_label_new (_("owners")); gtk_widget_show (label); GtkWidget *vbox = gtk_vbox_new (FALSE, E2_PADDING); gtk_widget_show (vbox); #ifdef E2_ASSISTED GtkWidget *label2 = #endif _e2p_find_create_label (vbox, _("Find items with:")); GtkWidget *hbox = _e2p_find_create_hbox (vbox); //user GtkWidget *vbox2 = _e2p_find_create_vbox (hbox); GtkWidget *radio = _e2p_find_create_radio_button (vbox2, NULL, UID_ANY_P, TRUE, _("any user id"), rt); _e2p_find_create_radio_button (vbox2, radio, UID_SPECIFIC_P, FALSE, _("specific user id"), rt); find_rt->curr_user = _e2p_find_create_toggle_grouped_button (vbox2, UID_LOGIN_P, FALSE, _("current user's uid"), NULL, rt); find_rt->choose_user = _e2p_find_create_toggle_grouped_button (vbox2, UID_NOT_LOGIN_P, FALSE, _("this user id"), find_rt->curr_user, rt); //entry for specified uid rt->user_entry = _e2p_find_create_entry (vbox2, entries [UID_ENTRY]); #ifdef E2_ASSISTED e2_widget_set_label_relations (GTK_LABEL (label2), rt->user_entry); #endif _e2p_find_create_radio_button (vbox2, radio, UID_NONE_P, FALSE, _("unregistered user"), rt); gboolean status = (_e2p_find_get_flag (UID_ANY_P) || _e2p_find_get_flag (UID_NONE_P)); //, rt)); if (status) { gtk_widget_set_sensitive (find_rt->curr_user, FALSE); gtk_widget_set_sensitive (find_rt->choose_user, FALSE); gtk_widget_set_sensitive (find_rt->user_entry, FALSE); } else //UID_SPECIFIC_P { gtk_widget_set_sensitive (find_rt->curr_user, TRUE); gtk_widget_set_sensitive (find_rt->choose_user, TRUE); gtk_widget_set_sensitive (find_rt->user_entry, _e2p_find_get_flag (UID_NOT_LOGIN_P)); //, find_rt)); } // group vbox2 = _e2p_find_create_vbox (hbox); radio = _e2p_find_create_radio_button (vbox2, NULL, GID_ANY_P, TRUE, _("any group id"), rt); _e2p_find_create_radio_button (vbox2, radio, GID_SPECIFIC_P, FALSE, _("specific group id"), rt); find_rt->curr_group = _e2p_find_create_toggle_grouped_button (vbox2, GID_LOGIN_P, FALSE, _("current user's gid"), NULL, rt); find_rt->choose_group = _e2p_find_create_toggle_grouped_button (vbox2, GID_NOT_LOGIN_P, FALSE, _("this group id"), find_rt->curr_group, rt); //entry for specified gid rt->group_entry = _e2p_find_create_entry (vbox2, entries [GID_ENTRY]); #ifdef E2_ASSISTED e2_widget_set_label_relations (GTK_LABEL (label2), rt->group_entry); #endif _e2p_find_create_radio_button (vbox2, radio, GID_NONE_P, FALSE, _("unregistered group"), rt); status = (_e2p_find_get_flag (GID_ANY_P) || _e2p_find_get_flag (GID_NONE_P)); //, rt)); if (status) { gtk_widget_set_sensitive (rt->curr_group, FALSE); gtk_widget_set_sensitive (rt->choose_group, FALSE); gtk_widget_set_sensitive (rt->group_entry, FALSE); } else //GID_SPECIFIC_P { gtk_widget_set_sensitive (find_rt->curr_group, TRUE); gtk_widget_set_sensitive (find_rt->choose_group, TRUE); gtk_widget_set_sensitive (find_rt->group_entry, _e2p_find_get_flag (GID_NOT_LOGIN_P)); //, find_rt)); } gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, label); } /** @brief make notebook tab with file content search options matches simple substrings by default, but may match wildcards. If you want regexps, use grep !! @param notebook widget to which the tab wiil be added @param rt ptr to dialog data struct @return */ static void _e2p_find_make_content_tab (GtkWidget *notebook, E2_FindDialogRuntime *rt) { GtkWidget *label = gtk_label_new (_("content")); gtk_widget_show (label); GtkWidget *vbox = gtk_vbox_new (FALSE, E2_PADDING); gtk_widget_show (vbox); #ifdef E2_ASSISTED GtkWidget *label2 = #endif _e2p_find_create_label (vbox, _("Using grep, find files with content that:")); GtkWidget *hbox = _e2p_find_create_hbox (vbox); GtkWidget *radio = _e2p_find_create_radio_button (hbox, NULL, STRING_CONTENT_P, TRUE, _("is"), rt); _e2p_find_create_radio_button (hbox, radio, WILDCARD_CONTENT_P, FALSE, _("is like"), rt); _e2p_find_create_radio_button (hbox, radio, REGEXP_CONTENT_P, FALSE, _("matches this regex"), rt); _e2p_find_create_toggle_button (hbox, ANYCASE_CONTENT_P, FALSE, _("ignore case"), rt); hbox = _e2p_find_create_hbox (vbox); // hbox = e2_widget_add_box (vbox, TRUE, 0, TRUE, FALSE, E2_PADDING); rt->content_pattern = _e2p_find_create_entry (hbox, entries [CONTENT_ENTRY]); #ifdef E2_ASSISTED e2_widget_set_label_relations (GTK_LABEL (label2), rt->content_pattern); #endif // hbox = _e2p_find_create_hbox (vbox); // _e2p_find_create_toggle_button (hbox, // ANYCASE_CONTENT_P, FALSE, _("ignore case"), rt); #ifdef TRACKERFIND //check whether tracker is available for content-matching //(test for existence, not (yet) whether it's running) /*tracker-status command prints: tracker daemon's status is Indexing. any of: Initializing,Watching,Indexing,Pending,Optimizing,Idle,Shutdown */ gpointer found; gchar *command = "which trackerd"; if (e2_fs_get_command_output (command, &found)) { //output is a string like /path/to/trackerd or which: no trackerd in (... if (!g_str_has_prefix ((gchar *)found, "which: no trackerd") && g_str_has_suffix ((gchar *)found, G_DIR_SEPARATOR_S"trackerd\n")) { // hbox = _e2p_find_create_hbox (vbox); hbox = gtk_hbox_new (FALSE, E2_PADDING); GtkWidget *align = gtk_alignment_new (0.5, 0.0, 0.0, 0.6); gtk_container_add (GTK_CONTAINER (align), hbox); gtk_box_pack_start (GTK_BOX (vbox), align, FALSE, FALSE, E2_PADDING); gtk_widget_show (hbox); gtk_widget_show (align); //FIXME centre-alignment preferred _e2p_find_create_label (hbox, _("Using")); GtkWidget *button = _e2p_find_create_radio_button (hbox, radio, TRACK_CONTENT_P, FALSE, "tracker", rt); //no translation //over-ride default packing arrangement gtk_box_set_child_packing (GTK_BOX (hbox), button, FALSE, FALSE, 0, GTK_PACK_START); #ifdef E2_ASSISTED label2 = #endif _e2p_find_create_label (hbox, _("find files with content that is:")); hbox = _e2p_find_create_hbox (vbox); rt->content_pattern2 = _e2p_find_create_entry (hbox, entries [CONTENT_ENTRY2]); #ifdef E2_ASSISTED e2_widget_set_label_relations (GTK_LABEL (label2), rt->content_pattern2); #endif } else rt->content_pattern2 = NULL; g_free (found); } else rt->content_pattern2 = NULL; #endif gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, label); } /****************************/ /** lesser widget creation **/ /****************************/ /* * @brief create and show a vbox widget in a specified container widget @param container widget into which the vbox is to be added @return the vbox widget */ /*static GtkWidget *_e2p_find_create_vbox (GtkWidget *container) { GtkWidget *rv = gtk_vbox_new (FALSE, E2_PADDING); gtk_container_add (GTK_CONTAINER (container), rv); gtk_widget_show (rv); return rv; } */ /* * @brief create and show a hbox widget in a specified container widget @param container widget into which the hbox is to be added @return the hbox widget */ /*GtkWidget *_e2p_find_create_hbox (GtkWidget *container) { GtkWidget *rv = gtk_hbox_new (FALSE, E2_PADDING); gtk_container_add (GTK_CONTAINER (container), rv); gtk_widget_show (rv); return rv; } */ /* * @brief create and show a label widget in a specified container widget @param container widget into which the label is to be added @return the label widget */ /*static GtkWidget *_e2p_find_create_label (GtkWidget *container, gchar *text) { GtkWidget *rv = gtk_label_new (text); gtk_container_add (GTK_CONTAINER (container), rv); gtk_widget_show (rv); return rv; } */ /** @brief create and show an entry widget in a specified box @param box widget into which the entry is to be added @param text the initial text to show in the entry @return the entry widget */ static GtkWidget *_e2p_find_create_entry (GtkWidget *box, gchar *text) { GtkWidget *rv = e2_widget_add_entry (box, text, TRUE, FALSE); g_object_set_data (G_OBJECT (rv), "reset_yourself", _e2p_find_reset_entry); return rv; } /** @brief create a hbox in @a box @param box the widget into which the button is to be placed @return the created box widget */ static GtkWidget *_e2p_find_create_hbox (GtkWidget *box) { GtkWidget *hbox = gtk_hbox_new (FALSE, E2_PADDING); gtk_box_pack_start (GTK_BOX (box), hbox, FALSE, FALSE, E2_PADDING); gtk_widget_show (hbox); return hbox; } /** @brief create and show a button in a specified container @param box the widget into which the button is to be placed @param f enumerated value of flag to be associated with the button @param state T/F default initial state of the toggle @param label translated string for the button label @param callback the "toggled"-signal callback for the button @param rt ptr to dialog data struct @return the button widget */ static GtkWidget *_e2p_find_create_toggle_button_real (GtkWidget *box, findflag_t f, gboolean state, gchar *label, gpointer callback, E2_FindDialogRuntime *rt) { gboolean first; if (nocacheflags) { first = state; //newly-initialised flags default to FALSE, we don't want that if (state) _e2p_find_set_flag (f, TRUE); //, rt); } else first = _e2p_find_get_flag (f); //, rt); GtkWidget *button = e2_button_add_toggle (box, TRUE, first, label, NULL, TRUE, E2_PADDING, callback, (gpointer) f); g_object_set_data (G_OBJECT (button), "reset_yourself", state ? _e2p_find_set_toggle_button_on : _e2p_find_set_toggle_button_off ); return button; } /** @brief create and show a grouped toggle in a specified box @param box the widget into which the button is to be placed @param f enumerated value of flag to be associated with the button @param state T/F default initial state of the toggle @param label translated string for the button label @param leader widget for the 'leader' of the group, or NULL if this is the leader @param rt ptr to dialog data struct @return the button widget (UNUSED, now) */ static GtkWidget *_e2p_find_create_toggle_grouped_button (GtkWidget *box, findflag_t f, gboolean state, gchar *label, GtkWidget *leader, E2_FindDialogRuntime *rt) { GtkWidget *button = _e2p_find_create_toggle_button_real (box, f, state, label, _e2p_find_grouptoggle_cb, rt); GtkWidget *ptr; GSList *members; if (leader == NULL) { //this is the leader of a new group ptr = button; //point to self members = NULL; rt->groups = g_slist_append (rt->groups, button); //remember it, for cleaning up } else { //this is a group member ptr = leader; //point to group leader, which has list members = g_object_get_data (G_OBJECT (leader), "group_members"); } g_object_set_data (G_OBJECT (button), "group_leader", ptr); members = g_slist_append (members, button); g_object_set_data (G_OBJECT (ptr), "group_members", members); return button; } /** @brief create and show a toggle in a specified container @param box the widget into which the button is to be placed @param f enumerated value of flag to be associated with the button @param state T/F initial state of the toggle @param label translated string for the button label @return the button widget (UNUSED, now) */ static GtkWidget *_e2p_find_create_toggle_button (GtkWidget *box, findflag_t f, gboolean state, gchar *label, E2_FindDialogRuntime *rt) { GtkWidget *button = _e2p_find_create_toggle_button_real (box, f, state, label, _e2p_find_toggle_cb, rt); return button; } /** @brief create and show a radio btn in a specified box The leader of a group is initialized to TRUE, other group members may cause that to be changed @param box the widget into which the button is to be placed @param leader the leader of the radio group, or NULL if this is the leader @param f enumerated value of flag to be associated with the button @param state the default state of the button T/F @param label translated string for the button label @param rt ptr to dialog data struct @return the button widget */ static GtkWidget *_e2p_find_create_radio_button (GtkWidget *box, GtkWidget *leader, findflag_t f, gboolean state, gchar *label, E2_FindDialogRuntime *rt) { gboolean first; if (nocacheflags) { first = state; //newly-initialised flags default to FALSE, we don't want that if (state) _e2p_find_set_flag (f, TRUE); //, rt); } else { first = _e2p_find_get_flag (f); //, rt); } GSList *group = (leader == NULL) ? NULL : gtk_radio_button_get_group (GTK_RADIO_BUTTON (leader)); GtkWidget *button = e2_button_add_radio (box, label, group, first, TRUE, 0, _e2p_find_toggle_cb, (gpointer) f); g_object_set_data (G_OBJECT (button), "reset_yourself", (state) ? _e2p_find_set_toggle_button_on : _e2p_find_set_toggle_button_off ); return button; } /* * @brief create and show a radio btn in a specified container @param container the widget into which the button is to be placed @param leader the radio button widget that 'leads' the group @param f enumerated value of flag to be associated with the button @param state default state of the toggle T/F @param label translated string for the button label @param rt ptr to dialog data struct @return the button widget */ /*static GtkWidget *_e2p_find_create_radio_grouped_button (GtkWidget *box, GtkWidget *leader, findflag_t f, gboolean state, gchar *label, E2_FindDialogRuntime *rt) { gboolean first; if (nocacheflags) { first = state; //newly-initialised flags default to FALSE, we don't want that if (state) _e2p_find_set_flag (f, TRUE); //, rt); } else first = _e2p_find_get_flag (f); //, rt); GSList *group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (leader)); //this packs box with expand and fill set ... GtkWidget *button = e2_button_add_radio (box, label, group, first, TRUE, 0, _e2p_find_toggle_cb, (gpointer) f); g_object_set_data (G_OBJECT (button), "reset_yourself", (state) ? _e2p_find_set_toggle_button_on : _e2p_find_set_toggle_button_off ); return button; } */ /** @brief create a spin button @param default_value ptr to value to be stored @param min_value minimum allowed value for the button @param max_value maximum allowed value for the button @return the button widget */ static GtkWidget *_e2p_find_create_spin_button (gfloat *default_value, gfloat min_value, gfloat max_value) { GtkObject *adj = gtk_adjustment_new (*default_value, min_value, max_value, 1.0, 2.0, 0.0); GtkWidget *button = gtk_spin_button_new (GTK_ADJUSTMENT (adj), 0, 0); gtk_spin_button_set_wrap (GTK_SPIN_BUTTON (button), TRUE); g_object_set_data (G_OBJECT (button), "default_value", default_value); g_object_set_data (G_OBJECT (button), "reset_yourself", _e2p_find_reset_spin_button); return button; } /** @brief add date and time spinners to specified container @param box widget to which the spinners are to be added @param time ptr to spinners data struct for mtime, atime or ctime @return */ static void _e2p_find_make_all_spinners (GtkWidget *box, spinners *time) { GtkWidget *hbox = _e2p_find_create_hbox (box); //day widget GtkWidget *vbox = _e2p_find_create_vbox (hbox); GtkWidget *label = _e2p_find_create_label (vbox, _("Day")); // *day time->day_spin = _e2p_find_create_spin_button (¤t.day, 1.0, 31.0); gtk_box_pack_start (GTK_BOX (vbox), time->day_spin, FALSE, FALSE, E2_PADDING_XSMALL); gtk_widget_show (time->day_spin); //month widget vbox = _e2p_find_create_vbox (hbox); label = _e2p_find_create_label (vbox, _("Month")); // *month time->month_spin = _e2p_find_create_spin_button (¤t.month, 1.0, 12.0); gtk_box_pack_start (GTK_BOX (vbox), time->month_spin, FALSE, FALSE, E2_PADDING_XSMALL); g_signal_connect_after (G_OBJECT (time->month_spin), "changed", G_CALLBACK (_e2p_find_month_changed_cb), time); gtk_widget_show (time->month_spin); //year widget vbox = _e2p_find_create_vbox (hbox); label = _e2p_find_create_label (vbox, _("Year")); // *year time->year_spin = _e2p_find_create_spin_button (¤t.year, 0.0, 9999.0); // gtk_widget_set_size_size_request (*year, 55, -1); // make it 4 digits wide gtk_box_pack_start (GTK_BOX (vbox), time->year_spin, FALSE, FALSE, E2_PADDING_XSMALL); g_signal_connect_after (G_OBJECT (time->year_spin), "changed", G_CALLBACK (_e2p_find_year_changed_cb), time); gtk_widget_show (time->year_spin); //hour widget vbox = _e2p_find_create_vbox (hbox); label = _e2p_find_create_label (vbox, _("Hour")); // *hour time->hour_spin = _e2p_find_create_spin_button (¤t.hour, 0.0, 23.0); gtk_box_pack_start (GTK_BOX (vbox), time->hour_spin, FALSE, FALSE, E2_PADDING_XSMALL); gtk_widget_show (time->hour_spin); //minute widget vbox = _e2p_find_create_vbox (hbox); label = _e2p_find_create_label (vbox, _("Minute")); // *minute time->minute_spin = _e2p_find_create_spin_button (¤t.minute, 0.0, 59.0); gtk_box_pack_start (GTK_BOX (vbox), time->minute_spin, FALSE, FALSE, E2_PADDING_XSMALL); gtk_widget_show (time->minute_spin); /* //second widget vbox = _e2p_find_create_vbox (hbox); label = _e2p_find_create_label (vbox, _("Second")); GtkWidget *second = _e2p_find_create_spin_button (¤t.second, 0.0, 59.0); gtk_box_pack_start (GTK_BOX (vbox), second, FALSE, FALSE, E2_PADDING_XSMALL); gtk_widget_show (second); */ //init proper days per month _e2p_find_month_changed_cb (time->month_spin, time); } /** @brief establish and show find dialog There is no compelling reason to queue this, it does not necessarily work on either displayed filelist @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2p_find_dialog_create (gpointer from, E2_ActionRuntime *art) { //because we use static var's, check if there is already a config dialog opened pthread_mutex_lock (&find_mutex); if (find_rt != NULL) { gtk_window_present (GTK_WINDOW (find_rt->dialog)); pthread_mutex_unlock (&find_mutex); return TRUE; } //init runtime object find_rt = ALLOCATE (E2_FindDialogRuntime); pthread_mutex_unlock (&find_mutex); CHECKALLOCATEDWARN (find_rt, return FALSE;) find_rt->groups = NULL; find_rt->matchdata = NULL; //for cleanups before any search is run gint startpage = page_store; //preserve this //create dialog find_rt->dialog = e2_dialog_create (NULL, NULL, _("find files"), _e2p_find_response_cb, find_rt); GtkWidget *dialog_vbox = GTK_DIALOG (find_rt->dialog)->vbox; gtk_container_set_border_width (GTK_CONTAINER (dialog_vbox), E2_PADDING); //populate it //first with things for selecting the seach starting place _e2p_find_make_directory_widgets (dialog_vbox, find_rt); e2_widget_add_separator (dialog_vbox, FALSE, E2_PADDING_SMALL); //then with things to identify the files of interest _e2p_find_make_notebook (dialog_vbox, find_rt); //open at same page as last time if (startpage > 0) gtk_notebook_set_current_page (GTK_NOTEBOOK (find_rt->notebook), startpage); //add buttons in the order that they will appear find_rt->help_button = e2_dialog_add_undefined_button_custom (find_rt->dialog, FALSE, E2_RESPONSE_USER2, _("_Help"), GTK_STOCK_HELP, _("get advice on search options on this page"), _e2p_find_help_cb, find_rt); /* //omit this button = _e2p_find_create_button (hbox, _e2p_find_save_search_cb, NULL); label = _e2p_find_create_label (button, _("_Save")); */ find_rt->stop_button = e2_dialog_add_button_custom (find_rt->dialog, FALSE, &E2_BUTTON_NOTOALL, _("stop the current search"), _e2p_find_stop_find_cb, find_rt); //de-sensitize stop btn, at this stage gtk_widget_set_sensitive (find_rt->stop_button, FALSE); find_rt->start_button = e2_dialog_add_undefined_button_custom (find_rt->dialog, FALSE, E2_RESPONSE_FIND, _("_Find"), GTK_STOCK_FIND, _("begin searching"), _e2p_find_find_cb, find_rt); e2_dialog_add_undefined_button_custom (find_rt->dialog, FALSE, E2_RESPONSE_USER1, _("Clea_r"), GTK_STOCK_CLEAR, _("clear all search parameters"), _e2p_find_clear_find_cb, find_rt); e2_dialog_add_defined_button (find_rt->dialog, &E2_BUTTON_CLOSE); e2_dialog_set_negative_response (find_rt->dialog, GTK_RESPONSE_CLOSE); gboolean state; if (nocacheflags) { //no cached flags were used in this se gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (find_rt->active_button), TRUE); state = FALSE; nocacheflags = FALSE; //from now on, use static/cached flag values } else state = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (find_rt->thisdir_button)); gtk_widget_set_sensitive (find_rt->directory, state); e2_dialog_setup (find_rt->dialog, app.main_window); gtk_widget_show (find_rt->dialog); return TRUE; } //aname must be confined to this module static gchar *aname; /** @brief plugin initialization function, called by main program @param p ptr to plugin data struct @return TRUE if the initialization succeeds, else FALSE */ gboolean init_plugin (Plugin *p) { #define ANAME "find" aname = _("detfind"); p->signature = ANAME VERSION; p->menu_name = _("_Find.."); p->description = _("Find and list items, using detailed criteria"); p->icon = "plugin_find_"E2IP".png"; //use icon file pathname if appropriate if (p->action == NULL) { //no need to free this gchar *action_name = g_strconcat (_A(1),".",aname,NULL); p->action = e2_plugins_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2p_find_dialog_create, NULL, FALSE, 0, NULL); nocacheflags = !e2_cache_check ("find-plugin-flags"); if (nocacheflags) //initialise all flags to FALSE //(some will be changed when specific widgets are created) _e2p_find_reset_flags (); //find_rt); e2_cache_array_register ("find-plugin-flags", MAX_FLAGS, flags, flags); guint i; e2_cache_list_register ("find-plugin-strings", &strings); if (strings == NULL) { //no cache found for this item //init entry strings, with fillers in case session ends before dialog is closed for (i = 0; i < MAX_ENTRIES; i++) strings = g_list_append (strings, g_strdup (".")); } else //REMOVEME when things are stable { //when mucking around with API's we don't want to crash due to //too-few cached strings guint n = g_list_length (strings); if (n != MAX_ENTRIES) { e2_list_free_with_data (&strings); for (i = 0; i < MAX_ENTRIES; i++) strings = g_list_append (strings, g_strdup (".")); } } for (i = 0; i < MAX_ENTRIES; i++) { //allocate new strings because string and array may be cleared separately gchar *str = (gchar *)g_list_nth_data (strings, i); //remove cached filler string if (g_str_equal (str, ".")) str = ""; entries[i] = g_strdup (str); } #ifdef TRACKERFIND for (i = 0; i < ACTIVE_TRACKER_SERVICES; i++) object_names [i] = gettext (object_names[i]); #endif //setup mutex to protect threaded access to find data pthread_mutexattr_t attr; pthread_mutexattr_init (&attr); pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init (&find_mutex, &attr); return TRUE; } return FALSE; } /** @brief cleanup transient things for this plugin @param p pointer to data struct for the plugin @return TRUE if all cleanups were completed */ gboolean clean_plugin (Plugin *p) { gchar *action_name = g_strconcat (_A(1),".",aname,NULL); gboolean ret = e2_plugins_action_unregister (action_name); g_free (action_name); if (ret) { //backup the cache data e2_cache_unregister ("find-plugin-flags"); e2_cache_unregister ("find-plugin-strings"); //cleanup e2_list_free_with_data (&strings); //this also clears the array } return ret; } emelfm2-0.4.1/plugins/e2p_rename.c0000600000175000017500000014402010720407016015647 0ustar cairocairo/* $Id: e2p_rename.c 731 2007-11-19 22:28:30Z tpgww $ Copyright (C) 2005-2007 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file plugins/e2p_rename_ext.c @brief file-rename plugin This file contains functions related to creation of a file-rename dialog, and execution of a find & rename task in accord with options selected in that dialog. */ /* TODO FIXME's hard = detect end of shell command, and update start/stop/help btn sensitivity accordingly share code with the find plugin ? */ #include "emelfm2.h" #include #include #include #include #include "e2p_rename.h" #include "e2_plugins.h" #include "e2_dialog.h" #include "e2_task.h" #include "e2_filelist.h" static gboolean scanaborted = FALSE; //FIXME make it auto variable static gboolean flags[MAX_FLAGS]; //session-cache for toggle values static GList *dir_history; static GList *pattern_history; static GList *newpattern_history; //counter macros in replacement name pattern #define E2_COUNTER_ENABLED #ifdef E2_COUNTER_ENABLED #define MAX_COUNTERS 4 //enum { CINDEX, enum { CLEN, CSTART, CWIDTH, CCOLSCOUNT }; static guint countercount; static guint counterdata [MAX_COUNTERS][CCOLSCOUNT]; #endif static gboolean _e2p_renameQ (E2_ActionTaskData *qed); /*********************/ /***** utilities *****/ /*********************/ /** @brief set specified flag to T/F The relevant array value is set @param f enumerated value of flag to be set @param value new value for the flag @param rt ptr to dialog data struct @return */ static void _e2p_ren_set_flag (renflag_t f, gboolean value) //, E2_RenDialogRuntime *rt) { if (f < MAX_FLAGS) // rt-> flags [(gint) f] = value; } /** @brief return the value of a specified flag @param f enumerated value of flag to be interrogated @param rt @return flag value, T/F, or FALSE if the value is not recognised */ static gboolean _e2p_ren_get_flag (renflag_t f) //, E2_RenDialogRuntime *rt) { if (f < MAX_FLAGS) return (//rt-> flags [(gint) f]); else return FALSE; } /* * @brief set all flags to FALSE @param rt ptr to dialog data struct @return */ /*UNUSED static void _e2p_ren_reset_flags (E2_RenDialogRuntime *rt) { gint i; for (i = 0; i < MAX_FLAGS; i++) flags[i] = FALSE; } */ #ifdef E2_COUNTER_ENABLED /** @brief setup counter information for replacement name Up to MAX_COUNTERS counter macros can be used in @a newtemplate. For each counter, if "i" is provided it is the number which starts that series For each counter, if "w" is provided it is the minumum width of each count string in that series, which will be padded with leading 0's as required @param newtemplate replace pattern string, maybe with %c[i[,w]] @param rt pointer to dialog data struct @return TRUE if one or more counter macros were processed */ //FIXME make this work with replacement name, instead of replacement pattern static gboolean _e2p_ren_parse_counters (const gchar *newtemplate, E2_RenDialogRuntime *rt) { guint indx = 0; //array index gulong start, width; const gchar *c, *s; gchar *endptr; rt->modeflags &= ~E2PR_COUNTER; s = newtemplate; while ((s = strstr (s, "%c")) != NULL) { rt->modeflags |= E2PR_COUNTER; //signal we have counter(s) c = s; s += 2; //skip ascii macro signature start = strtoul (s, &endptr, 10); if (endptr == s) start = 1; //no number provided else s = endptr; if (*s == ',') //no whitespace check { s++; width = strtoul (s, &endptr, 10); if (endptr == s) width = 1; //no number provided else s = endptr; } else width = 1; //store counter data for later use // counterdata [indx][CINDEX] = c - newtemplate; we need to scan each replacement name counterdata [indx][CLEN] = s - c; counterdata [indx][CSTART] = (guint) start; counterdata [indx][CWIDTH] = (guint) width; if (++indx == MAX_COUNTERS) break; } countercount = indx; return (rt->modeflags & E2PR_COUNTER); } #endif /** @brief parse replacement pattern a regex-matched file Carve up new tempate into chunks separated by \1, \2 .... \ and store copies in rt->chunks[1] .... chunks[E2_HUNK_LIMIT-1] Chunk[0] used for pointerised chunks count (=last index, since chunk[0] is not used). Sets rt->modeflags E2PR_NEWALL, and chunk[1] to @a newtemplate if that has no \1 ... Sets rt->modeflags E2PR_WHOLE if "\0" detected. Sets rt->modeflags E2PR_COUNTER if "%c" detected. @param newtemplate replace pattern string maybe with regex \1 [\2 ...] or %c['s] @param rt pointer to dialog data struct @return */ static void _e2p_ren_parse_regexpattern (const gchar *newtemplate, E2_RenDialogRuntime *rt) { gint thiscount = 0; gchar *s, *s1, *s2, *s3; s = s1 = g_strdup (newtemplate); //work with a copy so original is not clobbered rt->modeflags = E2PR_NORMAL; while ((s2=strchr (s1, '\\')) != NULL) //always ascii { s2++; if (*s2=='\\') { //ignore any escaped "\" s1=s2+1; continue; } //log but don't store any "\0" if (*s2 == '0') { rt->modeflags = E2PR_WHOLE; s1=s2+1; continue; } s3 = s2; while (*s3 >= '0' && *s3 <= '9') //atoi() doesn't handle errors s3++; if (s3 > s2) { gchar r = *s3; *s3 = '\0'; thiscount = atoi (s2); *(s2-1) = '\0'; if (thiscount > 0 && thiscount < E2_CHUNK_LIMIT) //don't record \0 rt->chunks[thiscount] = g_strdup (s1); *s3 = r; } s1 = s3; } if (thiscount == 0) { rt->modeflags |= E2PR_NEWALL; rt->chunks[0] = GINT_TO_POINTER (1); rt->chunks[1] = s; //use whole pattern in case there was a "\0" } else { //get the tail of the pattern too rt->chunks[0] = GINT_TO_POINTER (thiscount+1); //this assumes that the last \N is the highest number backref ? rt->chunks[thiscount+1] = g_strdup (s1); g_free (s); } #ifdef E2_COUNTER_ENABLED _e2p_ren_parse_counters (newtemplate, rt); #endif } /** @brief parse replacement pattern for a wildcard matched file Carve up new tempate into chunks separated by * or ? and store copies in rt->chunks[1] .... chunks[E2_HUNK_LIMIT-1]. Some may be "". Chunk[0] used for pointerised chunks count (=last index, since chunk[0] is not used). Sets rt->modeflags E2PR_NEWALL, and chunk[1] to @a newtemplate if that has no "*" or "?" Sets rt->modeflags E2PR_WHOLE if "\0" detected. Sets rt->modeflags E2PR_COUNTER, and parses counter string(s), if any "%c" detected. @param newtemplate replace pattern string, maybe with one or more *, ? or %c @param rt pointer to dialog data struct @return */ static void _e2p_ren_parse_wildpattern (const gchar *newtemplate, E2_RenDialogRuntime *rt) { if ( (strchr (newtemplate, '?') == NULL) && (strchr (newtemplate, '*') == NULL) ) //always ascii { //there's nothing wild about it ... rt->modeflags = E2PR_NEWALL; rt->chunks[0] = GINT_TO_POINTER (1); rt->chunks[1] = g_strdup (newtemplate); } else { rt->modeflags = E2PR_NORMAL; gint thiscount = 1; //bypass chunk[0] gchar **split = g_strsplit_set (newtemplate, "*?", E2_CHUNK_LIMIT); gchar **tmp = split; while (*tmp != NULL && (thiscount < E2_CHUNK_LIMIT)) { rt->chunks[thiscount++] = *tmp; tmp++; } while (*tmp != NULL) { //FIXME leftovers should be rejoined into last chunk g_free (*tmp); tmp++; } rt->chunks[0] = GINT_TO_POINTER (--thiscount); g_free (split); } if (strstr (newtemplate, "\\0") != NULL) rt->modeflags |= E2PR_WHOLE; #ifdef E2_COUNTER_ENABLED _e2p_ren_parse_counters (newtemplate, rt); #endif } #ifdef E2_COUNTER_ENABLED /** @brief update @a newname with current counter values @param newname string with occurrence(s) of counter macro %c[n[,m]] @param rt pointer to dialog data struct @return replacement name, newly-allocated string in same encoding as original */ static gchar *_e2p_ren_count_replace (gchar *newname, E2_RenDialogRuntime *rt) { // gint adj = 0; guint i; gchar *c, *s, *p, *expanded = g_strdup (newname); gchar numfmt[20]; numfmt[0] = '%'; //use each stored start, width, macro string from when parsed for (i = 0; i < countercount; i++) { //get count string for current value and width if (counterdata [i][CWIDTH] > 1) g_snprintf (numfmt+1, sizeof(numfmt)-1, "0%uu", counterdata [i][CWIDTH]); else g_strlcpy (numfmt+1, "u", sizeof(numfmt)-1); c = g_strdup_printf (numfmt, counterdata [i][CSTART]); //substitute count string for counter macro p = strstr (expanded, "%c"); if (p == NULL) break; //oops ! // p = (expanded + counterdata [i][CINDEX] + adj); *p = '\0'; s = (p + counterdata [i][CLEN]); p = expanded; expanded = g_strconcat (p, c, s, NULL); //updates // adj += strlen (c) - counterdata [i][CLEN]; counterdata [i][CSTART]++; //cleanups g_free (c); g_free (p); } return expanded; } #endif /** @brief determine replacement name for a file For same-case renames, new tempate chunks[] will have been populated before this is called @param oldtemplate search pattern string (utf8) possibly with 'extended' regex as appropriate, or NULL @param oldpath absolute path (utf8 string) of item to be changed @param rt pointer to dialog data struct @return replacement name, newly allocated utf8 string */ static gchar *_e2p_ren_name_replace (gchar *oldtemplate, gchar *oldpath, E2_RenDialogRuntime *rt) { gchar *s1, *result; gchar *newbase = g_path_get_basename (oldpath); if (rt->modeflags & E2PR_NEWALL) //no wild or regex to interpret in replacement template { #ifdef E2_COUNTER_ENABLED if ((rt->modeflags & (E2PR_COUNTER | E2PR_WHOLE)) == (E2PR_COUNTER | E2PR_WHOLE)) { //get replacement pattern with counters substituted s1 = _e2p_ren_count_replace (rt->chunks[1], rt); result = e2_utils_str_replace (s1, "\\0", newbase); //handle "\0" } else if (rt->modeflags & E2PR_WHOLE) result = e2_utils_str_replace (rt->chunks[1], "\\0", newbase); else //no \0 in pattern result = (rt->modeflags & E2PR_COUNTER) ? //if counter[s] required, use the template, ignore the found filename _e2p_ren_count_replace (rt->chunks[1], rt) : g_strdup (newbase); #else result = (rt->modeflags & E2PR_WHOLE) ? e2_utils_str_replace (rt->chunks[1], "\\0", newbase): //handle any "\0" g_strdup (newbase); #endif } else { //not just a case-change regex_t oldcompiled; // gint cflags = (ext_regex) ? REG_EXTENDED : 0 ; //not REG_ICASE if (!regcomp (&oldcompiled, oldtemplate, REG_EXTENDED)) { //need this many data spots gint matchcount = (gint)oldcompiled.re_nsub + 1; regmatch_t matcholdptr [matchcount]; gint error; gint eflags = 0; //not REG_NOTBOL | REG_NOTEOL; if ((error = regexec (&oldcompiled, newbase, matchcount, matcholdptr, eflags)) != 0) {//handle nomatch //DEBUG size_t len = regerror (error, &oldcompiled, NULL, 0); gchar local[len+2]; regerror (error, &oldcompiled, local, len+2); printd (DEBUG, local); e2_output_print_error (local, FALSE); } //transform newtemplate into new name, with corresponding matches from oldname gchar buf[NAME_MAX+2]; result = g_strdup (""); regmatch_t rx; gint i, j, L; //ignore chunk[0] j=1; //ignore chunks past what we asked for gint newwilds = GPOINTER_TO_INT (rt->chunks[0]); if (newwilds < matchcount) matchcount = newwilds; //ignore the initial 'whole pattern' match for (i = 1; i < matchcount; i++) { //get the matched substring rx=matcholdptr[i]; if (rx.rm_so > -1) { //the substring was actually used in the match //get a null-terminated copy s1 = newbase + rx.rm_so; L = rx.rm_eo - rx.rm_so; if (L > NAME_MAX+1) L = NAME_MAX+1; //prevent buffer overflow memcpy (buf, s1, L); buf[L] = '\0'; } else { //FIXME continue; } //progressively join up fixed and variable for ( ; j <= i ; j++) { if (rt->chunks[j] == NULL || *(rt->chunks[j]) == '\0') continue; s1=result; result = g_strconcat (s1, rt->chunks[j], NULL); g_free (s1); } s1=result; result = g_strconcat (s1, buf, NULL); g_free (s1); } //and add any left-over chunks if ((newwilds <= (gint)oldcompiled.re_nsub + 1) && j < E2_CHUNK_LIMIT && rt->chunks[j] != NULL && *(rt->chunks[j]) != '\0') { s1=result; result = g_strconcat (s1, rt->chunks[j], NULL); g_free (s1); } /* for (;jchunks[j] == NULL || *(rt->chunks[j]) == '\0') continue; s1=result; result = g_strconcat (s1, rt->chunks[j], NULL); g_free (s1); } */ //cleanup regfree (&oldcompiled); } else { //handle error - eg no regex in expression result = g_strdup (newbase); } #ifdef E2_COUNTER_ENABLED if (rt->modeflags & E2PR_COUNTER) { s1 = result; result = _e2p_ren_count_replace (result, rt); g_free (s1); } #endif if (rt->modeflags & E2PR_WHOLE) { //handle all "\0" s1 = result; result = e2_utils_str_replace (result, "\\0", newbase); g_free (s1); } } if (rt->modeflags & E2PR_LOWER) { s1 = result; result = g_utf8_strdown (result, -1); g_free (s1); } else if (rt->modeflags & E2PR_UPPER) { s1 = result; result = g_utf8_strup (result, -1); g_free (s1); } g_free (newbase); return result; } /** @brief check whether @a name is a candidate for renaming @param name localised name string of candidate @param data pointer to rename data struct @return TRUE if @a name matches the pattern sought */ static gboolean _e2p_ren_match_name (gchar *name, E2P_RenameData *data) { if (data->flags & (E2PR_NORMAL | E2PR_WILD)) //non-regex search return (!fnmatch (data->pattern, name, 0)); else //regex search return (!regexec (data->compiled, name, 0, NULL, REG_NOTBOL)); } /** @brief when recursing, update matching items array This is a callback for a treewalk function. Name(s) (utf8) of matched item(s) are stored in an array Expects BGL to be closed @param localpath absolute path of item reported by the walker, localised string @param statptr pointer to struct stat with data about @a localpath @param status code from the walker, indicating what type of report it is @param twdata pointer to tw data struct @return E2TW_CONTINUE always */ static E2_TwResult _e2p_ren_twcb (VPATH *localpath, const struct stat *statptr, E2_TwStatus status, E2P_RenameData *twdata) { WAIT_FOR_EVENTS if (scanaborted) { scanaborted = FALSE; return E2TW_STOP; } gchar *s, *utf; switch (status) { case E2TW_DM: //directory, not opened due to different file system (reported upstream) case E2TW_DL: //directory, not opened due to tree-depth limit (reported upstream) case E2TW_DNR: //unreadable directory (for which, error is reported upstream) case E2TW_DRR: //directory now readable case E2TW_D: //directory case E2TW_F: //not directory or link case E2TW_SL: //symbolic link case E2TW_SLN: //symbolic link naming non-existing file //the first call here provides the root dir for the search //distinguished by a trailing / s = strrchr (VPSTR (localpath), G_DIR_SEPARATOR); //really, there must be a / in the string, but test anyway ... s = (s == NULL) ? (gchar *)localpath : s+1; //s now at the name start, or after a trailing / from the root dir if ((ITEM_ISHIDDEN(s) && (s[1] == '\0' || (s[1] == '.' && s[2] == '\0'))) || *s == '\0' //this is the first call, with just the root dir ) break; if (_e2p_ren_match_name (s, twdata)) { utf = D_FILENAME_FROM_LOCALE ((gchar *) localpath); //always get a copy g_ptr_array_add (twdata->candidates, (gpointer) utf); } break; // case E2TW_DP: //directory, finished // case E2TW_NS: //un-stattable item (for which, error is reported upstream) default: break; } return E2TW_CONTINUE; } /* * @brief when not recursing, update matching items array @param dirpath localised path of directory to scan for matching items @param data pointer to rename data struct @return */ /*static void _e2p_ren_dirscan (gchar *dirpath, E2P_RenameData *data) { gchar *msg, *joined, *utf; struct dirent *entry; struct dirent entrybuf; #ifdef E2_VFSTMP //this open/iterate/clode mechanism probably useless for vfs dir #endif DIR *dp = e2_fs_dir_open (dirpath E2_ERR_NONE()); if (dp == NULL) { utf = F_DISPLAYNAME_FROM_LOCALE (dirpath); msg = g_strdup_printf (_("Cannot open directory %s"), utf); e2_output_print_error (msg, TRUE); F_FREE (utf); return; } while (!e2_fs_dir_read (dp, &entrybuf, &entry E2_ERR_NONE()) && entry != NULL) //FIXME vfs { if (ITEM_ISHIDDEN(entry->d_name) && (entry->d_name[1] == '\0' || g_str_equal (entry->d_name, "..")) ) continue; if (_e2p_ren_match_name (entry->d_name, data)) { joined = e2_utils_strcat (dirpath, entry->d_name); utf = F_FILENAME_FROM_LOCALE (joined); //always get a copy g_ptr_array_add (data->candidates, (gpointer) utf); if (joined != utf) //conversion really happened g_free (joined); } } e2_fs_dir_close (dp E2_ERR_NONE()); } */ /** @brief perform rename task This is called only from within callbacks, BGL closed Then processes the results @param rt ptr to dialog data struct @return */ static void _e2p_ren_rename (E2_RenDialogRuntime *rt) { const gchar *old, *new; if (!_e2p_ren_get_flag (OLD_SEL_P)) //, rt)) { old = gtk_entry_get_text (GTK_ENTRY (GTK_BIN (rt->pattern)->child)); if (*old == '\0') { e2_output_print_error (_("No current name pattern is specified"), FALSE); return; } e2_list_update_history ((gchar *) old, &pattern_history, NULL, 20, FALSE); } else old = NULL; if (_e2p_ren_get_flag (NEW_THIS_P)) //, rt)) { new = gtk_entry_get_text (GTK_ENTRY (GTK_BIN (rt->newpattern)->child)); if (*new == '\0') { e2_output_print_error (_("No replacement name pattern is specified"), FALSE); return; } //eliminate inappropriate replacement pattern entered when processing selected items //(but no check for regex backrefs, \0 is ok) if (_e2p_ren_get_flag (OLD_SEL_P) //, rt) && ( (strchr (new, '?') != NULL) || (strchr (new, '*') != NULL) ) ) //always ascii { //there's something wild about it ... e2_output_print_error (_("Replacement name pattern cannot have wildcard(s)"), FALSE); return; } e2_list_update_history ((gchar *) new, &newpattern_history, NULL, 20, FALSE); } else new = NULL; *rt->status = E2_TASK_RUNNING; //update dialog button sensitivities while we search gtk_widget_set_sensitive (rt->help_button, FALSE); gtk_widget_set_sensitive (rt->start_button, FALSE); gtk_widget_set_sensitive (rt->stop_button, TRUE); WAIT_FOR_EVENTS gchar *localroot; //ignore warning about uninitialized usage gchar *tmp2 = NULL; //assignment for complier-warning prevention only const gchar *startdir; regex_t compiled; E2P_RenameData data; #ifdef E2_VFS VPATH sdata; #endif //get paths/names of items to replace gboolean result; data.candidates = g_ptr_array_new (); if (_e2p_ren_get_flag (OLD_SEL_P)) //, rt)) { #ifdef E2_VFS sdata.spacedata = curr_view->spacedata; #endif //to distinguish items, get quoted names then remove quotes gchar *macro = (_e2p_ren_get_flag (SEARCH_OTHER_P)) ? "%F" : "%p"; //%F==%P gchar *itempaths = e2_utils_expand_macros (macro, NULL); if (itempaths != NULL) { gchar *s, *p = itempaths; while (*p != '\0') { s = strchr (p, '"'); p = s+1; s = strchr (p, '"'); *s = '\0'; g_ptr_array_add (data.candidates, g_strdup (p)); p = s+1; } g_free (itempaths); } result = (data.candidates->len > 0); } else //not renaming selected items { result = TRUE; //get search-start dir, with a trailing /, among other reasons //to signal to the tw cb which is the search-root to be ignored if (_e2p_ren_get_flag (SEARCH_CURRENT_P)) //, rt)) { #ifdef E2_VFS sdata.spacedata = curr_view->spacedata; #endif startdir = curr_view->dir; } else if (_e2p_ren_get_flag (SEARCH_OTHER_P)) //, rt)) { #ifdef E2_VFS sdata.spacedata = other_view->spacedata; #endif startdir = other_view->dir; } else if (_e2p_ren_get_flag (SEARCH_ALL_P)) //, rt)) { #ifdef E2_VFS sdata.spacedata = curr_view->spacedata; #endif startdir = G_DIR_SEPARATOR_S; //root of filesystem tree FIXME vfs _e2p_ren_set_flag (SEARCH_SUBDIRS_P, TRUE); //, rt); } else { #ifdef E2_VFS # ifdef E2_VFSTMP FIXME get relevant space # endif sdata.spacedata = curr_view->spacedata; #endif startdir = (gchar *) gtk_entry_get_text (GTK_ENTRY (GTK_BIN (rt->directory)->child)); e2_list_update_history (startdir, &dir_history, NULL, 20, FALSE); } if (result) { if (_e2p_ren_get_flag (OLD_WILD_P)) //, rt)) //exact or wildcard match { data.flags = E2PR_WILD; data.pattern = old; } else //regex match { if (regcomp (&compiled, old, REG_EXTENDED)) { result = FALSE; tmp2 = g_strdup_printf (_("Error in regular expression %s"), old); e2_output_print_error (tmp2, TRUE); } else { data.flags = E2PR_REGEX; data.compiled = &compiled; } } if (result) { //accumulate array of matching items, path-by-path localroot = D_FILENAME_TO_LOCALE (startdir); //always copied //add trailing / if need be if ((tmp2 = strrchr (localroot, G_DIR_SEPARATOR)) == NULL || tmp2 != (localroot + strlen (localroot) - 1)) { tmp2 = localroot; localroot = e2_utils_strcat (localroot, G_DIR_SEPARATOR_S); g_free (tmp2); } #ifdef E2_VFS sdata.localpath = localroot; #endif if (_e2p_ren_get_flag (SEARCH_SUBDIRS_P)) //, rt)) { data.flags |= E2PR_RECURSE; e2_dialog_set_cursor (rt->dialog, GDK_WATCH); gdk_threads_leave (); #ifdef E2_VFS result = e2_fs_tw (&sdata, _e2p_ren_twcb, &data, -1, #else result = e2_fs_tw (localroot, _e2p_ren_twcb, &data, -1, #endif E2TW_PHYS E2_ERR_NONE()); gdk_threads_enter (); e2_dialog_set_cursor (rt->dialog, GDK_LEFT_PTR); } else { gdk_threads_leave (); #ifdef E2_VFS result = e2_fs_tw (&sdata, _e2p_ren_twcb, &data, 1, #else result = e2_fs_tw (localroot, _e2p_ren_twcb, &data, 1, #endif E2TW_QT | E2TW_PHYS E2_ERR_NONE()); gdk_threads_enter (); } g_free (localroot); //restore real flag state if (_e2p_ren_get_flag (SEARCH_ALL_P)) //, rt)) { gboolean oldstate = GTK_TOGGLE_BUTTON (rt->recurse_button)->active; _e2p_ren_set_flag (SEARCH_SUBDIRS_P, oldstate); //, rt); } if (data.candidates->len == 0) { //couldn't find anything tmp2 = g_strdup_printf (_("Cannot find anything which matches %s"), old); e2_output_print_error (tmp2, TRUE); result = FALSE; } } } } if (!result) { gtk_widget_set_sensitive (rt->help_button, TRUE); gtk_widget_set_sensitive (rt->start_button, TRUE); gtk_widget_set_sensitive (rt->stop_button, FALSE); g_ptr_array_free (data.candidates, TRUE); WAIT_FOR_EVENTS *rt->status = E2_TASK_PAUSED; return; } g_ptr_array_add (data.candidates, NULL); //null-terminate the array gchar **candidates, **thisone; candidates = thisone = (gchar **) data.candidates->pdata; g_ptr_array_free (data.candidates, FALSE); if (_e2p_ren_get_flag (NEW_THIS_P)) //*new != '\0' checked before { if (_e2p_ren_get_flag (OLD_WILD_P)) //, rt)) (data.flags == E2PR_WILD) { //wildcard or specific names used //adjust the find pattern, by replacing all '.', '*' and '?' //in the pattern with their extended regex equivalents gchar **split = g_strsplit (old, ".", -1); tmp2 = g_strjoinv ("\\.", split); g_strfreev (split); split = g_strsplit (tmp2, "?", -1); g_free (tmp2); tmp2 = g_strjoinv ("(.)", split); g_strfreev (split); split = g_strsplit (tmp2, "*", -1); g_free (tmp2); tmp2 = g_strjoinv ("(.*?)", split); //freeme later lazy-star to prevent greedy matching ? g_strfreev (split); // printd (DEBUG, "wildcard rename pattern is %s", pattern); //get the chunks of the replacement pattern _e2p_ren_parse_wildpattern (new, rt); } else if (_e2p_ren_get_flag (OLD_REGEX_P)) { //regex names (actually, paths) used //convert the name using the same regex as 'find' used tmp2 = (gchar *)old; //get the chunks of the replacement pattern _e2p_ren_parse_regexpattern (new, rt); } else //OLD_SEL_P //for this case, real tmp2 is set inside remame loop //tmp2 = NULL; //warning prevention only //for selected-item renaming \0 or %c can be in the pattern //cheap parsing, having already checked that new pattern does not have * or ? _e2p_ren_parse_wildpattern (new, rt); rt->modeflags |= E2PR_PATTERN; } else //we are _only_ changing case { rt->modeflags = E2PR_NEWALL; //prevent rename func from trying to parse tmp2 tmp2 = NULL; //don't need an old pattern } if (_e2p_ren_get_flag (OLD_SEL_P)) //, rt)) rt->modeflags |= E2PR_SEL; if (_e2p_ren_get_flag (NEW_LOWER_P)) //, rt)) rt->modeflags |= E2PR_LOWER; else if (_e2p_ren_get_flag (NEW_UPPER_P)) //, rt)) rt->modeflags |= E2PR_UPPER; //FIXME start an async task-thread DialogButtons doit; GtkWidget *dialog; if (_e2p_ren_get_flag (CONFIRM_P)) //, rt)) { //setup for repeated non-modal dialogs dialog = e2_dialog_create (GTK_STOCK_DIALOG_QUESTION, "", _("confirm"), e2_dialog_response_decode_cb, &doit); //set default button to 'no' e2_dialog_set_negative_response (dialog, E2_RESPONSE_NOTOALL); E2_BUTTON_NO.showflags |= E2_BTN_DEFAULT; E2_BUTTON_YES.showflags &= ~E2_BTN_DEFAULT; e2_dialog_add_defined_button (dialog, &E2_BUTTON_NOTOALL); e2_dialog_add_defined_button (dialog, &E2_BUTTON_NO); e2_dialog_add_defined_button (dialog, &E2_BUTTON_YES); e2_dialog_setup (dialog, app.main_window); } else dialog = NULL; gboolean check = e2_option_bool_get ("confirm-overwrite"); e2_filelist_disable_refresh (); e2_dialog_set_cursor (rt->dialog, GDK_WATCH); //walk the vector of matching files while (*thisone != NULL) { gchar *base = g_path_get_basename (*thisone); gchar *dir = g_path_get_dirname (*thisone); //for other cases, tmp2 is set outside loop (maybe NULL) if ((rt->modeflags & (E2PR_SEL | E2PR_PATTERN)) == (E2PR_SEL | E2PR_PATTERN)) tmp2 = base; //get the replacement basename gchar *newbase = _e2p_ren_name_replace (tmp2, *thisone, rt); //escape any pango-annoying component of the filenames gchar *base_public = g_markup_escape_text (base, -1); gchar *newbase_public = g_markup_escape_text (newbase, -1); //ask, if the confirm option is is force, and the parent's stop btn not pressed if (_e2p_ren_get_flag (CONFIRM_P) && ! rt->abort) { gchar *prompt = g_strdup_printf ("%s\n%s\n%s\n%s\n%s %s", _("Rename"), base_public, _("to"), newbase_public, _("in"), dir); GtkWidget *label = g_object_get_data (G_OBJECT (dialog), "e2-dialog-label"); gtk_label_set_markup (GTK_LABEL (label), prompt); g_free (prompt); gtk_widget_show (dialog); *rt->status = E2_TASK_PAUSED; gtk_main (); *rt->status = E2_TASK_RUNNING; gtk_widget_hide (dialog); gtk_widget_grab_focus (rt->dialog); } else //no confirmation doit = OK; if (doit == OK) { gchar *newpath = g_build_filename (dir, newbase, NULL); gchar *tempname, *slocal, *dlocal = F_FILENAME_TO_LOCALE (newpath); gboolean success; #ifdef E2_VFS VPATH ddata = { dlocal, sdata.spacedata }; VPATH tdata; tdata.spacedata = sdata.spacedata; #endif //get o/w confirmation if that option is in force #ifdef E2_VFS if (check && e2_fs_access2 (&ddata E2_ERR_NONE()) == 0) #else if (check && e2_fs_access2 (dlocal E2_ERR_NONE()) == 0) #endif { //maybe new name exists, or maybe it's just the old name with some different case gchar *old_name_lc = g_utf8_casefold (*thisone, -1); gchar *new_name_lc = g_utf8_casefold (newpath, -1); if (!g_str_equal (old_name_lc, new_name_lc)) { //we may have just a case-difference that looks the same to the kernel //do interim name change to check, & also to ensure case-change happens slocal = F_FILENAME_TO_LOCALE (*thisone); tempname = e2_utils_get_tempname (slocal); #ifdef E2_VFS tdata.localpath = tempname; #endif gdk_threads_leave (); //downstream errors invoke local mutex locking #ifdef E2_VFS success = e2_task_backend_rename (&sdata, &tdata); #else success = e2_task_backend_rename (slocal, tempname); #endif gdk_threads_enter (); if (success) { #ifdef E2_VFS if (! e2_fs_access2 (&ddata E2_ERR_NONE())) #else if (! e2_fs_access2 (dlocal E2_ERR_NONE())) #endif { //the new name really does exist //revert old name and process normally gdk_threads_leave (); #ifdef E2_VFS e2_task_backend_rename (&tdata, &sdata); #else e2_task_backend_rename (tempname, slocal); #endif gdk_threads_enter (); } else { //the former detection was fake, just do the rest of the rename gdk_threads_leave (); //downstream errors invoke local mutex locking #ifdef E2_VFS e2_task_backend_rename (&tdata, &ddata); #else e2_task_backend_rename (tempname, dlocal); #endif gdk_threads_enter (); doit = CANCEL; } } else { gdk_threads_leave (); //downstream errors invoke local mutex locking #ifdef E2_VFS tdata.localpath = *thisone; e2_fs_error_simple (_("Cannot rename %s"), &tdata); #else e2_fs_error_simple (_("Cannot rename %s"), *thisone); #endif gdk_threads_enter (); doit = CANCEL; } F_FREE (slocal); g_free (tempname); } /* else //we have a real difference { //FIXME duplication here e2_filelist_enable_refresh (); //allow updates while we wait doit = e2_dialog_ow_check (*thisone, dlocal, NOALL); e2_filelist_disable_refresh (); } */ g_free (old_name_lc); g_free (new_name_lc); } if (doit == OK) { gdk_threads_leave (); //downstream errors invoke local mutex locking #ifdef E2_VFS tdata.localpath = *thisone; success = e2_task_backend_rename (&tdata, &ddata); #else success = e2_task_backend_rename (*thisone, dlocal); #endif gdk_threads_enter (); if (success && !_e2p_ren_get_flag (CONFIRM_P)) //, rt)) { //we didn't ask already, so show what's done gchar *msg = g_strdup_printf ("%s %s %s %s %s %s", _("Renamed"), base_public, _("to"), newbase_public, _("in"), dir); e2_output_print (&app.tab, msg, NULL, TRUE, NULL); g_free (msg); } } g_free (newpath); F_FREE (dlocal); } g_free (base); g_free (dir); g_free (newbase); g_free (base_public); g_free (newbase_public); if (doit == NO_TO_ALL || rt->abort) { //confirm dialog or o/w dialog or main dialog stop btn pressed rt->abort = FALSE; //make sure this is now clear break; } thisone++; } //end of items loop if (dialog != NULL) gtk_widget_destroy (dialog); *rt->status = E2_TASK_PAUSED; //we don't know what really needs refreshing ... if (_e2p_ren_get_flag (SEARCH_CURRENT_P)) //, rt)) e2_filelist_request_refresh (curr_view->dir, TRUE); else if (_e2p_ren_get_flag (SEARCH_OTHER_P)) //, rt)) e2_filelist_request_refresh (other_view->dir, TRUE); e2_dialog_set_cursor (rt->dialog, GDK_LEFT_PTR); e2_filelist_enable_refresh(); //cleanups if ((rt->modeflags & E2PR_PATTERN) && (data.flags == E2PR_WILD)) //new pattern with wildcards was constructed g_free (tmp2); g_strfreev (candidates); gint j; //[0] used for counter, = index of last chunk with contents gint m = GPOINTER_TO_INT (rt->chunks[0]); rt->chunks[0] = NULL; for (j = 1; j <= m; j++) { g_free (rt->chunks[j]); rt->chunks[j] = NULL; } rt->parsed = FALSE; //revert the buttons gtk_widget_set_sensitive (rt->help_button, TRUE); gtk_widget_set_sensitive (rt->start_button, TRUE); gtk_widget_set_sensitive (rt->stop_button, FALSE); gtk_window_present (GTK_WINDOW (rt->dialog)); // gtk_widget_grab_focus (GTK_BIN (rt->pattern)->child); } /*********************/ /***** callbacks *****/ /*********************/ /** @brief toggle specified option flag @param button clicked button widget @param flagnum pointerized number of the flag to be toggled @return */ static void _e2p_ren_toggle_cb (GtkWidget *button, gpointer flagnum) { E2_RenDialogRuntime *rt = g_object_get_data (G_OBJECT (button), "e2-runtime"); //if this if this is during setup, before a widget is created ... if (!GTK_WIDGET_MAPPED (rt->dialog)) return; renflag_t flg = (renflag_t) flagnum; gboolean newflag = ! _e2p_ren_get_flag (flg); //, rt); _e2p_ren_set_flag (flg, newflag); //, rt); switch (flg) { case OLD_SEL_P: if (newflag && (_e2p_ren_get_flag (SEARCH_ALL_P) //, rt) || _e2p_ren_get_flag (SEARCH_THIS_P))) //, rt))) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (rt->active_button), TRUE); if (newflag) { gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (rt->recurse_button), FALSE); gtk_widget_set_sensitive (rt->pattern, FALSE); } gtk_widget_set_sensitive (rt->recurse_button, !newflag); break; case OLD_WILD_P: case OLD_REGEX_P: if (newflag) { gtk_widget_set_sensitive (rt->pattern, TRUE); gtk_widget_grab_focus (GTK_BIN (rt->pattern)->child); } break; case SEARCH_ALL_P: if (newflag) { if (_e2p_ren_get_flag (OLD_SEL_P)) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (rt->wild_button), TRUE); } break; case SEARCH_THIS_P: gtk_widget_set_sensitive (rt->directory, newflag); if (newflag) { if (_e2p_ren_get_flag (OLD_SEL_P)) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (rt->wild_button), TRUE); gtk_widget_grab_focus (GTK_BIN (rt->directory)->child); } break; case NEW_THIS_P: gtk_widget_set_sensitive (rt->newpattern, newflag); if (newflag) gtk_widget_grab_focus (GTK_BIN (rt->newpattern)->child); break; default: break; } } /** @brief toggle specified option flag @param button clicked button widget @param flagnum pointerized number of the flag to be toggled @return */ static void _e2p_ren_grouptoggle_cb (GtkWidget *button, gpointer flagnum) { renflag_t flg = (renflag_t) flagnum; // E2_RenDialogRuntime *rt = g_object_get_data (G_OBJECT (button), "e2-runtime"); gboolean newflag = ! _e2p_ren_get_flag (flg); //, rt); _e2p_ren_set_flag (flg, newflag); //, rt); if (newflag) { //clear all other members of the group GtkWidget *tmp = g_object_get_data (G_OBJECT (button), "group_leader"); GSList *members = g_object_get_data (G_OBJECT (tmp), "group_members"); for (;members != NULL; members = members->next) { tmp = (GtkWidget *)members->data; if (tmp != button) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (tmp), FALSE); } } } /** @brief adjust directory string in @a entry After a keypress, this clears any selection and completes the path. If the current content is not an absolute path, the active-pane directory is referenced for completion. @param entry the entry widget for directory data @param event pointer to event data struct @param data UNUSED data specified when callback was connnected @return TRUE if the key was non-modified and a textkey and completion was done */ static gboolean _e2p_ren_key_press2_cb (GtkWidget *entry, GdkEventKey *event, gpointer data) { if ((event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)) == 0 //#ifdef USE_GTK2_10 | GDK_SUPER_MASK | GDK_HYPER_MASK | GDK_META_MASK && (event->keyval < 0xF000 || event->keyval > 0xFFFF) && e2_fs_complete_dir (entry, event->keyval, 0)) //default is active pane return TRUE; return FALSE; } /** @brief entry-activation signal callback to commence renaming @param entry UNUSED entry widget which was activated @param rt ptr to dialog data struct @return */ static void _e2p_ren_activation_cb (GtkEntry *entry, E2_RenDialogRuntime *rt) { _e2p_ren_rename (rt); } /** @brief dialog response callback @param dialog the dialog where the response was triggered @param response the response for the clicked button @param rt pointer to dialog data struct @return */ static void _e2p_ren_response_cb (GtkDialog *dialog, gint response, E2_RenDialogRuntime *rt) { switch (response) { case E2_RESPONSE_USER1: //rename _e2p_ren_rename (rt); break; case E2_RESPONSE_USER2: //help e2_utils_show_help ("rename plugin"); //no translation unless help doc is translated gtk_widget_grab_focus (rt->dialog); break; case E2_RESPONSE_NOTOALL: //stop button click //this can be clicked during a scan for matching items, or before //renaming a matched item rt->abort = TRUE; //this is the pre-rename signal scanaborted = TRUE; //and this for intra-scan signal break; // case GTK_RESPONSE_CLOSE: default: //cancel if (rt->groups != NULL) { //rt->groups is a list of "leader" buttons in the dialog GSList *members, *tmp; for (tmp = rt->groups; tmp != NULL; tmp=tmp->next) { members = g_object_get_data (G_OBJECT (tmp->data), "group_members"); g_slist_free (members); } g_slist_free (rt->groups); } //now that we don't need buttons any longer ... gtk_widget_destroy (rt->dialog); DEALLOCATE (E2_RenDialogRuntime, rt); // gtk_widget_grab_focus (curr_view->treeview); gtk_main_quit (); break; } } /***********************/ /*** widget creation ***/ /***********************/ /** @brief create and show a toggle in a specified container @param box the widget into which the button is to be placed @param label translated string for the button label @param state T/F initial state of the toggle @param callback_fn the button's callback function @param f enumerated value of flag to be associated with the button @param rt ptr to dialog data struct @return the button widget (UNUSED, now) */ static GtkWidget *__e2p_ren_create_toggle_button (GtkWidget *box, gchar *label, gboolean state, gpointer callback_fn, renflag_t f, E2_RenDialogRuntime *rt) { GtkWidget *button = e2_button_add_toggle (box, TRUE, state, label, NULL, FALSE, E2_PADDING_XSMALL, callback_fn, (gpointer) f); g_object_set_data (G_OBJECT (button), "e2-runtime", rt); //cached flags default to FALSE // if (state) // _e2p_ren_set_flag (f, TRUE); //, rt); return button; } /** @brief create and show a grouped toggle in a specified container @param box the widget into which the button is to be placed @param leader widget for the 'leader' of the group, or NULL if this is the leader @param label translated string for the button label @param f enumerated value of flag to be associated with the button @param rt ptr to dialog data struct @return the button widget */ static GtkWidget *_e2p_ren_create_toggle_grouped_button (GtkWidget *box, GtkWidget *leader, gchar *label, renflag_t f, E2_RenDialogRuntime *rt) { gboolean state = _e2p_ren_get_flag (f); GtkWidget *button = __e2p_ren_create_toggle_button (box, label, state, _e2p_ren_grouptoggle_cb, f, rt); GtkWidget *leadptr; GSList *members; if (leader == NULL) { //this is the leader of a new group leadptr = button; //leader points to self members = NULL; //start a new list rt->groups = g_slist_append (rt->groups, button); //remember it, for cleaning up } else { //this is a group member leadptr = leader; //point to group leader, which has list members = g_object_get_data (G_OBJECT (leader), "group_members"); } members = g_slist_append (members, button); g_object_set_data (G_OBJECT (leadptr), "group_members", members); g_object_set_data (G_OBJECT (button), "group_leader", leadptr); return button; } /** @brief create and show a toggle in a specified container @param box the widget into which the button is to be placed @param label translated string for the button label @param state T/F initial state of the toggle @param f enumerated value of flag to be associated with the button @param rt pointer to dialog data struct @return the button widget */ static GtkWidget *_e2p_ren_create_toggle_button (GtkWidget *box, gchar *label, renflag_t f, E2_RenDialogRuntime *rt) { gboolean state = _e2p_ren_get_flag (f); GtkWidget *button = __e2p_ren_create_toggle_button (box, label, state, _e2p_ren_toggle_cb, f, rt); return button; } /** @brief create and show a 'leader' radio button in a specified container The initial button-state is set to value of @a f, it may be toggled by other group members @param box the widget into which the button is to be placed @param label translated string for the button label @param f enumerated value of flag to be associated with the button @param rt pointer to dialog data struct @return the button widget */ static GtkWidget *_e2p_ren_create_radio_button (GtkWidget *box, gchar *label, renflag_t f, E2_RenDialogRuntime *rt) { GtkWidget *button = e2_button_add_radio (box, label, NULL, _e2p_ren_get_flag (f), TRUE, 0, _e2p_ren_toggle_cb, (gpointer) f); g_object_set_data (G_OBJECT (button), "e2-runtime", rt); return button; } /** @brief create and show a radio btn in a specified container The intial button-state is set to the value of @a f @param box the widget into which the button is to be placed @param leader the radio button widget that 'leads' the group @param label translated string for the button label @param f enumerated value of flag to be associated with the button @param rt pointer to dialog data struct @return the button widget */ static GtkWidget *_e2p_ren_create_radio_grouped_button (GtkWidget *box, GtkWidget *leader, gchar *label, renflag_t f, E2_RenDialogRuntime *rt) { GSList *group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (leader)); GtkWidget *button = e2_button_add_radio (box, label, group, _e2p_ren_get_flag (f), TRUE, 0, _e2p_ren_toggle_cb, (gpointer) f); g_object_set_data (G_OBJECT (button), "e2-runtime", rt); return button; } /* * @brief create and show a file selection widget for identifying the search-root dir @param title string used for file selection widget title @param ok ptr to void ok-callback for the widget @param cancel ptr to void cancel-callback for the widget @param rt ptr to find data struct @return the file selection widget */ /* UNUSED static GtkWidget *_e2p_ren_create_filesel (gchar *title, void (*ok)(GtkWidget *, E2_FindDialogRuntime *), void(*cancel)(GtkWidget *, E2_FindDialogRuntime *), E2_FindDialogRuntime *rt ) { GtkWidget *wid = gtk_file_selection_new (title); g_signal_connect (G_OBJECT (GTK_FILE_SELECTION (wid)->ok_button), "clicked", G_CALLBACK (ok), rt); g_signal_connect (G_OBJECT (GTK_FILE_SELECTION (wid)->cancel_button), "clicked", G_CALLBACK (cancel), rt); gtk_widget_show (wid); return wid; } */ /** @brief establish and show rename dialog Operation is queued, even though it does not necessarily work on either displayed filelist. @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if action completed successfully, else FALSE */ static gboolean _e2p_rename_dialog_create (gpointer from, E2_ActionRuntime *art) { return (e2_task_do_task (E2_TASK_RENAME, art, from, _e2p_renameQ, NULL)); } static gboolean _e2p_renameQ (E2_ActionTaskData *qed) { //init runtime object E2_RenDialogRuntime *rt = ALLOCATE0 (E2_RenDialogRuntime); CHECKALLOCATEDWARN (rt, return FALSE;) rt->status = qed->status; //enable status changes on-the-fly //don't need to be considered active while simply showing the dialog *qed->status = E2_TASK_PAUSED; //create dialog rt->dialog = e2_dialog_create (NULL, NULL, _("rename items"), _e2p_ren_response_cb, rt); //populate it with widgets GtkWidget *vbox, *hbox; vbox = GTK_DIALOG (rt->dialog)->vbox; // hbox = e2_widget_add_box (vbox, TRUE, 0, FALSE, FALSE, 0); e2_widget_add_mid_label (vbox, _("Search for items:"), 0.02, TRUE, 0); GtkWidget *radio = _e2p_ren_create_radio_button (vbox, _("any_where"), SEARCH_ALL_P, rt); rt->active_button = _e2p_ren_create_radio_grouped_button (vbox, radio, _("in _active directory"), SEARCH_CURRENT_P, rt); _e2p_ren_create_radio_grouped_button (vbox, radio, _("in _other directory"), SEARCH_OTHER_P, rt); _e2p_ren_create_radio_grouped_button (vbox, radio, _("in _this directory"), SEARCH_THIS_P, rt); gdk_threads_enter (); //reduce delay when keying in data (dunno why) rt->directory = e2_combobox_add (vbox, FALSE, 0, _e2p_ren_activation_cb, rt, &dir_history, E2_COMBOBOX_HAS_ENTRY | E2_COMBOBOX_FOCUS_ON_CHANGE); gboolean state = _e2p_ren_get_flag (SEARCH_THIS_P); gtk_widget_set_sensitive (rt->directory, state); //later toggled if corresponsing radio is selected gdk_threads_leave (); //handle path completion g_signal_connect (G_OBJECT (GTK_BIN (rt->directory)->child), "key-press-event", G_CALLBACK (_e2p_ren_key_press2_cb), NULL); rt->recurse_button = _e2p_ren_create_toggle_button (vbox, _("R_ecurse subdirectories"), SEARCH_SUBDIRS_P, rt); e2_widget_add_separator (vbox, TRUE, 0); hbox = e2_widget_add_box (vbox, TRUE, 0, FALSE, FALSE, E2_PADDING); radio = _e2p_ren_create_radio_button (hbox, _("_Selected item(s)"), OLD_SEL_P, rt); hbox = e2_widget_add_box (vbox, TRUE, 0, FALSE, FALSE, E2_PADDING); rt->wild_button = _e2p_ren_create_radio_grouped_button (hbox, radio, _("Match _exact/wildcard"), OLD_WILD_P, rt); _e2p_ren_create_radio_grouped_button (hbox, radio, _("Match regular e_xpression"), OLD_REGEX_P, rt); hbox = e2_widget_add_box (vbox, TRUE, 0, FALSE, FALSE, E2_PADDING); e2_widget_add_mid_label (hbox, _("Current name is like this:"), 0.05, TRUE, 0); gdk_threads_enter (); rt->pattern = e2_combobox_add (hbox, FALSE, 0, _e2p_ren_activation_cb, rt, &pattern_history, E2_COMBOBOX_HAS_ENTRY | E2_COMBOBOX_FOCUS_ON_CHANGE); gtk_entry_set_text (GTK_ENTRY (GTK_BIN (rt->pattern)->child), "(.*)"); state = _e2p_ren_get_flag (OLD_SEL_P); gtk_widget_set_sensitive (rt->pattern, !state); //later toggled if corresponsing radio is selected gdk_threads_leave (); e2_widget_add_separator (vbox, TRUE, 0); hbox = e2_widget_add_box (vbox, TRUE, 0, FALSE, FALSE, E2_PADDING); /* radio = _e2p_ren_create_radio_button (hbox, _("New name is _upper case"), NEW_UPPER_P, rt); _e2p_ren_create_radio_grouped_button (hbox, radio, _("New name is _lower case"), NEW_LOWER_P, rt); hbox = e2_widget_add_box (vbox, TRUE, 0, FALSE, FALSE, E2_PADDING); _e2p_ren_create_radio_grouped_button (hbox, radio, _("_New name is like this:"), NEW_THIS_P, rt); */ radio = _e2p_ren_create_toggle_grouped_button (hbox, NULL, _("New name is _upper case"), NEW_UPPER_P, rt); _e2p_ren_create_toggle_grouped_button (hbox, radio, _("New name is _lower case"), NEW_LOWER_P, rt); hbox = e2_widget_add_box (vbox, TRUE, 0, FALSE, FALSE, E2_PADDING); _e2p_ren_create_toggle_button (hbox, _("_New name is like this:"), NEW_THIS_P, rt); gdk_threads_enter (); rt->newpattern = e2_combobox_add (hbox, FALSE, 0, _e2p_ren_activation_cb, rt, &newpattern_history, E2_COMBOBOX_HAS_ENTRY | E2_COMBOBOX_FOCUS_ON_CHANGE); state = _e2p_ren_get_flag (NEW_THIS_P); gtk_widget_set_sensitive (rt->newpattern, state); //later toggled if corresponsing radio is selected // gtk_entry_set_text (GTK_ENTRY (rt->newpattern), "\\1"); //FIXME utf-8 // g_object_set_data (G_OBJECT (rt->newpattern), "reset_yourself", reset_entry); gdk_threads_leave (); e2_widget_add_separator (vbox, TRUE, 0); _e2p_ren_create_toggle_button (vbox, _("Con_firm before each rename"), CONFIRM_P, rt); //add action buttons in the order that they will appear rt->help_button = e2_dialog_add_undefined_button_custom (rt->dialog, FALSE, E2_RESPONSE_USER2, _("_Help"), GTK_STOCK_HELP, _("Get advice on rename options"), NULL, NULL); rt->stop_button = e2_dialog_add_button_custom (rt->dialog, FALSE, &E2_BUTTON_NOTOALL, _("Stop the current search"), NULL, NULL); //de-sensitize stop btn, at this stage gtk_widget_set_sensitive (rt->stop_button, FALSE); rt->start_button = e2_dialog_add_undefined_button_custom (rt->dialog, FALSE, E2_RESPONSE_USER1, _("_Rename"), GTK_STOCK_CONVERT, _("Begin renaming"), NULL, NULL); e2_dialog_add_button_custom (rt->dialog, TRUE, &E2_BUTTON_CLOSE, NULL, NULL, NULL); e2_dialog_set_negative_response (rt->dialog, E2_RESPONSE_NOTOALL); state = _e2p_ren_get_flag (OLD_SEL_P); if (!state) gtk_widget_grab_focus (GTK_BIN (rt->pattern)->child); e2_dialog_setup (rt->dialog, app.main_window); gdk_threads_enter (); e2_dialog_run (rt->dialog, NULL, 0); gtk_main (); gdk_threads_leave (); return TRUE; } //aname must be confined to this module static gchar *aname; /** @brief plugin initialization function, called by main program @param p ptr to plugin data struct @return TRUE if the initialization succeeds, else FALSE */ gboolean init_plugin (Plugin *p) { #define ANAME "rename" aname = _("renext"); p->signature = ANAME VERSION; p->menu_name = _("_Rename.."); p->description = _("Rename item(s), using wildcards or regular-expressions"); p->icon = "plugin_rename_"E2IP".png"; if (p->action == NULL) { dir_history = (GList *)g_new0 (gpointer, 1); pattern_history = (GList *)g_new0 (gpointer, 1); newpattern_history = (GList *)g_new0 (gpointer, 1); if (!e2_cache_check ("rename-flags")) { //initialise TRUE flags flags[SEARCH_CURRENT_P] = TRUE; flags[OLD_WILD_P] = TRUE; flags[NEW_THIS_P] = TRUE; flags[CONFIRM_P] = TRUE; } e2_cache_array_register ("rename-flags", MAX_FLAGS, flags, flags); e2_cache_list_register ("rename-dir-history", &dir_history); e2_cache_list_register ("rename-oldpattern-history", &pattern_history); e2_cache_list_register ("rename-newpattern-history", &newpattern_history); //no need to free this gchar *action_name = g_strconcat (_A(1),".",aname,NULL); p->action = e2_plugins_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2p_rename_dialog_create, NULL, FALSE, 0, NULL); return TRUE; } return FALSE; } /** @brief cleanup transient things for this plugin @param p pointer to data struct for the plugin @return TRUE if all cleanups were completed */ gboolean clean_plugin (Plugin *p) { gchar *action_name = g_strconcat (_A(1),".",aname,NULL); gboolean ret = e2_plugins_action_unregister (action_name); g_free (action_name); if (ret) { //backup the cache data e2_cache_unregister ("rename-flags"); e2_cache_unregister ("rename-dir-history"); e2_cache_unregister ("rename-oldpattern-history"); e2_cache_unregister ("rename-newpattern-history"); //cleanup e2_list_free_with_data (&dir_history); e2_list_free_with_data (&pattern_history); e2_list_free_with_data (&newpattern_history); } return ret; } emelfm2-0.4.1/plugins/e2p_view.c0000600000175000017500000000640710657677167015411 0ustar cairocairo/* $Id: e2p_view.c 598 2007-08-12 21:41:11Z tpgww $ Copyright (C) 2003-2007 tooar Portions copyright (C) 1999 Michael Clark This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file plugins/e2p_view.c @brief plugin for view first selected item using the internal viewer */ #include "emelfm2.h" #include "e2_plugins.h" #include "e2_task.h" static gboolean _e2p_viewQ (E2_ActionTaskData *qed); /** @brief view first selected item using the internal viewer @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if action completed successfully, else FALSE */ static gboolean _e2p_view (gpointer from, E2_ActionRuntime *art) { return (e2_task_do_task (E2_TASK_VIEW, art, from, _e2p_viewQ, NULL)); } static gboolean _e2p_viewQ (E2_ActionTaskData *qed) { gboolean retval; GPtrArray *names = qed->names; if (names == NULL) return FALSE; E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata; //".." entries filtered when names compiled gchar *localpath = e2_utils_strcat (qed->currdir, (*iterator)->filename); #ifdef E2_VFS VPATH ddata = { localpath, qed->currspace }; #endif gdk_threads_enter (); #ifdef E2_VFS retval = e2_view_dialog_create (&ddata); #else retval = e2_view_dialog_create (localpath); #endif gdk_threads_leave (); g_free (localpath); return retval; } //aname must be confined to this module static gchar *aname; /** @brief plugin initialization function, called by main program @param p ptr to plugin data struct @return TRUE if the initialization succeeds, else FALSE */ gboolean init_plugin (Plugin *p) { #define ANAME "view" aname = _("view_with_plugin"); //note: the action-name cannot be same as internal file.view p->signature = ANAME VERSION; p->menu_name = _("_View"); p->description = g_strdup_printf (_("Open the first selected item with the %s text-file viewer"), PROGNAME); // p->icon = "plugin_"ANAME"_"E2IP".png"; //use icon file pathname if appropriate // p->icon = ""; add icon file pathname if appropriate p->cleanflags = E2P_CLEANTIP; if (p->action == NULL) { //no need to free this gchar *action_name = g_strconcat (_A(5),".",aname,NULL); p->action = e2_plugins_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2p_view, NULL, FALSE, 0, NULL); return TRUE; } return FALSE; } /** @brief cleanup transient things for this plugin @param p pointer to data struct for the plugin @return TRUE if all cleanups were completed */ gboolean clean_plugin (Plugin *p) { gchar *action_name = g_strconcat (_A(5),".",aname,NULL); gboolean ret = e2_plugins_action_unregister (action_name); g_free (action_name); return ret; } emelfm2-0.4.1/plugins/e2p_dircmp.h0000600000175000017500000003064011010340377015664 0ustar cairocairo/* $Id: e2p_dircmp.h 874 2008-05-07 14:47:27Z tpgww $ This file is essentially md5deep.h and md5.h from the appplication md5deep 1.9.1 by Jesse Kornblum To that extent, this is a work of the US Government. In accordance with 17 USC 105, copyright protection is not available for any work of the US Government. This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file plugins/e2p_dircmp.h @brief header for direcory-compare plugin */ #ifndef __E2P_DIRCMP_H__ #define __E2P_DIRCMP_H__ /* We use \r\n for newlines as this has to work on Win32. It's redundant for everybody else, but shouldn't cause any harm. */ /*#define COPYRIGHT "This program is a work of the US Government. "\ "In accordance with 17 USC 105,\r\n"\ "copyright protection is not available for any work of the US Government.\r\n"\ "This is free software; see the source for copying conditions. There is NO\r\n"\ "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\r\n" */ //#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __LINUX #include #include #endif /* This allows us to open standard input in binary mode by default See http://gnuwin32.sourceforge.net/compile.html for more */ #include /* A few operating systems (e.g. versions of OpenBSD) don't meet the C99 standard and don't define the PRI??? macros we use to display large numbers. We have to do something to help those systems, so we guess. This snippet was copied from the FreeBSD source tree, so hopefully it should work on the other BSDs too. */ #ifndef PRIu64 #define PRIu64 "llu" #endif #ifndef MAX #define MAX(A,B) (A>B)?A:B #endif #define STRINGS_EQUAL(A,B) (!strncasecmp(A,B,MAX(strlen(A),strlen(B)))) /* MD5 and SHA-1 setup require knowing if we're big or little endian */ #ifdef __LINUX #ifndef __USE_BSD #define __USE_BSD #endif #include #elif defined (__SOLARIS) #define BIG_ENDIAN 4321 #define LITTLE_ENDIAN 1234 #include #ifdef _BIG_ENDIAN #define BYTE_ORDER BIG_ENDIAN #else #define BYTE_ORDER LITTLE_ENDIAN #endif #elif defined (_WIN32) #include #elif defined (__APPLE__) #include #endif // Some algorithms need to know if this is a big endian host #if BYTE_ORDER == BIG_ENDIAN // For MD5 #define HIGHFIRST 1 // For Tiger #define BIG_ENDIAN_HOST #endif // #if BYTE_ORDER == BIG_ENDIAN //#ifndef TRUE //#define TRUE 1 //#endif //#ifndef FALSE //#define FALSE 0 //#endif #define ONE_MEGABYTE 1048576 /* Strings have to be long enough to handle inputs from matched hashing files. The NSRL is already larger than 256 bytes. We go longer to be safer. */ #define MAX_STRING_LENGTH 1024 /* LINE_LENGTH is different between UNIX and WIN32 and is defined below */ #define MAX_FILENAME_LENGTH LINE_LENGTH - 41 /* These are the types of files that we can match against */ #define TYPE_PLAIN 0 #define TYPE_BSD 1 #define TYPE_HASHKEEPER 2 #define TYPE_NSRL_15 3 #define TYPE_NSRL_20 4 #define TYPE_ILOOK 5 #define TYPE_UNKNOWN 254 #define mode_none 0 #define mode_recursive 1<<0 #define mode_estimate 1<<1 #define mode_silent 1<<2 #define mode_match 1<<3 #define mode_match_neg 1<<4 #define mode_display_hash 1<<5 #define mode_display_size 1<<6 #define mode_zero 1<<7 #define mode_relative 1<<8 #define mode_which 1<<9 #define mode_barename 1<<10 #define mode_asterisk 1<<11 #define mode_not_matched 1<<12 /* Modes 13 to 22 and 32 to 63 are reserved for future use. (Yes, I could move the expert file modes, below, up to the higher ranger of numbers, but it's working now, and so why change anything? The next person who wants to add a lot of modes can have the fun.) */ #define mode_regular 1<<23 #define mode_directory 1<<24 #define mode_door 1<<25 #define mode_block 1<<26 #define mode_character 1<<27 #define mode_pipe 1<<28 #define mode_socket 1<<29 #define mode_symlink 1<<30 #define mode_expert 1<<31 /* These are the types of files we can encounter while hashing */ #define file_regular 0 #define file_directory 1 #define file_door 3 #define file_block 4 #define file_character 5 #define file_pipe 6 #define file_socket 7 #define file_symlink 8 #define file_unknown 254 #define M_MATCH(A) (A & mode_match) #define M_MATCHNEG(A) (A & mode_match_neg) #define M_RECURSIVE(A) (A & mode_recursive) #define M_ESTIMATE(A) (A & mode_estimate) #define M_SILENT(A) (A & mode_silent) #define M_DISPLAY_HASH(A) (A & mode_display_hash) #define M_DISPLAY_SIZE(A) (A & mode_display_size) #define M_ZERO(A) (A & mode_zero) #define M_RELATIVE(A) (A & mode_relative) #define M_WHICH(A) (A & mode_which) #define M_BARENAME(A) (A & mode_barename) #define M_ASTERISK(A) (A & mode_asterisk) #define M_NOT_MATCHED(A) (A & mode_not_matched) #define M_EXPERT(A) (A & mode_expert) #define M_REGULAR(A) (A & mode_regular) #define M_BLOCK(A) (A & mode_block) #define M_CHARACTER(A) (A & mode_character) #define M_PIPE(A) (A & mode_pipe) #define M_SOCKET(A) (A & mode_socket) #define M_DOOR(A) (A & mode_door) #define M_SYMLINK(A) (A & mode_symlink) // Return values for the program #define STATUS_OK 0 #define STATUS_UNUSED_HASHES 1 #define STATUS_INPUT_DID_NOT_MATCH 2 #define STATUS_USER_ERROR 64 #define STATUS_INTERNAL_ERROR 128 #ifdef __SOLARIS #define u_int32_t unsigned int #define u_int64_t unsigned long #endif /* Set up the environment for the *nix operating systems (Mac, Linux, BSD, Solaris, and really everybody except Microsoft Windows) */ #ifndef _WIN32 #include // These prototypes help us avoid compiler warnings on older systems int fseeko(FILE *stream, off_t offset, int whence); off_t ftello(FILE *stream); #define CMD_PROMPT "$" #define DIR_SEPARATOR '/' #define NEWLINE "\n" #define LINE_LENGTH 74 #define BLANK_LINE \ " " #endif // #ifndef _WIN32 #ifdef _WIN32 /* The current cross compiler for OS X->Windows does not support a few critical error codes normally defined in errno.h. Because we need these to detect fatal errors while reading files, we have them here. These will hopefully get wrapped into the Windows API sometime soon. */ #ifndef ENOTBLK #define ENOTBLK 15 // Not a block device #endif #ifndef ETXTBSY #define ETXTBSY 26 // Text file busy #endif #ifndef EAGAIN #define EAGAIN 35 // Resource temporarily unavailable #endif #ifndef EALREADY #define EALREADY 37 // Operation already in progress #endif #define CMD_PROMPT "c:\\>" #define DIR_SEPARATOR '\\' #define NEWLINE "\r\n" #define LINE_LENGTH 72 #define BLANK_LINE \ " " /* By default BUFSIZ is 512 on Windows. We make it 8192 so that it's the same as UNIX. While that shouldn't mean anything in terms of computing the hash values, it should speed us up a little bit. */ #ifdef BUFSIZ #undef BUFSIZ #endif #define BUFSIZ 8192 #define ftello ftell #define fseeko fseek #define snprintf _snprintf #define u_int32_t unsigned long /* We create macros for the Windows equivalent UNIX functions. No worries about lstat to stat; Windows doesn't have symbolic links */ #define lstat(A,B) stat(A,B) #define realpath(A,B) _fullpath(B,A,PATH_MAX) /* Not used in md5deep anymore, but left in here in case I ever need it again. Win32 documentation searches are evil. int asprintf(char **strp, const char *fmt, ...); */ char *basename(char *a); extern char *optarg; extern int optind; int getopt(int argc, char *const argv[], const char *optstring); #endif /* ifdef _WIN32 */ /* On non-glibc systems we have to manually set the __progname variable */ #ifdef __GLIBC__ extern char *__progname; #else char *__progname; #endif /* ifdef __GLIBC__ */ /* The algorithm headers need all of the operating system specific defines, so we don't add them until down here */ //#include "algorithms.h" /* ----------------------------------------------------------------- Function definitions ----------------------------------------------------------------- */ /* To avoid cycles */ int have_processed_dir(uint64_t mode, char *fn); int processing_dir(uint64_t mode, char *fn); int done_processing_dir(uint64_t mode, char *fn); /* Functions from matching (match.c) */ int load_match_file(uint64_t mode, char *filename); int is_known_hash(char *h, char *known_fn); int was_input_not_matched(void); int finalize_matching(uint64_t mode); // Add a single hash to the matching set void add_hash(uint64_t mode, char *h, char *fn); /* Functions for file evaluation (files.c) */ int valid_hash(char *buf); int hash_file_type(FILE *f); int find_hash_in_line(char *buf, int fileType, char *filename); /* Dig into file hierarchies */ int process(uint64_t mode, char *input); /* Hashing functions */ int hash_file(uint64_t mode, char *filename); int hash_stdin(uint64_t mode); /* Miscellaneous helper functions */ void shift_string(char *fn, int start, int new_start); void print_error(uint64_t mode, char *fn, char *msg); void internal_error(char *fn, char *msg); void make_newline(uint64_t mode); int find_comma_separated_string(char *s, unsigned int n); int find_quoted_string(char *buf, unsigned int n); /* Return the size, in bytes of an open file stream. On error, return -1 */ off_t find_file_size(FILE *f); #ifdef HASH_ALGORITHM #error Hash algorithm already defined! #else #define HASH_ALGORITHM "MD5" #endif /* Bytes in hash */ #define HASH_LENGTH 16 /* Characters needed to display hash. This is *usually* twice the HASH_LENGTH defined above. This number is used to find and compare hashes as part of the matching process. */ #define HASH_STRING_LENGTH 32 /* These supply the hashing code, hash.c, with the names of the functions used in the algorithm to do the real computation. HASH_Init takes a HASH_CONTEXT only HASH_Update takes a hash context, the buffer, and its size in bytes HASH_Final takes a char to put the sum in and then a context */ #define HASH_CONTEXT MD5_CTX #define HASH_Init(A) MD5Init(A) #define HASH_Update(A,B,C) MD5Update(A,B,C) #define HASH_Final(A,B) MD5Final(A,B) /* Define which types of files of known hashes that support this type of hash. The values, if given, are the location of the hash value in terms of number of commas that preceed the hash. For example, if the file format is: hash,stuff,junk the define should be SUPPORT_FORMAT 0 if the file format is stuff,junk,hash the define should be SUPPORT_FORMAT 2 Remember that numbers are not necessary for all file types! */ #define SUPPORT_PLAIN #define SUPPORT_BSD #define SUPPORT_HASHKEEPER 4 #define SUPPORT_ILOOK #define SUPPORT_NSRL_15 6 #define SUPPORT_NSRL_20 1 /* -------------------------------------------------------------- */ /* After this is the algorithm itself. You shouldn't change these */ struct MD5Context { u_int32_t buf[4]; u_int32_t bits[2]; unsigned char in[64]; }; /* This is needed to make RSAREF happy on some MS-DOS compilers */ typedef struct MD5Context MD5_CTX; void MD5Init(struct MD5Context *context); void MD5Update(struct MD5Context *context, unsigned char const *buf, unsigned len); void MD5Final(unsigned char digest[16], struct MD5Context *context); void MD5Transform(u_int32_t buf[4], u_int32_t const in[16]); #endif //ndef __E2P_DIRCMP_H__ emelfm2-0.4.1/plugins/e2p_mvbar.c0000600000175000017500000006610310724077122015521 0ustar cairocairo/* $Id: e2p_mvbar.c 773 2007-11-30 21:19:14Z tpgww $ ************************************** Emelmovebar - a plugin for emelFM2 by Florian Zaehringer with help from tooar and tom to port it to Gtk+2 source of source-code and inspiration: emelFM by Michael Clark ************************************** This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file plugins/e2p_mvbar.c @brief plugin for moving selected items, with a progress-bar */ #include "emelfm2.h" #include #include #include #include "e2_plugins.h" #include "e2_dialog.h" #include "e2_option.h" #include "e2_filelist.h" #include "e2_task.h" //max. no. of _bytes_, at the end of a copied item's source-path //and dest-path, shown in the progress dialog //paths longer than this will be 'ellipsized' #define MAX_CHAR 55 //size of buffer for progress dialog //make this >= (MAX_CHAR+4)*2 + space for rest of message text //#define MAX_MSG 220 //interval (usec) between progress window updates //for items < and > 10MB resepcively #define MIN_UPDATE_INTERVAL 100000 #define MAX_UPDATE_INTERVAL 200000 //include things to support pause and resume #ifndef IS_PAUSABLE #define IS_PAUSABLE #endif #ifdef IS_PAUSABLE typedef enum { E2_BARTASK_STOPPED = 1, E2_BARTASK_PAUSEREQ = 1 << 1, //pause requested E2_BARTASK_PAUSED = 1 << 2, //actually paused E2_BARTASK_COMPLETED = 1 << 3, E2_BARTASK_SUCCEEDED = 1 << 4, } E2_BarFlags; #endif typedef struct _E2_BarData { guint64 count; //count of items to be processed, in active pane guint64 totalsize; //aggregate apparent size of items to be processed } E2_BarData; typedef struct _E2_ProgressData { pthread_mutex_t mutex; //protects access to these data pthread_cond_t cond; //signals change VPATH *dlocal; //includes localised destination path (maybe temp) of item moved guint64 done_size; guint64 refresh_interval; } E2_ProgressData; typedef struct _E2_ActionData { // pthread_mutex_t mutex; //protects access to these data E2_FileTaskMode flags; //flags for features of current copy VPATH *slocal; //includes localised source path of item copied VPATH *dlocal; //includes localised destination path of item copied gboolean completed; //set TRUE when the backend task is completed (valid or not) gboolean result; //value returned by backend task function } E2_ActionData; typedef struct _E2_BarWindowData { GtkWidget *dialog; GtkWidget *label; // GtkWidget *label2; GtkWidget *progbar; #ifdef IS_PAUSABLE GtkWidget *pause_btn; GtkWidget *resume_btn; GtkWidget *stop_btn; E2_BarFlags flags; #endif } E2_BarWindowData; static gboolean _e2p_mvbarQ (E2_ActionTaskData *qed); /** @brief update items' count and total size This is a callback for a treewalk function Error message expects BGL to be open @param localpath absolute path of item reported by the walker, localised string @param statptr pointer to struct stat with data about @a localpath @param status code from the walker, indicating what type of report it is @param twdata pointer to tw data struct @return E2TW_CONTINUE always */ static E2_TwResult _e2p_mvbar_twcb (VPATH *localpath, const struct stat *statptr, E2_TwStatus status, E2_BarData *twdata) { switch (status) { case E2TW_F: //not dir or link case E2TW_SL: //symbolic link case E2TW_SLN: //symbolic link naming non-existing file twdata->totalsize += statptr->st_size; case E2TW_DL: //dir, not opened due to tree-depth limit (reported upstream) case E2TW_DM: //dir, not opened due to different file system (reported upstream) case E2TW_D: //dir (don't care about its reported size) case E2TW_DRR: //dir now readable case E2TW_DNR: //unreadable dir (reported upstream) case E2TW_NS: //un-statable item (reported upstream) twdata->count++; // case E2TW_DP: //dir, finished default: break; } return E2TW_CONTINUE; } /** @brief 'shorten' string @a string if it is longer than allowed @a dots is set to "..." if @a string is too long, or else "" @param string utf string which may need to be shortened @param dots pointer to store for utf string which will preceed @a string, when it is displayed @param string_diff pointer to store for the no. of bytes to be omitted from the start of @a string, when it is displayed @return */ /*static void _e2p_mvbar_shorten (gchar *string, gchar **dots, gint *string_diff) { if (strlen (string) > MAX_CHAR) { //nominally we will drop this many bytes off the start of string gint trunc = strlen (string) - MAX_CHAR; //but make sure we don't cut off in the middle of a char gchar *c = g_utf8_find_next_char (string+trunc-1, NULL); *string_diff = c - string; *dots = "..."; } else { *string_diff = 0; *dots = ""; } return; } */ /** @brief thread function to determine how much progress has been made on the current item This provides data for progressive updates during the course of moving an item. The thread doesn't end of its own accord, but continues until it is cancelled by the main thread BGL will be open @param data pointer to struct with data which is needed here @return NULL (never happens) */ static void * _e2p_mvbar_progress (E2_ProgressData *data) { gchar *localpath; struct stat sb; #ifdef E2_VFS VPATH ddata; #endif // printd (DEBUG, "monitor thread underway"); e2_utils_block_thread_signals (); //block all allowed signals to this thread #ifdef E2_VFS ddata.spacedata = data->dlocal->spacedata; #endif while (TRUE) { pthread_testcancel (); //has a cancel-instruction been issued ? //prevent the main thread from setting the cancel signal now if (pthread_mutex_lock (&(data->mutex))) { printd (WARN, "Lock error!"); } //get a local copy of the present item's destination path //(that will be NULL if the action thread has finished already) if (data->dlocal == NULL) localpath = NULL; else localpath = g_strdup (VPSTR (data->dlocal)); if (pthread_mutex_unlock (&(data->mutex))) { printd (WARN,"Unlock error!"); } if (localpath != NULL) { //we're still underway //determine how much of current item has been moved E2_BarData pdata; pdata.totalsize = 0; // pdata.count = 0; #ifdef E2_VFS ddata.localpath = localpath; if (!e2_fs_lstat (&ddata, &sb E2_ERR_NONE())) #else if (!e2_fs_lstat (localpath, &sb E2_ERR_NONE())) #endif { if (S_ISDIR (sb.st_mode)) { //if (! #ifdef E2_VFS e2_fs_tw (&ddata, #else e2_fs_tw (localpath, #endif _e2p_mvbar_twcb, &pdata, -1, E2TW_PHYS E2_ERR_NONE()); //) //{ //FIXME handle error //} } else // { pdata.totalsize = sb.st_size; // pdata.count = 1; // } } g_free (localpath); if (pthread_mutex_lock (&(data->mutex))) { printd (WARN, "Lock error!\n"); } //update size data for the main thread to use data->done_size = pdata.totalsize; // printd (DEBUG, "progress is %li", pdata.totalsize); if (pthread_cond_signal (&data->cond)) { printd (WARN,"Signal error!"); } if (pthread_mutex_unlock (&(data->mutex))) { printd (WARN,"Unlock error!"); } } //usleep is another cancellation point usleep (data->refresh_interval); //delay between updates } return NULL; } /** @brief perform the move This is the thread function which performs the move task. The thread simply exits when completed @param data pointer to struct with data which is needed here @return NULL (irrelevant pointer) */ static void * _e2p_mvbar_action (E2_ActionData *data) { // printd (DEBUG, "action thread underway for %s", data->slocal); e2_utils_block_thread_signals (); //block all allowed signals to this thread data->result = e2_task_backend_move //no background-move option in this context (data->slocal, data->dlocal); data->completed = TRUE; return NULL; } /** @brief update progress window details, and move item @a src to @a dest This is called with BGL open @param slocal path of item to be moved, localised string @param dlocal new path of moved item, localised string @param realmove TRUE if this is a move between devices or partititions @param breakflag pointer to store for abort-flag @param flags bitflags indicating task parameters @param bdata pointer to bar data struct holding progress data @param tdata pointer to bar data struct holding totals data @param wdata pointer to info-window data struct @return */ static void _e2p_mvbar_exec (VPATH *slocal, VPATH *dlocal, gboolean realmove, #ifndef IS_PAUSABLE gboolean *breakflag, #endif E2_FileTaskMode flags, E2_BarData *bdata, E2_BarData *tdata, E2_BarWindowData *wdata) { gchar progresstext[64]; //utf-8 string, middle line of progress dialog //localised strings gchar *src = F_FILENAME_FROM_LOCALE (VPSTR (slocal)); // gchar *dest = F_FILENAME_FROM_LOCALE (VPSTR (dlocal)); gchar *dest_dir = g_path_get_dirname (VPSTR (dlocal)); //before move starts, get size of item to be moved E2_BarData pdata = { 0,0 }; //if (! e2_fs_tw (slocal, _e2p_mvbar_twcb, &pdata, -1, E2TW_PHYS E2_ERR_NONE()); //) //{ //FIXME handle error //} //create action thread //work with a temp name so that the backend doesn't create its own //(which would prevent destination-monitoring) gchar *templocal = e2_utils_get_tempname (VPSTR (dlocal)); #ifdef E2_VFS VPATH tempdata = { templocal, dlocal->spacedata }; E2_ActionData a_data = { flags, slocal , &tempdata, FALSE, FALSE }; #else E2_ActionData a_data = { flags, slocal, templocal, FALSE, FALSE }; #endif pthread_t action_thread_id; gint status = pthread_create (&action_thread_id, NULL, (void *) _e2p_mvbar_action, &a_data); if (status != 0) { printd (WARN,"action-thread-create error!"); // if (errno == EAGAIN) // printd (WARN, "not enough system resources"); g_free (templocal); return; } //wait for task to initiate, at least guint waitup; if (!realmove) waitup = 1000; //shorter delay if the move is logical (same device) else waitup = 50000; //longer delay if the move needs to be physical g_usleep (waitup); //create progress monitor, if task not finished already //NOTE since we're processing 1 item at a time, this monitoring //could probably be handled just as well in the main thread if (!a_data.completed) { //create monitor thread E2_ProgressData m_data; pthread_mutex_init (&(m_data.mutex), NULL); pthread_cond_init (&(m_data.cond), NULL); //get the temp name now in use for moving the item #ifdef E2_VFS m_data.dlocal = &tempdata; #else m_data.dlocal = templocal; #endif m_data.done_size = 0; //rough approach to setting the reporting interval (usec) // m_data.refresh_interval = pdata.totalsize * 10; // if (m_data.refresh_interval > MAX_UPDATE_INTERVAL) if (pdata.totalsize < 10000000) m_data.refresh_interval = MIN_UPDATE_INTERVAL; else m_data.refresh_interval = MAX_UPDATE_INTERVAL; //with a cap pthread_t mon_thread_id; status = pthread_create (&mon_thread_id, NULL, (gpointer) _e2p_mvbar_progress, &m_data); if (status != 0) { printd (WARN,"monitor-thread-create error!"); g_free (templocal); return; } if (!GTK_WIDGET_VISIBLE (wdata->dialog)) { gdk_threads_enter (); gtk_widget_show (wdata->dialog); gdk_threads_leave (); } gchar *shortsrc = e2_utils_str_shorten (src, MAX_CHAR, E2_DOTS_START); gchar *shortdest = e2_utils_str_shorten (dest_dir, MAX_CHAR, E2_DOTS_START); gchar *num1 = g_strdup_printf ("%"PRIu64, bdata->count); //gettext workaround gchar *num2 = g_strdup_printf ("%"PRIu64, tdata->count); gchar *labeltext = g_strdup_printf ( _("moving %s\nto %s\nthis is item %s of %s"), shortsrc, shortdest, num1, num2); gdk_threads_enter (); gtk_label_set_text (GTK_LABEL (wdata->label), labeltext); gdk_threads_leave (); g_free (shortsrc); g_free (shortdest); g_free (num1); g_free (num2); g_free (labeltext); gchar *progress_format = _("%.2f MB of %.2f MB (%.0f\%%)"); gfloat fraction; //update progress when the monitor thread reports guint64 progress; while (!a_data.completed) //loop until the action thread is completed { if (pthread_mutex_lock (&(m_data.mutex))) { printd (WARN, "Lock error!"); } while (m_data.done_size == 0) { status = pthread_cond_wait (&(m_data.cond), &(m_data.mutex)); } if (status != 0) { printd (WARN,"Wait error!"); } progress = m_data.done_size + bdata->totalsize; m_data.done_size = 0; //reset if (pthread_mutex_unlock (&(m_data.mutex))) { printd (WARN,"Unlock error!"); } #ifdef IS_PAUSABLE if (wdata->flags & E2_BARTASK_STOPPED) //user wants to abort #else if (*breakflag) //user wants to abort #endif { if (pthread_cancel (mon_thread_id)) { printd (WARN,"Thread cancel error!\n"); } //cancel the task thread if (pthread_cancel (action_thread_id)) { printd (WARN,"Thread cancel error!\n"); } //cleanup anything part-moved #ifdef E2_VFS e2_task_backend_delete (&tempdata); #else e2_task_backend_delete (templocal); #endif g_free (templocal); return; } fraction = (gdouble) progress / tdata->totalsize; //deal with rounding errors if (fraction > 1.0) fraction = 1.0; g_snprintf (progresstext, sizeof (progresstext), progress_format, progress / 1048576.0, tdata->totalsize / 1048576.0, fraction * 100.0); gdk_threads_enter (); gtk_progress_bar_set_text (GTK_PROGRESS_BAR (wdata->progbar), progresstext); gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (wdata->progbar), fraction); //CHECKME some sort of wait here ?? gdk_threads_leave (); } //immediate signal to monitor thread to stop processing if (pthread_mutex_lock (&(m_data.mutex))) { printd (WARN, "Lock error!"); } m_data.dlocal = NULL; if (pthread_mutex_unlock (&(m_data.mutex))) { printd (WARN,"Unlock error!"); } //show the full-time score ASAP if (a_data.result) //move succeeded { progress = pdata.totalsize + bdata->totalsize; g_snprintf (progresstext, sizeof (progresstext), progress_format, progress / 1048576.0, tdata->totalsize / 1048576.0, 100.0); gdk_threads_enter (); gtk_progress_bar_set_text (GTK_PROGRESS_BAR (wdata->progbar), progresstext); gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (wdata->progbar), (gdouble) progress / tdata->totalsize); //CHECKME some sort of wait here ?? gdk_threads_leave (); //update progressive total bdata->totalsize = progress; } //cancel the monitor thread if (pthread_cancel (mon_thread_id)) { printd (WARN,"Thread cancel error!\n"); } //make sure things are finished before we move on pthread_join (action_thread_id, NULL); pthread_join (mon_thread_id, NULL); //CHECKME some sort of wait here ?? usleep (100000); //small delay to (sort of) show the completion status ? //need to slow down ... //this approach is empirical, needs to be validated for various systems // if (realmove && bdata->count > 300 && bdata->count%10 == 0) // g_usleep (50000); //small delay to allow filesystem to complete its business #ifdef IS_PAUSABLE // if (!(wdata->flags & E2_BARTASK_PAUSED)) #endif // gtk_widget_hide (wdata->dialog); } else bdata->totalsize += pdata.totalsize; if (a_data.result) //move succeeded { #ifdef E2_VFS e2_task_backend_rename (&tempdata, dlocal); #else e2_task_backend_rename (templocal, dlocal); #endif //update the progressive totals // bdata->count += pdata.count; /* final display now done earlier bdata->count++; bdata->totalsize += pdata.totalsize; fraction = (gdouble) bdata->totalsize / tdata->totalsize; //deal with rounding errors if (fraction > 1.0) fraction = 1.0; //and show them g_snprintf (progresstext, sizeof (progresstext), progress_format, bdata->totalsize / 1048576.0, tdata->totalsize / 1048576.0, fraction * 100.0); gdk_threads_enter (); gtk_label_set_text (GTK_LABEL (wdata->label2), progresstext); // gtk_progress_bar_set_text (GTK_PROGRESS_BAR (wdata->progbar), progresstext); gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (wdata->progbar), fraction); //CHECKME some sort of wait here ?? gdk_threads_leave (); */ } else #ifdef E2_VFS e2_task_backend_delete (&tempdata); //cleanup anything part-moved #else e2_task_backend_delete (templocal); //cleanup anything part-moved #endif g_free (templocal); #ifdef IS_PAUSABLE if (wdata->flags & E2_BARTASK_PAUSEREQ) { //if it was paused, the dialog must be still visible wdata->flags &= ~E2_BARTASK_PAUSEREQ; wdata->flags |= E2_BARTASK_PAUSED; e2_filelist_enable_refresh (); gdk_threads_enter (); gtk_main (); gdk_threads_leave (); } #endif return; } #ifdef IS_PAUSABLE /** @brief callback for stop-button press and window-close event @param widget the activated widget which triggered the callback @param flag pointer to store for flag which will abort the process when set to TRUE; @return TRUE always */ static gboolean _e2p_mvbar_break_cb (GtkWidget *widget, E2_BarWindowData *wdata) { wdata->flags |= E2_BARTASK_STOPPED; //handle paused moving if (wdata->flags & E2_BARTASK_PAUSED) { wdata->flags &= ~E2_BARTASK_PAUSED; e2_filelist_disable_refresh (); gtk_main_quit (); //FIXME = cleanups // pthread_cancel (); } return TRUE; } /** @brief callback for pause-button press @param widget the button widget which triggered the callback @param resbutton pointer to paired button widget @return TRUE always */ static gboolean _e2p_mvbar_pause_cb (GtkWidget *widget, E2_BarWindowData *wdata) { gtk_widget_set_sensitive (widget, FALSE); gtk_widget_set_sensitive (wdata->resume_btn, TRUE); gtk_widget_grab_focus (wdata->resume_btn); wdata->flags |= E2_BARTASK_PAUSEREQ; //actual pause is started in the monitor loop return TRUE; } /** @brief callback for resume-button press @param widget the button widget which triggered the callback @param resbutton pointer to paired button widget @return TRUE unless the process is not paused */ static gboolean _e2p_mvbar_resume_cb (GtkWidget *widget, E2_BarWindowData *wdata) { if (!(wdata->flags & E2_BARTASK_PAUSED)) return FALSE; gtk_widget_set_sensitive (widget, FALSE); gtk_widget_set_sensitive (wdata->pause_btn, TRUE); gtk_widget_grab_focus (wdata->pause_btn); wdata->flags &= ~E2_BARTASK_PAUSED; e2_filelist_disable_refresh (); gtk_main_quit (); return TRUE; } #else //ndef IS_PAUSABLE /** @brief callback for stop-button press and window-close event @param widget the activated widget which triggered the callback @param flag pointer to store for flag which will abort the process when set to TRUE; @return TRUE always */ static gboolean _e2p_mvbar_break_cb (GtkWidget *widget, #ifdef IS_PAUSABLE E2_BarFlags *flags #else gboolean *flag #endif ) { #ifdef IS_PAUSABLE *flags |= E2_BARTASK_STOPPED; #else *flag = TRUE; #endif return TRUE; } #endif /** @brief mvbar action This sets up progress display window, and processes each selected item @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if action completed successfully, else FALSE */ static gboolean _e2p_mvbar (gpointer from, E2_ActionRuntime *art) { return (e2_task_enqueue_task (E2_TASK_MOVE, art, from, _e2p_mvbarQ, e2_task_refresh_lists)); } static gboolean _e2p_mvbarQ (E2_ActionTaskData *qed) { //handle case of specific data instead of selection?? if (g_str_equal (qed->currdir, qed->othrdir)) { //display some message ?? return FALSE; } E2_ERR_DECLARE //FIXME change dir permission if possible ? #ifdef E2_VFS VPATH sdata; sdata.localpath = qed->currdir; VPATH ddata = { qed->othrdir, qed->othrspace }; if (e2_fs_access (&ddata, W_OK E2_ERR_PTR())) #else if (e2_fs_access (qed->othrdir, W_OK E2_ERR_PTR())) #endif { e2_fs_error_local (_("Cannot put anything in %s"), #ifdef E2_VFS &ddata E2_ERR_MSGL()); #else qed->othrdir E2_ERR_MSGL()); #endif E2_ERR_CLEAR return FALSE; } GPtrArray *names = qed->names; GtkWidget *vbox; //, *hbox; GString *src = g_string_sized_new (1024); GString *dest = g_string_sized_new (1024); //determine whether move will be physical or logical gboolean realmove; #ifdef E2_VFS if (qed->currspace != qed->othrspace) realmove = TRUE; else { #endif dev_t device; struct stat statbuf; #ifdef E2_VFS sdata.spacedata = qed->currspace; if (!e2_fs_stat (&sdata, &statbuf E2_ERR_NONE())) #else if (!e2_fs_stat (qed->currdir, &statbuf E2_ERR_NONE())) #endif device = statbuf.st_dev; else device = -1; #ifdef E2_VFS if (!e2_fs_stat (&ddata, &statbuf E2_ERR_NONE())) #else if (!e2_fs_stat (qed->othrdir, &statbuf E2_ERR_NONE())) #endif realmove = (statbuf.st_dev != device); else realmove = TRUE; #ifdef E2_VFS } #endif printd (DEBUG, (realmove) ? "physical move":"logical move"); //setup the information window E2_BarWindowData windowdata; #ifdef IS_PAUSABLE windowdata.flags = 0; #else gboolean breakflag = FALSE; //this is for aborting the process #endif windowdata.dialog = e2_dialog_create (NULL, NULL, _("moving"), NULL, NULL); e2_dialog_setup (windowdata.dialog, app.main_window); //CHECKME permit Esc-key aborting ? #ifdef IS_PAUSABLE g_signal_connect (G_OBJECT (windowdata.dialog), "delete-event", G_CALLBACK (_e2p_mvbar_break_cb), &windowdata); #else g_signal_connect (G_OBJECT (windowdata.dialog), "delete-event", G_CALLBACK (_e2p_mvbar_break_cb), &breakflag); #endif gtk_dialog_set_has_separator (GTK_DIALOG (windowdata.dialog), FALSE); vbox = GTK_DIALOG (windowdata.dialog)->vbox; windowdata.label = e2_widget_add_mid_label (vbox, "", 0.0, FALSE, 0); // hbox = e2_widget_add_box (vbox, FALSE, 0, TRUE, FALSE, 0); // windowdata.label2 = e2_widget_add_mid_label (hbox, "", 0.5, TRUE, 0); windowdata.progbar = gtk_progress_bar_new (); gtk_box_pack_start (GTK_BOX (vbox), windowdata.progbar, TRUE, TRUE, E2_PADDING_LARGE); gtk_widget_show (windowdata.progbar); #ifdef IS_PAUSABLE //buttons in reverse order windowdata.resume_btn = e2_dialog_add_undefined_button_custom (windowdata.dialog, FALSE, E2_RESPONSE_USER1,_("_Resume"), GTK_STOCK_MEDIA_PLAY, _("Resume moving after pause"), _e2p_mvbar_resume_cb, &windowdata); //this one is disabled for now gtk_widget_set_sensitive (windowdata.resume_btn, FALSE); windowdata.pause_btn = e2_dialog_add_undefined_button_custom (windowdata.dialog, FALSE, E2_RESPONSE_USER2,_("_Pause"), GTK_STOCK_MEDIA_PAUSE, _("Suspend moving, after the current item"), _e2p_mvbar_pause_cb, &windowdata); windowdata.stop_btn = e2_dialog_add_undefined_button_custom (windowdata.dialog, TRUE, E2_RESPONSE_NOTOALL,_("_Stop"), GTK_STOCK_STOP, _("Abort the moving"), _e2p_mvbar_break_cb, &windowdata); #else e2_dialog_add_undefined_button_custom (windowdata.dialog, TRUE, E2_RESPONSE_NOTOALL,_("_Stop"), GTK_STOCK_STOP, _("Abort the moving"), _e2p_mvbar_break_cb, &breakflag); #endif //accumulate non-recursed count and total size of src item(s) E2_BarData totaldata = { 0,0 }; guint count; E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata; for (count=0; count < names->len; count++, iterator++) { g_string_printf (src, "%s%s", qed->currdir, (*iterator)->filename); //separator comes with dir #ifdef E2_VFS sdata.localpath = src->str; #endif //if (! #ifdef E2_VFS e2_fs_tw (&sdata, _e2p_mvbar_twcb, &totaldata, -1, #else e2_fs_tw (src->str, _e2p_mvbar_twcb, &totaldata, -1, #endif E2TW_PHYS E2_ERR_NONE()); //) //{ //FIXME handle error //} } totaldata.count = names->len; //not interested in nested count gboolean check = e2_option_bool_get ("confirm-overwrite") #ifdef E2_VFS && qed->currspace == qed->othrspace #endif ; E2_BarData progressdata = { 1,0 }; OW_ButtonFlags extras = (totaldata.count > 1) ? BOTHALL : NONE; iterator = (E2_SelectedItemInfo **) names->pdata; e2_filelist_disable_refresh (); for (count=0; count < names->len; count++, iterator++) { #ifdef IS_PAUSABLE if (windowdata.flags & E2_BARTASK_STOPPED) #else if (breakflag) #endif break; //user pressed stop btn or closed info window //src_dir, dest_dir have trailing "/" g_string_printf (src, "%s%s", qed->currdir, (*iterator)->filename); //separator comes with dir g_string_printf (dest, "%s%s", qed->othrdir, (*iterator)->filename); #ifdef E2_VFS sdata.localpath = src->str; ddata.localpath = dest->str; if (check && e2_fs_access2 (&ddata E2_ERR_NONE()) == 0) #else if (check && e2_fs_access2 (dest->str E2_ERR_NONE()) == 0) #endif { e2_filelist_enable_refresh (); //allow updates while we wait gdk_threads_enter (); *qed->status = E2_TASK_PAUSED; DialogButtons result = e2_dialog_ow_check (src->str, dest->str, extras); *qed->status = E2_TASK_RUNNING; gdk_threads_leave (); e2_filelist_disable_refresh (); switch (result) { case YES_TO_ALL: check = FALSE; case OK: #ifdef E2_VFS _e2p_mvbar_exec (&sdata, &ddata, #else _e2p_mvbar_exec (src->str, dest->str, #endif realmove, #ifndef IS_PAUSABLE &breakflag, #endif GPOINTER_TO_INT (qed->action->data), &progressdata, &totaldata, &windowdata); break; case CANCEL: break; default: result = NO_TO_ALL; break; } if (result == NO_TO_ALL) { break; } } else //no overwrite, or don't care { #ifdef E2_VFS _e2p_mvbar_exec (&sdata, &ddata, #else _e2p_mvbar_exec (src->str, dest->str, #endif realmove, #ifndef IS_PAUSABLE &breakflag, #endif GPOINTER_TO_INT (qed->action->data), &progressdata, &totaldata, &windowdata); } progressdata.count++; } gdk_threads_enter (); gtk_widget_destroy (windowdata.dialog); gdk_threads_leave (); g_string_free (src, TRUE); g_string_free (dest, TRUE); #ifdef E2_FAM e2_filelist_request_refresh (other_view->dir, FALSE); e2_filelist_request_refresh (curr_view->dir, TRUE); #else e2_filelist_check_dirty (GINT_TO_POINTER (1)); #endif e2_filelist_enable_refresh (); return TRUE; } //aname must be confined to this module static gchar *aname; /** @brief initialize this plugin @param p pointer to plugin data struct to be populated @return TRUE if the intialization succeeded */ gboolean init_plugin (Plugin *p) { #define ANAME "mvbar" aname = _("mvbar"); p->signature = ANAME VERSION; p->menu_name = _("_Move"); p->description = _("Move selected item(s), with displayed progress details"); p->icon = "plugin_move_"E2IP".png"; //use icon file pathname if appropriate if (p->action == NULL) { //no need to free this gchar *action_name = g_strconcat (_A(5),".",aname,NULL); p->action = e2_plugins_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2p_mvbar, NULL, FALSE, 0, NULL); return TRUE; } return FALSE; } /** @brief cleanup transient things for this plugin @param p pointer to data struct for the plugin @return TRUE if all cleanups were completed */ gboolean clean_plugin (Plugin *p) { gchar *action_name = g_strconcat (_A(5),".",aname,NULL); gboolean ret = e2_plugins_action_unregister (action_name); g_free (action_name); return ret; } #undef IS_PAUSABLE emelfm2-0.4.1/plugins/e2p_crypt.c0000600000175000017500000031473610775531146015572 0ustar cairocairo/* $Id: e2p_crypt.c 848 2008-04-04 22:57:42Z tpgww $ Copyright (C) 2007-2008 tooar This file is part of emelFM2. emelFM2 is free software; for the most part 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 3, or (at your option) any later version. However this file, a plugin for emelFM2, may be configured to include mini-LZO code, about which there are mixed messages. It might be licensed only under version 2 of the General Public License, and if so and that code is included, then this plugin must entirely remain under that same version of the General Public Licence. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file plugins/optional/e2p_crypt.c @brief plugin for encrypting or decrypting selected items The encryption code is derived from tinycrypt v.0.4. It's based on ARC4 (see http://en.wikipedia.org/wiki/RC4) with enchancements by the tinycrypt author: - reduced nonce value to about 20 bytes - 160 bits should be enough - reduced discarded bytes to 512 - there's no evidence on the net that more are needed - simplified mixing of the password and nonce values with the key, since throwing away the first 512 bytes mixes them up anyway As for tinycrypt, any [de]compression is peformed using minilzo, the mini subset of the fast LZO real-time data compression library */ /*TODO en/de-compression single handle counter in new custom name ?m[un]map (void *address, size_t length, int protect, int flags, int filedes, off_t offset) buffered I/O if not processing whole file at once, can't be done with compression ? pthread_testcancel (); //swap threads, cancel if instructed WHERE RELEVANT library access for [de]compression links - ok ? LZO supports in-place decompression, so sniff or reallocate loaded buffer NO - fails in-place decompressing instead of duplicate buffers seems to NOT work max size of lzo buffer lzo_unt = size_t? = ? LZO streaming? UI error handling - warnings entry latency sometimes crashes sometimes during dialog setup (maybe more to do with de-cryption?) entry text scrolled out of entry window sometimes "active" entry text selected but not focused @ startup some dialog-button choices have a BGL problem, if hide dialog after user-choice ? */ #include "emelfm2.h" #include #include #include #include #include //#include "e2p_crypt.h" #include "e2_plugins.h" #include "e2_password_dialog.h" #include "e2_task.h" #include "e2_filelist.h" //enable internal mini-LZO for en/de-cryption //#define E2_MINICRYPT #ifdef E2_MINICRYPT //get mini-lzo headers and code # include "e2p_crypt_lzo.source" #endif #ifdef TC_COMPATIBLE //if we want to be compatible with tinycrypt # ifndef E2_MINICRYPT # define E2_MINICRYPT # endif # define csize_t gulong //32 bits, probably? # define SIZESHIFT 24 //(sizeof (csize_t) - 1) * 8, for handling endian-independent stored csize_t # define NONCE_LENGTH 20 # define HEADER_LENGTH1 20 //no flags in TC files #elif defined(_LARGEFILE_SOURCE)//if we cater for "large" files # define csize_t guint64 # define SIZESHIFT 56 # define NONCE_LENGTH 24 //HEADER_LENGTH1 - csize_t (>=20) # define HEADER_LENGTH1 32 //multiple of 8 >= (csize_t + 20) #else //if we cater for files up to 2 GB # define csize_t guint32 # define SIZESHIFT 24 # define NONCE_LENGTH 20 # define HEADER_LENGTH1 24 //NONCE_LENGTH + csize_t for flags #endif #define KEY_LENGTH 256 //hash table size KEY_LENGTH MUST be 256 //#define DISCARD_BYTES 768 // KEY_LENGTH * 3 #define DISCARD_BYTES 512 // KEY_LENGTH * 2 #define SWAP(a,b) temp=a;a=b;b=temp; //bitflags in csize_t stored in after nonce in encrypted file //(don't change or remove any existing flag!) enum { E2_CFLAGNONE = 0, E2_CFLAGCOMPRESS = 1, //file is compressed (and one of the type-flags is set) E2_CFLAGSIZE = 1 << 4, //original file size stored as csize_t E2_CFLAGNAME = 1 << 5, //original file name stored as text E2_CFLAGINFO = 1 << 6, //original file data stored in FileInfo E2_CFLAGINTLZ = 1 << 16, //file compressed with internal mini lzo //these need to be in ascending usage-preference-order E2_CFLAGLZO = 1 << 17, //file compressed with external liblzo E2_CFLAGZ = 1 << 18, //file compressed with external libz E2_CFLAGBZ2 = 1 << 19 //file compressed with external libbz2 }; //for library checking #define E2_CFLAGLIBMASK 0xf0000 //FOR LZO [DE]COMPRESSION #define LZO1X_1_MEM_COMPRESS 16384*sizeof(guchar *) //FOR LIBZ [DE]COMPRESSION //compression levels enum { Z_DEFAULT_COMPRESSION = -1, Z_NO_COMPRESSION, Z_BEST_SPEED, Z_BEST_COMPRESSION = 9 }; //FOR LIBBZIP2 [DE]COMPRESSION typedef struct _E2P_CryptOpts { gboolean en_name_same; //encrypted file name = same as onencrypted name gboolean en_name_suffix;//encrypted file name has user-specified suffix (if any) gboolean en_name_custom;//encrypted file name = user-specified // gboolean en_name_embed; //store filenama in encrypted file gboolean en_properties_embed; //store filenama and statbuf in encrypted file gboolean de_name_same; //decrypted file name = same as encrypted name gboolean de_name_stored; //decrypted file name = embedded original name gboolean de_name_suffix; //decrypted file name omits user-specified suffix (if any) gboolean de_name_custom; //decrypted file name = user-specified gboolean de_props_stored; //decrypted file other properties = original file gboolean compress; //compress file before encryption gboolean backup; //preserve any file with same name as specified for the [de]crypted file gboolean preserve; //preserve the file to be [de]crypted, with alternate name if appropriate gboolean recurse; //recursively process all files in any selected dir gboolean walklinks; //process link targets gboolean decryptmode;//TRUE = decrypt, FALSE = encrypt gboolean permission;//for transferring whether it's ok to make the change gboolean multisrc; //TRUE when processing > 1 item in loop gboolean ignore_suffix; //don't worry about incorrect suffix when decompressing gboolean owrite; //don't ask to confirm overwrites gchar *en_suffix; //user-specified suffix, freeable utf-8 gchar *en_name; //user-specified name, freeable utf-8 gchar *de_suffix; //user-specified suffix, freeable utf-8 gchar *de_name; //user-specified name, freeable utf-8 gchar *plain_pw; //store for plaintext password ptr const gchar *localpath; //copy of dialog-function arg, or substitute during treewalk struct stat *statptr; #ifdef E2_VFS PlaceInfo *spacedata; #endif GList *dirdata; //list of E2_Dirent's for dirs yet to be processed } E2P_CryptOpts; typedef struct _E2P_CryptDlgRuntime { GtkWidget *dialog; //main dialog widget E2P_CryptOpts *opts; E2_PWDataRuntime *pwrt; //data struct for password-related widgets gboolean dlgopen; //the following widgets exist GtkWidget *mode_btn; //radio button for en/de-crypt mode GtkWidget *encryptbox; //vbox with encryption-specific widgets GtkWidget *en_name_btn_same; GtkWidget *en_name_btn_suffix; GtkWidget *en_name_btn_custom; GtkWidget *en_name_suffix_entry; GtkWidget *en_name_custom_entry; // GtkWidget *en_name_embed_btn; GtkWidget *confirmbox; GtkWidget *en_properties_embed_btn; GtkWidget *compress_btn; GtkWidget *decryptbox; //vbox with decryption-specific widgets GtkWidget *de_name_btn_same; GtkWidget *de_name_btn_stored; GtkWidget *de_name_btn_suffix; GtkWidget *de_name_btn_custom; GtkWidget *de_name_suffix_entry; GtkWidget *de_name_custom_entry; GtkWidget *recurse_btn; GtkWidget *backup_btn; GtkWidget *preserve_btn; GtkWidget *linktarget_btn; GtkWidget *properties_btn; // GtkWidget *all_btn; //for [de]sensitisizing apply-to-all choice DialogButtons result; } E2P_CryptDlgRuntime; static gboolean _e2p_task_docryptQ (E2_ActionTaskData *qed); static csize_t _e2pcr_compress_buffer (gpointer filebuffer, /*ssize_t*/ gulong filebuffersize, gpointer *compressedbuffer); //CHECKME gulong size ? static gboolean _e2pcr_write_buffer (VPATH *localpath, gint descriptor, gpointer filebuffer, /*ssize_t*/ gulong filebuffersize); //CHECKME gulong size ? static gboolean _e2pcr_read_file (VPATH *localpath, gpointer *filebuffer, /*ssize_t*/ gulong filebuffersize); static csize_t _e2pcr_decompress_buffer (gpointer filebuffer, /*ssize_t*/ gulong filebuffersize, csize_t originalfilesize, #ifndef E2_MINICRYPT //FIXME make altdecompress_buf a function arg, non-static csize_t libflags, //gint (*altdecompress_buf) (), #endif gpointer *decompressedbuffer); static gboolean _e2pcr_flush_file (VPATH *localpath, guint8 hashes[KEY_LENGTH]); //session-static parameters static E2P_CryptOpts session_opts = { FALSE, //encrypted file name = same as onencrypted name TRUE, //encrypted file name has user-specified suffix (if any) FALSE, //encrypted file name = user-specified // FALSE, //store filenama in encrypted file FALSE, //store filename and statbuf in encrypted file FALSE, //decrypted file name = same as encrypted name FALSE, //decrypted file name = embedded original name TRUE, //decrypted file name omits user-specified suffix (if any) FALSE, //decrypted file name = user-specified FALSE, //reinstate properties of original file other than its name TRUE, //compress file before encryption TRUE, //preserve any file with same name as specified for the [de]crypted file TRUE, //preserve the file to be [de]crypted, with alternate name if appropriate FALSE, //recursively process all files in any selected dir TRUE, //process link targets /*rest are all default values, FALSE or NULL FALSE, //decrypt mode FALSE, //permission FALSE, //>1 item FALSE, //ignore decomp suffix FALSE, //overwrite NULL, //encryption suffix, initialised to ".enc" NULL, //custom name for encryption NULL, //decryption suffix, initialised to ".enc" NULL, //custom name for decryption NULL, //password NULL, //item path NULL //stat buffer ptr */ }; //these are ok to be static for all usage in session ?? csize_t compresslib = E2_CFLAGNONE; csize_t altdecompresslib = E2_CFLAGNONE; //pointers to library functions for compressing and decompressing a buffer //these will be set to relevant functions from LZO lib, libz or libbz2, //according to which lib is detected (scanniing in that order). Or NULL if no //compression lib is available at runtime gint (*init_compress) (); gint (*compress_buf) (); gint (*decompress_buf) (); //for decompressing using a lib other than the default, in accord with in-file flag gint (*altdecompress_buf) (); /***************/ /** utilities **/ /***************/ /** @brief get a random value in range 0 .. 255 @param retval pointer to store for value @return TRUE if the value was filled */ static gboolean _e2pcr_getrandom (guint8 *retval) { E2_FILE *randFile = fopen //no vfs needed here #if defined(__linux__) || defined(__solaris__) || defined(darwin) || defined(__OpenBSD__) || defined(AIX) //CHECKME which other OS's ? ("/dev/urandom", "r"); #else ("/dev/random", "r"); #endif if (randFile == NULL) { printd (DEBUG, "cannot open random number source"); //FIXME handle error *retval = 0; return FALSE; } *retval = getc (randFile); fclose (randFile); return TRUE; } /** @brief construct a temporary itemname by adding a suffix to @a localpath @param localpath absolute path of item to be processed, localised string @param custom string to append to @a localpath, localised string @return newly-allocated, localised, path string comprising the temp name */ static gchar *_e2pcr_get_tempname (VPATH *localpath, gchar *custom) { gchar *temppath, *s; guint i = 0; E2_ERR_DECLARE #ifdef E2_VFS VPATH tdata; tdata.spacedata = localpath->spacedata; #endif while (TRUE) { temppath = g_strdup_printf ("%s%s~%d", VPSTR(localpath), custom, i); if (i == 0) { //first try without any "~N" suffix s = strrchr (temppath, '~'); *s = '\0'; } #ifdef E2_VFS tdata.localpath = temppath; if (e2_fs_access2 (&tdata E2_ERR_PTR()) && E2_ERR_IS (ENOENT)) #else if (e2_fs_access2 (temppath E2_ERR_PTR()) && E2_ERR_IS (ENOENT)) #endif { E2_ERR_CLEAR break; } E2_ERR_CLEAR g_free (temppath); i++; } return temppath; } /** @brief in an endian-independant fashion, store unencrypted data corresponding to @a value starting from @a datastart @param datastart pointer to first of a series of unencrypted bytes to store @return */ static void _e2pcr_store (csize_t value, guchar *datastart) { csize_t s = value; guint i; //stored byte-order is low ... high for (i = 0; i < sizeof (csize_t); i++) { *datastart++ = (guint8) s; s >>= 8; } } /** @brief in an endian-independant fashion, construct a csize_t from data stored starting from @a datastart @param datastart pointer to first of a series of decrypted stored bytes to process @return the stored value */ static csize_t _e2pcr_read_store (guchar *datastart) { csize_t grabber, value = 0; guint i; for (i = 0; i < sizeof (csize_t); i++) { grabber = *datastart++; value = (value >> 8) | (grabber << SIZESHIFT); } return value; } /** @brief check whether the user wlll allow an existing item to be over-written This is not done in main task loop, as we need to handle custom names set in the dialog or stored in a file being decompressed This expects BGL to be open/off @param localpath absolute path of item to check, localised string @param multisrc TRUE if the current item is part of a multi-item selection @return OK if there is no conflict, or code corresponding to the user's choice in the o/w dialog */ static DialogButtons _e2pcr_ow_check (VPATH *localpath, gboolean multisrc) { DialogButtons result; if (e2_fs_access2 (localpath E2_ERR_NONE()) == 0) { #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, copy as dialog"); #endif e2_filelist_enable_refresh (); //allow updates while we wait gdk_threads_enter (); result = e2_dialog_ow_check (NULL, VPSTR (localpath), (multisrc) ? BOTHALL : NONE); gdk_threads_leave (); e2_filelist_disable_refresh (); } else result = OK; return result; } /** @brief run warning dialog for confirmation @param prompt prompt string @param multi this is part of a multi-file task @return button code returned by the dialog */ static DialogButtons _e2pcr_dialog_warning (gchar *prompt, gboolean multi) { DialogButtons retval; GtkWidget *dialog = e2_dialog_create (GTK_STOCK_DIALOG_WARNING, prompt, _("confirm"), e2_dialog_response_decode_cb, &retval); //set default button to 'no' E2_BUTTON_NO.showflags |= E2_BTN_DEFAULT; gdk_threads_enter (); if (multi) e2_dialog_show (dialog, NULL, //parent stays sensitive 0, &E2_BUTTON_NOTOALL, &E2_BUTTON_YESTOALL, &E2_BUTTON_YES, &E2_BUTTON_NO, NULL); else e2_dialog_show (dialog, NULL, //parent stays sensitive 0, &E2_BUTTON_YES, &E2_BUTTON_NO, NULL); gtk_main (); //because the decoder has gtk_main_quit(); gtk_widget_destroy (dialog); gdk_threads_leave (); return retval; } /****************************/ /* en/de-cryption functions */ /****************************/ #ifndef E2_MINICRYPT /** @brief setup things for decompression, according to @a flags @param localpath localised path string, for use in error messages @param flags the flags recorded in a file being decompressed @return TRUE if no error happens */ static gboolean _e2pcr_check_lib (VPATH *localpath, csize_t flags) { printd (DEBUG, "masked file lib-flags %0x", flags & E2_CFLAGLIBMASK); gpointer libhandle; //FIXME use non-static func pointer, and gchar *message = NULL; //no error yet if ((flags & E2_CFLAGLIBMASK) == (compresslib & E2_CFLAGLIBMASK)) { altdecompress_buf = decompress_buf; printd (DEBUG, "using default lib"); } else { if (flags & E2_CFLAGLZO) //(E2_CFLAGLZO | E2_CFLAGINTLZ)); { if (!(altdecompresslib & E2_CFLAGLZO) //need to get this lib && (compresslib & E2_CFLAGLIBMASK) > E2_CFLAGLZO) //couldn't find it at start of session { printd (DEBUG, "LZO decompression library missing"); message = GINT_TO_POINTER (1); } // else if (altdecompresslib & E2_CFLAGLZO) //got it before // { //FIXME // } else { libhandle = dlopen ("liblzo2.so.2", RTLD_LAZY); if (libhandle == NULL) { printd (DEBUG, "LZO decompression library missing"); message = GINT_TO_POINTER (1); } init_compress = dlsym (libhandle, "__lzo_init_v2"); //a #define in lzoconf.h if (init_compress != NULL) { altdecompress_buf = dlsym (libhandle, "lzo1x_decompress_safe"); if (altdecompress_buf != NULL) { altdecompresslib |= E2_CFLAGLZO; printd (DEBUG, "using LZO lib"); } else { dlclose (libhandle); message = GINT_TO_POINTER (1); } } else { dlclose (libhandle); message = GINT_TO_POINTER (1); } } if (message != NULL) message = _("No LZO compression-library for file %s"); } else if (flags & E2_CFLAGZ) { if (!(altdecompresslib & E2_CFLAGZ) //need to change && (compresslib & E2_CFLAGLIBMASK) > E2_CFLAGZ) //couldn't fine the lib at start of session { printd (DEBUG, "ZLIB decompression library missing"); message = GINT_TO_POINTER (1); } // else if (altdecompresslib & E2_CFLAGZ) //got it before // { //FIXME // } else { libhandle = dlopen ("libz.so.1", RTLD_LAZY); if (libhandle == NULL) { printd (DEBUG, "ZLIB decompression library missing"); message = GINT_TO_POINTER (1); } altdecompress_buf = dlsym (libhandle, "uncompress"); if (altdecompress_buf != NULL) { altdecompresslib |= E2_CFLAGZ; printd (DEBUG, "using ZLIB lib"); } else { dlclose (libhandle); message = GINT_TO_POINTER (1); } } if (message != NULL) message = _("No ZLIB compression-library for file %s"); } else if (flags & E2_CFLAGBZ2) { if (!(altdecompresslib & E2_CFLAGBZ2) //need to change && (compresslib & E2_CFLAGLIBMASK) > E2_CFLAGBZ2) //couldn't find the lib at start of session { printd (DEBUG, "BZ2LIB decompression library missing"); message = GINT_TO_POINTER (1); } // else if (altdecompresslib & E2_CFLAGBZ2) //got it before // { //FIXME // } else { libhandle = dlopen ("libbz2.so.1", RTLD_LAZY); if (libhandle == NULL) { printd (DEBUG, "BZ2LIB decompression library missing"); message = GINT_TO_POINTER (1); } altdecompress_buf = dlsym (libhandle, "BZ2_bzBuffToBuffCompress"); if (altdecompress_buf != NULL) { altdecompresslib |= E2_CFLAGBZ2; printd (DEBUG, "using BZ2 lib"); } else { dlclose (libhandle); message = GINT_TO_POINTER (1); } } if (message != NULL) message = _("No BZIP compression-library for file %s"); } else { printd (DEBUG, "unknown decompression library"); message = _("Unknown compression-library for file %s"); } if (message != NULL) { e2_fs_error_simple (message, localpath); return FALSE; } } return TRUE; } #endif /** @brief initialize hash key Unlike with tinycrypt, the "discard bytes" process starts at hashes[0], not hashes[1], and at the end of that process tha i-index is 255, not 0 @param hashes array of hash-bytes @param password the en/de-cryption password string @param nonce pointer to start of "nonce" string @param noncelength length of @a nonce @return the "seed" (j-index) of the hashkey to use for the next operation */ static guint8 _e2pcr_init_key (guint8 hashes[KEY_LENGTH], gchar *password, guchar *nonce, guint noncelength) { guint indx; guint8 i, j, temp; gchar *p; //initialize the key; for (indx = 0; indx < KEY_LENGTH; indx++) hashes[indx] = (guint8)indx; //mangle the key from the password, as done in ARC4 i = j = 0; p = password; for (indx = 0; indx < KEY_LENGTH; indx++) { if (*p == '\0') p = password; j += hashes[(guint8)indx] + *p++; SWAP(hashes[(guint8)indx], hashes[j]); } //mangle the key from the random bytes in the nonce for (indx = 0; indx < noncelength; indx++) { j += hashes[i] + *nonce++; SWAP(hashes[i], hashes[j]); i++; } //"discard bytes" = mangle some more i = 255; //don't skip the first value j = 0; for (indx = 0; indx < DISCARD_BYTES; indx++) { i++; j += hashes[i]; SWAP(hashes[i], hashes[j]); } //DISCARD_BYTES is a multiple of KEY_LENGTH, so now i = 255, j = whatever return j; } /** @brief setup nonce and hash table to use for encryption This uses with a random key XOR-ed with the user's munged key. Prepend the key, and the uncompressed file length, then encrypt it. @param hashes array of hash values to en/decrypt each byte of file @param password plaintext password to use @param noncebuffer array of chars in which to store the nonce, must be sized >= NONCE_LENGTH @return 0 on error, or the "j-index" resulting from the key mangling (which may be 0) */ static guint8 _e2pcr_encrypt_setup (guint8 hashes[KEY_LENGTH], gchar *password, guchar noncebuffer[NONCE_LENGTH]) { //fill nonce with random bytes E2_FILE *randFile = fopen #if defined(__linux__) || defined(__solaris__) || defined(darwin) || defined(__OpenBSD__) || defined(AIX) //CHECKME which other OS's ? ("/dev/urandom", "r"); #else ("/dev/random", "r"); #endif if (randFile == NULL) { printd (DEBUG, "cannot open random number source"); //FIXME handle error return 0; } guchar *bufferp = noncebuffer; guint8 i = 0; for (i = 0; i < NONCE_LENGTH; i++) *bufferp++ = getc (randFile); fclose (randFile); return (_e2pcr_init_key (hashes, password, noncebuffer, NONCE_LENGTH)); } /** @brief encrypt or decrypt @a filebuffer Buffer contents are XOR-ed with the user's munged key @param hashes array of key bytes @param iseed pointer to store for i-index for manipulating @a hashes @param jseed pointer to store for j-index for manipulating @a hashes @param filebuffer the buffer to be processed @param filebuffersize size of @a filebuffer @return */ static void _e2pcr_crypt_buffer (guint8 hashes[KEY_LENGTH], guint8 *iseed, guint8 *jseed, gpointer filebuffer, csize_t filebuffersize) { guchar *encryptedbufferp, *filebufferp; csize_t indx; guint8 i, j, temp; encryptedbufferp = filebufferp = filebuffer; i = *iseed; j = *jseed; for (indx = 0; indx < filebuffersize; indx++) { j += hashes[i]; *encryptedbufferp++ = *filebufferp ^ hashes[(guint8)(hashes[i] + hashes[j])]; SWAP(hashes[i], hashes[j]); i++; filebufferp++; } *iseed = i; *jseed = j; } /** @brief finalise naming of en/de-crypted file This is same for en- and de-cryption, except that in former case, redundant file is wiped, not just deleted @param localpath localised path of the item that's been processed @param temppath localised path of interim temp file holding the processed results @param newpath localised path of final name that's different from @a localpath @param same_name TRUE when the processed file ends up as @a localpath @param preserve TRUE to backup anything that would be overwritten, including @a localpath (options->preserve or options->backup) @param wipe TRUE to wipe, FALSE to delete, the original @a localpath @return TRUE if all was done as required */ static gboolean _e2pcr_finalise_item (VPATH *localpath, VPATH *temppath, VPATH *newpath, gboolean same_name, gboolean preserve, gboolean wipe, guint8 hashes[KEY_LENGTH]) { #ifdef E2_VFS VPATH otherpath; otherpath.spacedata = localpath->spacedata; #endif gboolean success; gchar *tmp; if (same_name) { if (preserve) { //no need to check for permission - that's done in the dialog tmp = _e2pcr_get_tempname (localpath, "-original"); //ascii, & don't bother with translation #ifdef E2_VFS otherpath.localpath = tmp; success = e2_task_backend_rename (localpath, &otherpath); #else success = e2_task_backend_rename (localpath, tmp); #endif g_free (tmp); if (!success) return FALSE; } else { success = (wipe) ? //wipe & delete original file (this fluffs up the hashes array) _e2pcr_flush_file (localpath, hashes) : //delete original file e2_task_backend_delete (localpath); if (!success) return FALSE; } if (!e2_task_backend_rename (temppath, localpath)) return FALSE; } else //final name != original name { if (!e2_fs_access (newpath, F_OK E2_ERR_NONE())) { if (preserve) { if (!e2_fs_access (newpath, W_OK E2_ERR_NONE())) { tmp = _e2pcr_get_tempname (newpath, "-original"); //ascii, & don't bother to translate #ifdef E2_VFS otherpath.localpath = tmp; success = e2_task_backend_rename (newpath, &otherpath); #else success = e2_task_backend_rename (newpath, tmp); #endif g_free (tmp); if (!success) { //can't change original file //CHECKME get & use a tempname for localpath? return FALSE; } } } else { if (e2_option_bool_get ("confirm-overwrite")) { DialogButtons choice = _e2pcr_ow_check (newpath, FALSE); if (choice != OK) return FALSE; } e2_task_backend_delete (newpath); } if (!e2_task_backend_rename (temppath, newpath)) return FALSE; } else //newpath doesn't exist if (!preserve) { if (!e2_task_backend_rename (temppath, newpath)) return FALSE; success = (wipe) ? //wipe & delete original file (this fluffs up the hashes array) _e2pcr_flush_file (localpath, hashes) : //delete original file e2_task_backend_delete (localpath); if (!success) return FALSE; } else //leave original as is if (!e2_task_backend_rename (temppath, newpath)) return FALSE; } return TRUE; } /** @brief encrypt file @a localpath Any error message expects BGL open @a newname is freed, if non-NULL @param localpath localised path of item being processed, also reflected in @a dir and @a oldname @param dir directory in which the item is located, absolute localised path @param oldname name of item being processed, localised string @param newname new name of item after processing, NULL if @a use_same_name is TRUE @param use_same_name TRUE to give encrypted file same name as original @param check whether to confirm overwrites when renaming @param options pointer to crypt options data @return code indicating choice or success */ static DialogButtons _e2pcr_encrypt1 (VPATH *localpath, const gchar *dir, const gchar *oldname, gchar *newname, gboolean use_same_name, gboolean check, E2P_CryptOpts *options) { guint8 iseed; guint8 jseed; guint8 hashes[KEY_LENGTH]; gint fdesc; gulong filebuffersize, compressedbuffersize; gboolean onwards; gpointer filebuffer, compressedbuffer; gchar *temppath; E2_ERR_DECLARE struct stat sb; if (e2_fs_stat (localpath, &sb E2_ERR_NONE())) { printd (DEBUG, "cannpt stat the file"); #ifdef E2_VFS e2_fs_set_error_from_errno (&E2_ERR_NAME); #endif e2_fs_error_local (_("Cannot open '%s' for reading"), localpath E2_ERR_MSGL()); E2_ERR_CLEAR return NO; } //file reading is size-limited to gulong (was ssize_t) filebuffersize = (gulong) sb.st_size; if (filebuffersize == 0) { //error or empty file, probably not error as file was opened ok return CANCEL; } if (!_e2pcr_read_file (localpath, &filebuffer, filebuffersize)) { return NO; } if (options->compress) { printd (DEBUG, "go to compress file buffer"); compressedbuffersize = _e2pcr_compress_buffer (filebuffer, filebuffersize, &compressedbuffer); if (compressedbuffersize == 0) { g_free (filebuffer); return NO; //or CANCEL ? } } else //warnings prevention only { compressedbuffersize = 0; compressedbuffer = NULL; } temppath = e2_utils_get_tempname (VPSTR (localpath)); #ifdef E2_VFS VPATH tdata = { temppath, localpath->spacedata }; #endif //descriptor for blockwize writing encryped file fdesc = e2_fs_safeopen (temppath, O_CREAT | O_WRONLY, S_IWUSR | S_IRUSR); if (fdesc < 0) { #ifdef E2_VFS e2_fs_set_error_from_errno (&E2_ERR_NAME); #endif e2_fs_error_local (_("Cannot open '%s' for writing"), #ifdef E2_VFS &tdata E2_ERR_MSGL()); #else temppath E2_ERR_MSGL()); #endif E2_ERR_CLEAR g_free (filebuffer); g_free (temppath); return NO; } csize_t flags = (options->compress) ? #ifdef E2_MINICRYPT E2_CFLAGCOMPRESS : E2_CFLAGNONE; #else E2_CFLAGCOMPRESS | (compresslib & E2_CFLAGLIBMASK) : E2_CFLAGNONE; #endif printd (DEBUG, "masked file lib-flags %0x", flags & E2_CFLAGLIBMASK); guint len = HEADER_LENGTH1; //NONCE_LENGTH + bitflags /* if (options->en_name_embed) { flags |= E2_CFLAGNAME; len += (NAME_MAX+1); } else */ if (options->en_properties_embed) { flags |= E2_CFLAGINFO; len += sizeof (FileInfo); } else { flags |= E2_CFLAGSIZE; len += sizeof (csize_t); } guchar headerbuffer[len]; memset (headerbuffer, 0, len); iseed = 0; jseed = _e2pcr_encrypt_setup (hashes, options->plain_pw, headerbuffer); //nonce at start of header // *((csize_t *) (headerbuffer + NONCE_LENGTH)) = flags; _e2pcr_store (flags, headerbuffer + NONCE_LENGTH); /* if (options->en_name_embed) { g_strlcpy (headerbuffer + HEADER_LENGTH1, oldname, NAME_MAX+1); FIXME need sixe too } else */ if (options->en_properties_embed) { FileInfo *info = (FileInfo *) (headerbuffer + HEADER_LENGTH1); g_strlcpy (info->filename, oldname, sizeof (info->filename)); //freshen the times to approx. this access if (e2_fs_lstat (localpath, &info->statbuf E2_ERR_NONE())) //or else just set old times memcpy (&info->statbuf, options->statptr, sizeof (struct stat)); } else { // sptr = (csize_t *) (headerBuffer + HEADER_LENGTH1); // *sptr = filebuffersize; _e2pcr_store (filebuffersize, headerbuffer + HEADER_LENGTH1); } //encrypt header after the nonce _e2pcr_crypt_buffer (hashes, &iseed, &jseed, headerbuffer + NONCE_LENGTH, len - NONCE_LENGTH); //write header #ifdef E2_VFS onwards = _e2pcr_write_buffer (&tdata, fdesc, headerbuffer, len); #else onwards = _e2pcr_write_buffer (temppath, fdesc, headerbuffer, len); #endif if (onwards) { //encrypt and write the main data if (flags & E2_CFLAGCOMPRESS) { _e2pcr_crypt_buffer (hashes, &iseed, &jseed, compressedbuffer, compressedbuffersize); #ifdef E2_VFS onwards = _e2pcr_write_buffer (&tdata, #else onwards = _e2pcr_write_buffer (temppath, #endif fdesc, compressedbuffer, compressedbuffersize); } else { _e2pcr_crypt_buffer (hashes, &iseed, &jseed, filebuffer, filebuffersize); #ifdef E2_VFS onwards = _e2pcr_write_buffer (&tdata, #else onwards = _e2pcr_write_buffer (temppath, #endif fdesc, filebuffer, filebuffersize); } } if (onwards) { //create trailer for signature-check when de-compressing //because it's 0, endian-ness doesn't matter csize_t *sptr = (csize_t *) headerbuffer; *sptr = 0; _e2pcr_crypt_buffer (hashes, &iseed, &jseed, headerbuffer, sizeof (csize_t)); #ifdef E2_VFS onwards = _e2pcr_write_buffer (&tdata, #else onwards = _e2pcr_write_buffer (temppath, #endif fdesc, headerbuffer, sizeof (csize_t)); } if (onwards) { gchar *newpath = (newname == NULL) ? NULL : g_build_filename (dir, newname, NULL); #ifdef E2_VFS VPATH ddata = { newpath, localpath->spacedata }; #endif onwards = _e2pcr_finalise_item (localpath, #ifdef E2_VFS &tdata, &ddata, #else temppath, newpath, #endif use_same_name, options->preserve || options->backup, TRUE, hashes); if (newpath != NULL) g_free (newpath); } g_free (temppath); if (newname != NULL) g_free (newname); return ((onwards) ? OK : NO); } /** @brief decrypt file @a localpath Any error message expects BGL open @param localpath localised path of item being processed, also reflected in @a dir and @a oldname @param dir directory in which the item is located, absolute localised path @param oldname name of item being processed, localised string @param newname new name of item after processing, NULL if @a use_same_name is TRUE @param use_same_name TRUE to give decrypted file same name as original @param check whether to confirm overwrites when renaming @param options pointer to crypt options data @return code indicating choice or NO for error */ static DialogButtons _e2pcr_decrypt1 (VPATH *localpath, const gchar *dir, const gchar *oldname, gchar *newname, gboolean use_same_name, gboolean check, E2P_CryptOpts *options) { guint8 iseed; guint8 jseed; guint8 hashes[KEY_LENGTH]; gboolean onwards; FileInfo *info; // FileInfo stored_info; //for remembering a streamed info E2_ERR_DECLARE struct stat sb; if (e2_fs_stat (localpath, &sb E2_ERR_NONE())) { printd (DEBUG, "cannpt stat the file"); #ifdef E2_VFS e2_fs_set_error_from_errno (&E2_ERR_NAME); #endif e2_fs_error_local (_("Cannot open '%s' for reading"), localpath E2_ERR_MSGL()); E2_ERR_CLEAR return NO; } //file reading is size-limited to gulong (was ssize_t) gulong filebuffersize = (gulong) sb.st_size; if (filebuffersize == 0) { //error or empty file, probably not error as file was opened ok return CANCEL; } //* for debugging ... /* NO MERIT IN 2-STAGE PROCESS, UNLESS SOME HEADER-TESTING IS DONE //sniff the file //sniff size must be >= NONCE_LENGTH + sizeof(c_size_t)(for flags) + sizeof(FileInfo) guchar headerbuffer[512]; memset (headerbuffer, 0, ); ssize_t headersize = MIN (sizeof (headerbuffer), filebuffersize); gpointer headerbuffer; gulong headersize = filebuffersize; if (!_e2pcr_read_file (localpath, &headerbuffer, headersize)) */ gpointer filebuffer; if (!_e2pcr_read_file (localpath, &filebuffer, filebuffersize)) { return NO; } //setup hashes using nonce from header iseed = 0; jseed = _e2pcr_init_key (hashes, options->plain_pw, filebuffer, NONCE_LENGTH); //nonce is at start of file buffer //decrypt rest of header _e2pcr_crypt_buffer (hashes, &iseed, &jseed, filebuffer + NONCE_LENGTH, filebuffersize - NONCE_LENGTH); csize_t flags; //if reading the whole file, check the trailing signature //since it's supposed to be 0, endian-ness doesn't matter flags = *((csize_t *) (filebuffer + filebuffersize - sizeof (csize_t))); if (flags != 0) { printd (DEBUG, "bad password"); e2_fs_error_simple (_("Wrong password for %s"), localpath); return NO; } #ifdef E2_VFS VPATH ddata; ddata.spacedata = localpath->spacedata; #endif guint skiplen; csize_t originalFileLength; // flags = *((csize_t *) (filebuffer + NONCE_LENGTH)); flags = _e2pcr_read_store (filebuffer + NONCE_LENGTH); gboolean compressed = flags & E2_CFLAGCOMPRESS; // gboolean name = ; gboolean properties = flags & E2_CFLAGINFO; gboolean use_stored_props = properties; gboolean use_stored_name; if (properties) { info = (FileInfo *)(filebuffer + HEADER_LENGTH1); if (options->de_name_stored) { use_stored_name = !g_str_equal (oldname, info->filename); if (use_stored_name) { newname = g_strdup (info->filename); if (check) //no point in warning about re-use of same name { gchar *checkpath = g_build_filename (dir, newname, NULL); #ifdef E2_VFS ddata.localpath = checkpath; DialogButtons choice = _e2pcr_ow_check (&ddata, options->multisrc); #else DialogButtons choice = _e2pcr_ow_check (checkpath, options->multisrc); #endif g_free (checkpath); if (choice == YES_TO_ALL) options->owrite = FALSE; else if (choice == CANCEL || choice == NO_TO_ALL) { g_free (newname); return choice; } } use_same_name = FALSE; } } else use_stored_name = FALSE; skiplen = HEADER_LENGTH1 + sizeof (FileInfo); originalFileLength = (csize_t) info->statbuf.st_size; if (options->de_props_stored) use_stored_props = TRUE; //signal for later attention } else { info = NULL; //warning prevention only use_stored_name = FALSE; skiplen = HEADER_LENGTH1 + sizeof (csize_t); // originalFileLength = *((csize_t *) (headerBuffer + HEADER_LENGTH1)); originalFileLength = _e2pcr_read_store (filebuffer + HEADER_LENGTH1); } /* if (filebuffersize > headersize) { //decrypt rest of file filebuffer = malloc (filebuffersize); //not g_try_malloc, that's limited to gulong CHECKALLOCATEDWARNT (filebuffer, ) //FIXME memcpy (filebuffer, headerbuffer, headersize); if (!_e2pcr_read_file ((gchar *)localpath, in_desc, filebuffer + headersize, filebuffersize - headersize)) { e2_fs_safeclose (in_desc); retval = NO; goto cleanup; } _e2pcr_crypt_buffer (hashes, &iseed, &jseed, filebuffer + headersize, filebuffersize - headersize); } //check the trailing signature //since it's supposed to be 0, endian-ness doesn't matter flags = *((csize_t *) (filebuffer + filebuffersize - sizeof (csize_t))); if (flags != 0) { printd (DEBUG, "bad password"); e2_fs_error_simple (_("Wrong password for %s"), (gchar *)localpath); e2_fs_safeclose (in_desc); retval = CANCEL; goto cleanup; } */ gpointer uncompressedbuffer; csize_t uncompressedbuffersize; if (compressed) { #ifndef E2_MINICRYPT if (!_e2pcr_check_lib (localpath, flags)) { if (use_stored_name) g_free (newname); return CANCEL; } #endif uncompressedbuffersize = _e2pcr_decompress_buffer (filebuffer + skiplen, filebuffersize - skiplen - sizeof (csize_t), originalFileLength, #ifndef E2_MINICRYPT flags & E2_CFLAGLIBMASK, #endif &uncompressedbuffer); //same buffer used for decomp if (uncompressedbuffersize == 0) { printd (DEBUG, "decompression failed"); e2_fs_error_simple (_("Error decompressing file %s"), localpath); if (use_stored_name) g_free (newname); return CANCEL; } } gchar *temppath = e2_utils_get_tempname (VPSTR (localpath)); #ifdef E2_VFS VPATH tdata = { temppath, localpath->spacedata }; #endif if (compressed) { #ifdef E2_VFS onwards = e2_fs_set_file_contents (&tdata, #else onwards = e2_fs_set_file_contents (temppath, #endif uncompressedbuffer, uncompressedbuffersize, S_IWUSR | S_IRUSR E2_ERR_PTR()); } else { #ifdef E2_VFS onwards = e2_fs_set_file_contents (&tdata, #else onwards = e2_fs_set_file_contents (temppath, #endif filebuffer + skiplen, filebuffersize - skiplen - sizeof (csize_t), S_IWUSR | S_IRUSR E2_ERR_PTR()); } gchar *newpath = (newname == NULL) ? NULL : g_build_filename (dir, newname, NULL); if (onwards) { #ifdef E2_VFS ddata.localpath = newpath; #endif onwards = _e2pcr_finalise_item (localpath, #ifdef E2_VFS &tdata, &ddata, #else temppath, newpath, #endif use_same_name, options->preserve || options->backup, FALSE, hashes); } if (onwards && use_stored_props) { //decrypting with stored data #ifndef E2_VFS const gchar *p; #endif if (use_same_name) { #ifdef E2_VFS ddata.localpath = localpath->localpath; #else p = VPCSTR (localpath); #endif } else { // ddata.localpath = newpath already #ifndef E2_VFS p = newpath; #endif } if (onwards && #ifdef E2_VFS !e2_task_backend_chown (&ddata, info->statbuf.st_uid, info->statbuf.st_gid, FALSE)) #else !e2_task_backend_chown (p, info->statbuf.st_uid, info->statbuf.st_gid, FALSE)) #endif onwards = FALSE; if (onwards && #ifdef E2_VFS e2_fs_chmod (&ddata, info->statbuf.st_mode & ALLPERMS E2_ERR_NONE())) #else e2_fs_chmod (p, info->statbuf.st_mode & ALLPERMS E2_ERR_NONE())) #endif onwards = FALSE; if (onwards) { struct utimbuf tb; tb.modtime = info->statbuf.st_mtime; tb.actime = info->statbuf.st_atime; #ifdef E2_VFS if (e2_fs_utime (&ddata, &tb E2_ERR_NONE())) #else if (e2_fs_utime (p, &tb E2_ERR_NONE())) #endif onwards = FALSE; } } g_free (temppath); if (newpath != NULL) g_free (newpath); if (newname != NULL) g_free (newname); //CAREFUL in caller too return ((onwards) ? OK : NO); } /*****************************/ /** buffer & file functions **/ /*****************************/ /** @brief compress @a filebuffer using using mini-lzo or some other supported library Any error message expects BGL open Size of the buffer is implicitly limited to ULONG_MAX @param filebuffer pointer to buffer to compress @param filebuffersize no. of bytes to compress in @a filebuffer @param compressedbuffer store for pointer newly-allocated buffer holding compressed bytes @return size of compressed buffer, 0 on error */ static csize_t _e2pcr_compress_buffer (gpointer filebuffer, /*size_t*/ gulong filebuffersize, gpointer *compressedbuffer) { #ifdef E2_MINICRYPT csize_t compressedbuffersize = filebuffersize + (filebuffersize >> 6) + 19; *compressedbuffer = malloc (compressedbuffersize); //not g_try_malloc, that's limited to gulong CHECKALLOCATEDWARNT (*compressedbuffer, return 0;) if (*compressedbuffer == NULL) { //FIXME handle error return 0; } gpointer workmem = g_try_malloc (LZO1X_1_MEM_COMPRESS); CHECKALLOCATEDWARNT (workmem, ) if (workmem != NULL) { lzo_uint newbuffersize; gint result = lzo1x_1_compress (filebuffer, filebuffersize, *compressedbuffer, &newbuffersize, workmem); g_free (workmem); if (result == LZO_E_OK) return (csize_t) newbuffersize; } #else printd (DEBUG, "compress file buffer"); csize_t compressedbuffersize; if (compresslib & E2_CFLAGLZO) compressedbuffersize = filebuffersize + (filebuffersize >> 6) + 19; else if (compresslib & E2_CFLAGZ) compressedbuffersize = (filebuffersize * 1.001 + 20 + 8) / 8 * 8; else if (compresslib & E2_CFLAGBZ2) compressedbuffersize = (filebuffersize * 1.01 + 600 + 8) / 8 * 8; else return 0; *compressedbuffer = malloc (compressedbuffersize); //not g_try_malloc, that's limited to gulong CHECKALLOCATEDWARNT (*compressedbuffer, return 0;) if (compresslib & E2_CFLAGLZO) { printd (DEBUG, "compress using LZO"); gpointer workmem = g_try_malloc (LZO1X_1_MEM_COMPRESS); CHECKALLOCATEDWARNT (workmem, ) if (workmem != NULL) { init_compress (); guint compressedlen; gint res = compress_buf (filebuffer, (guint) filebuffersize, *compressedbuffer, &compressedlen, workmem); g_free (workmem); if (res == 0) { return (csize_t) compressedlen; } } } else if (compresslib & E2_CFLAGZ) { printd (DEBUG, "compress using ZLIB"); gulong compressedlen = (gulong) compressedbuffersize; if (compress_buf (*compressedbuffer, &compressedlen, filebuffer, (gulong) filebuffersize, Z_BEST_SPEED) == 0) { return (csize_t) compressedlen; } } else if (compresslib & E2_CFLAGBZ2) { printd (DEBUG, "compress using BZ2"); guint compressedlen = (guint) compressedbuffersize; if (compress_buf (*compressedbuffer, &compressedlen, filebuffer, (guint) filebuffersize, 2, 0, 30) == 0) { return (csize_t) compressedlen; } } #endif //FIXME handle error, warn user printd (DEBUG, "compression failed"); g_free (*compressedbuffer); *compressedbuffer = NULL; return 0; } /** @brief decompress buffer using mini-lzo or some other supported library Size of the buffer is implicitly limited to ULONG_MAX @param filebuffer pointer to buffer to decompress @param filebuffersize no. of bytes in @a filebuffer @param originalfilesize expected length of decompressed buffer #ifndef E2_MINICRYPT @param csize_t libflags which decompression mode to use #endif @param decompressedbuffer store for pointer to newly-allocated buffer holding @a originalFileLength decompressed bytes @return size of uncompressed buffer, 0 on error */ static csize_t _e2pcr_decompress_buffer (gpointer filebuffer, /*size_t*/ gulong filebuffersize, csize_t originalfilesize, #ifndef E2_MINICRYPT //FIXME make altdecompress_buf a function arg, non-static csize_t libflags, //gint (*altdecompress_buf) (), #endif gpointer *decompressedbuffer) { *decompressedbuffer = malloc (originalfilesize); //not g_try_malloc CHECKALLOCATEDWARNT (*decompressedbuffer, return 0;) if (*decompressedbuffer == NULL) return 0; #ifdef E2_MINICRYPT lzo_uint decompressedlen; gint result = lzo1x_decompress (filebuffer, filebuffersize, *decompressedbuffer, &decompressedlen, NULL); if (result == LZO_E_OK && decompfilesize == originalfilesize) return (csize_t) decompfilesize; #else if (libflags & E2_CFLAGLZO) //(E2_CFLAGLZO | E2_CFLAGINTLZ)); { printd (DEBUG, "de-compressing using LZO lib"); init_compress (); guint decompressedlen = (guint) originalfilesize; if (altdecompress_buf (filebuffer, (guint) filebuffersize, *decompressedbuffer, &decompressedlen, NULL //gpointer wrkmem NOT USED ) == 0 && (csize_t) decompressedlen == originalfilesize) { return (csize_t) decompressedlen; } printd (DEBUG, "but that failed - original size %d decompressed to %d", originalfilesize, decompressedlen); } else if (libflags & E2_CFLAGZ) { printd (DEBUG, "de-compressing using ZLIB lib"); gulong decompressedlen = (gulong) originalfilesize; if (altdecompress_buf (*decompressedbuffer, &decompressedlen, filebuffer, (gulong) filebuffersize) == 0 && (csize_t) decompressedlen == originalfilesize) { return (csize_t) decompressedlen; } printd (DEBUG, "but that failed - original size %d decompressed to %d", originalfilesize, decompressedlen); } else if (libflags & E2_CFLAGBZ2) { printd (DEBUG, "de-compressing using BZ2 lib"); guint decompressedlen = (guint) originalfilesize; if (altdecompress_buf (*decompressedbuffer, &decompressedlen, filebuffer, (guint) filebuffersize, 0, 0) == 0 && (csize_t) decompressedlen == originalfilesize) { return (csize_t) decompressedlen; } printd (DEBUG, "but that failed - original size %d decompressed to %d", originalfilesize, decompressedlen); } #endif //FIXME handle error g_free (*decompressedbuffer); *decompressedbuffer = NULL; return 0; } /** @brief fill @a buffer with the contents of some file from $PATH This is an alternative to storing some sequence of data that is readily recognisable as over-written data Expects BGL to be open on arrival here @param buffer pointer to buffer to be overwritten @param buffersize size of @a buffer @return TRUE if the process was completed */ static gboolean _e2pcr_wipe_buffer (gpointer buffer, size_t buffersize) { gboolean retval = FALSE; gchar *sep; gchar *execpath = (gchar *)g_getenv ("PATH"); if (execpath == NULL) { sep = NULL; execpath = "/bin"; } else { sep = strchr (execpath, ':'); //ascii scan ok if (sep != NULL) execpath = g_strndup (execpath, sep-execpath); //FIXME preserve execpath so that later members can be used } #ifdef E2_VFS VPATH ddata = { execpath, NULL }; //files in $PATH must be local GList *entries = (GList *)e2_fs_dir_foreach (&ddata, #else GList *entries = (GList *)e2_fs_dir_foreach (execpath, #endif E2_DIRWATCH_NO, //local = fast read NULL, NULL, NULL E2_ERR_NONE()); if (E2DREAD_FAILED (entries)) { //FIXME try another dir in PATH, or ... //FIXME warn user // e2_fs_error_simple ( // _("You do not have authority to read %s"), execpath); if (sep != NULL) g_free (execpath); return FALSE; } guint count = g_list_length (entries); guint8 c; restart: if (!_e2pcr_getrandom (&c)) { //CHECKME recover ? or ... //FIXME warn user // e2_fs_error_simple ( // _("You do not have authority to modify %s"), (*iterator)->filename); goto cleanup; } guint first = count * c / 256; guint i = 0; gchar *filename, *filepath = NULL; GList *member; reloop: for (member = g_list_nth (entries, first); member != NULL; member = member->next) { filename = (gchar *)member->data; if (!g_str_equal (filename, "..")) { filepath = g_build_filename (execpath, filename, NULL); #ifdef E2_VFS ddata.localpath = filepath; if (!e2_fs_access (&ddata, R_OK E2_ERR_NONE())) #else if (!e2_fs_access (filepath, R_OK E2_ERR_NONE())) #endif break; g_free (filepath); } filepath = NULL; if (++i == count); { //try with next dir from PATH or ... printd (DEBUG, "cannot find a file for data source"); //FIXME warn user // e2_fs_error_simple ( // _("You do not have authority to read anything in %s"), execpath); goto cleanup; } } if (member == NULL && i < count) { //reached end of list, cycle back to start first = 0; goto reloop; } if (filepath == NULL) goto cleanup; E2_ERR_DECLARE gint fdesc = e2_fs_safeopen (filepath, O_RDONLY, 0); if (fdesc < 0) { printd (DEBUG, "cannot open data source file"); goto restart; //try with another file from list /* or ... #ifdef E2_VFS e2_fs_set_error_from_errno (E2_ERR_NAME); #endif e2_fs_error_local (_("Cannot open '%s' for reading"), filepath E2_ERR_MSGL()); E2_ERR_CLEAR goto cleanup; */ } struct stat sb; #ifdef E2_VFS e2_fs_stat (&ddata, &sb E2_ERR_NONE()); #else e2_fs_stat (filepath, &sb E2_ERR_NONE()); #endif csize_t masksize = (csize_t) sb.st_size; ssize_t n_read; if (masksize >= buffersize) { n_read = e2_fs_read (fdesc, buffer, buffersize E2_ERR_PTR()); if (n_read < buffersize) { //#ifdef E2_VFS // e2_fs_set_error_from_errno (&E2_ERR_NAME); //#endif // e2_fs_error_local (_("Error reading file %s"), localpath E2_ERR_MSGL()); E2_ERR_CLEAR //FIXME handle shortfall } } else { //mask-file is smaller than the buffer, read repeatedly until buffer is full csize_t readsofar = 0; guchar *readPtr = buffer; while (readsofar < buffersize) { n_read = e2_fs_read (fdesc, readPtr, masksize E2_ERR_PTR()); if (n_read < masksize) { //#ifdef E2_VFS // e2_fs_set_error_from_errno (&E2_ERR_NAME); //#endif // e2_fs_error_local (_("Error reading file %s"), localpath E2_ERR_MSGL()); E2_ERR_CLEAR //FIXME handle shortfall } lseek (fdesc, 0, SEEK_SET); //FIXME vfs readsofar += masksize; readPtr += masksize; if (readsofar > (buffersize - masksize)) masksize = buffersize - readsofar; } } //FIXME page buffer to disk, to mask any swap storage e2_fs_safeclose (fdesc); retval = TRUE; cleanup: if (sep != NULL) g_free (execpath); e2_list_free_with_data (&entries); return retval; } /** @brief read some or all of a file into @a filebuffer This allows full or partial writing of a file, to support streaming and otherwise-segemented input Any error message expects BGL to be open @param localpath localised name of item being read, used only for error messages @param filebuffer pointer to store for address of allocated buffer @param filebuffersize no. of bytes to read into @a filebuffer @return TRUE if requested no. of bytes were read */ static gboolean _e2pcr_read_file (VPATH *localpath, gpointer *filebuffer, /*size_t*/ gulong filebuffersize) { if (filebuffersize > 0) { E2_ERR_DECLARE /*ssize_t*/ gulong nread; if (!e2_fs_get_file_contents (localpath, filebuffer, &nread, FALSE E2_ERR_PTR()) || nread < filebuffersize) { printd (DEBUG, "cannot read whole file"); //FIXME handle error #ifdef E2_VFS e2_fs_set_error_from_errno (&E2_ERR_NAME); #endif e2_fs_error_local (_("Error reading file %s"), localpath E2_ERR_MSGL()); E2_ERR_CLEAR return FALSE; } } return TRUE; } /** @brief write @a buffer out to storage This allows full or partial writing of a file, to support streaming and otherwise-segemented output Any error message expects BGL to be open @param localpath localised name of item being read, used only for error messages @param descriptor file descriptor @param buffer store for pointer to buffer holding data to write @param buffersize size of @a filebuffer, > 0 @return TRUE if the write was completed */ static gboolean _e2pcr_write_buffer (VPATH *localpath, gint descriptor, gpointer buffer, /*size_t*/ gulong buffersize) { if (buffersize > 0) { E2_ERR_DECLARE ssize_t bytes_written = e2_fs_write (descriptor, buffer, buffersize E2_ERR_PTR()); if ((gulong)bytes_written < buffersize) { #ifdef E2_VFS e2_fs_set_error_from_errno (&E2_ERR_NAME); #endif e2_fs_error_local (_("Error writing file %s"), localpath E2_ERR_MSGL()); E2_ERR_CLEAR return FALSE; } } return TRUE; } /** @brief overwrite and delete @a localpath with the contents of some file from /bin Any error message here expects BGL to be open @param localpath absolute path of item to be processed, localised string @param hashes the hash array used for en/de-cryption @return TRUE if the process was completed */ static gboolean _e2pcr_flush_file (VPATH *localpath, guint8 hashes[KEY_LENGTH]) { E2_ERR_DECLARE struct stat sb; if (e2_fs_stat (localpath, &sb E2_ERR_PTR())) { e2_fs_error_local (_("Cannot get current data for %s"), localpath E2_ERR_MSGL()); E2_ERR_CLEAR return FALSE; } if (sb.st_size == 0) return TRUE; guint8 randomval = 112; _e2pcr_getrandom (&randomval);//fudge the size csize_t wipesize = (csize_t) sb.st_size + (csize_t) randomval; //find a buffer up to 64 times file's block-size csize_t buffersize = sb.st_blksize * 64; while (buffersize > wipesize) buffersize /= 2; if (buffersize < wipesize && buffersize < sb.st_blksize) buffersize = wipesize; gpointer buffer; while ((buffer = malloc (buffersize)) == NULL) { if (buffersize < sb.st_blksize) { gdk_threads_enter (); e2_utils_show_memory_message (); gdk_threads_leave (); return FALSE; } buffersize /= 2; } //open file for writing without truncation gint fdesc = e2_fs_safeopen (VPCSTR (localpath), O_RDWR | O_NONBLOCK, 0); if (fdesc < 0) { g_free (buffer); #ifdef E2_VFS e2_fs_set_error_from_errno (&E2_ERR_NAME); #endif e2_fs_error_local (_("Cannot open '%s' for writing"), localpath E2_ERR_MSGL()); E2_ERR_CLEAR return FALSE; } gboolean retval = FALSE; // flockfile (outputFile); if (buffersize == wipesize) { if (!_e2pcr_wipe_buffer (buffer, buffersize) || !_e2pcr_write_buffer (localpath, fdesc, buffer, buffersize)) { //FIXME error message // e2_fs_error_simple ( // _("You do not have authority to modify %s"), (*iterator)->filename); goto cleanup; } } else { csize_t writesofar = 0; csize_t bsize = buffersize; while (writesofar < wipesize) { if (_e2pcr_wipe_buffer (buffer, bsize) && _e2pcr_write_buffer (localpath, fdesc, buffer, bsize)) { writesofar += bsize; if (writesofar > (wipesize - buffersize)) bsize = wipesize - writesofar; } else { //FIXME error message // e2_fs_error_simple ( // _("You do not have authority to modify %s"), (*iterator)->filename); goto cleanup; } } } fsync (fdesc); retval = TRUE; cleanup: g_free (buffer); // funlockfile (outputFile); e2_fs_safeclose (fdesc); if (retval) { //rename it (which changes ctime to now) gchar *s = _e2pcr_get_tempname (localpath, "ABCDE"); gchar *t = strrchr (s, G_DIR_SEPARATOR); t += sizeof(gchar); guint8 iseed = randomval; guint8 jseed = (guint8) randomval * 2; _e2pcr_crypt_buffer (hashes, &iseed, &jseed, t, (csize_t) strlen (t)); guchar *p = (guchar *) t; while (*p != '\0') { if (*p < '0') *p += '0'; else { while (*p > 0x7e) *p -= 0x10; } p += sizeof(gchar); } #ifdef E2_VFS VPATH ddata = { s, localpath->spacedata }; e2_task_backend_move (localpath, &ddata); #else e2_task_backend_move (localpath, s); #endif //mask file m, atimes - random dates in past year time_t now = time (NULL); struct utimbuf tb; tb.modtime = now - 365 * 24 * 3600 * (time_t) randomval / 256; _e2pcr_getrandom (&randomval); tb.actime = now - 365 * 24 * 3600 * (time_t) randomval / 256; while (tb.actime < tb.modtime) tb.actime += 3600; #ifdef E2_VFS e2_fs_utime (&ddata, &tb E2_ERR_NONE()); #else e2_fs_utime (s, &tb E2_ERR_NONE()); #endif //delete #ifdef E2_VFS e2_task_backend_delete (&ddata); #else e2_task_backend_delete (s); #endif g_free (s); } return retval; } /** @brief encrypt or decrypt file @a localpath @a localpath may be target of a link, whose path is given in options Any error message here expects BGL to be open @param localpath absolute path of item to process, localised string @param options pointer to process-parameters data @return OK or YES_TO_ALL if successful, CANCEL or NO_TO_ALL if user chooses those, NO after error */ static DialogButtons _e2pcr_crypt1 (VPATH *localpath, E2P_CryptOpts *options) { gchar *s, *newname = NULL; //warning prevention gchar *dir = g_path_get_dirname (VPSTR (localpath)); gchar *oldname = g_path_get_basename (VPSTR (localpath)); DialogButtons retval; gboolean use_same_name = FALSE; gboolean check = (options->backup || options->owrite) ? FALSE : e2_option_bool_get ("confirm-overwrite"); #ifdef E2_VFS VPATH ddata; ddata.spacedata = localpath->spacedata; #endif if (options->decryptmode) //doing decryption { //check naming arrangements specified by dialog flags & widgets gboolean use_stored_name = FALSE; if (options->de_name_same) //decrypted file name = same as encrypted name use_same_name = TRUE; else { if (options->de_name_suffix) { newname = F_FILENAME_TO_LOCALE (options->de_suffix); if (*newname != '\0' && g_str_has_suffix (oldname, newname)) { use_same_name = FALSE; gint len = strlen (newname); F_FREE (newname); newname = g_strdup (oldname); *(newname + strlen (newname) - len) = '\0'; } else { if (*newname != '\0' && !options->ignore_suffix) { //ask user what to do gchar *utf = F_FILENAME_FROM_LOCALE (VPSTR (localpath)); s = g_strdup_printf ( _("%s does not end with \"%s\".\nProcess this file anyway?"), utf, options->de_suffix); retval = _e2pcr_dialog_warning (s, options->multisrc); F_FREE (utf); g_free (s); switch (retval) { case YES_TO_ALL: options->ignore_suffix = TRUE; case OK: break; //case NO_TO_ALL: //CHECKME consider a flag for stopping, instead of the current spaghetti to provoke a stop //case CANCEL: default: F_FREE (newname); g_free (dir); g_free (oldname); return retval; } } use_same_name = TRUE; F_FREE (newname); } } else if (options->de_name_custom) { if (*options->de_name != '\0') { newname = D_FILENAME_TO_LOCALE (options->de_name); use_same_name = g_str_equal (oldname, newname); if (use_same_name) g_free (newname); } else use_same_name = TRUE; } else //last choice is stored name (which may, later, turn out to be N/A or same) { use_stored_name = TRUE; // use_same_name = TRUE; //nothing to cleanup afterwards } //newname now clear or g_freeable } if (check && !use_same_name //no point in warning about re-use of same name && !use_stored_name) //we already know the name of the processed item { s = g_build_filename (dir, newname, NULL); #ifdef E2_VFS ddata.localpath = s; retval = _e2pcr_ow_check (&ddata, options->multisrc); #else retval = _e2pcr_ow_check (s, options->multisrc); #endif g_free (s); if (retval == YES_TO_ALL) options->owrite = FALSE; else if (retval == CANCEL || retval == NO_TO_ALL) { g_free (dir); g_free (oldname); // if (!use_same_name) g_free (newname); return retval; } } retval = _e2pcr_decrypt1 (localpath, dir, oldname, newname, use_same_name, check, options); } else //doing encryption { //check naming arrangements specified by dialog flags & widgets if (options->en_name_same) //encrypted file name = same as original name use_same_name = TRUE; else { if (options->en_name_suffix) { if (*options->en_suffix != '\0') { use_same_name = FALSE; s = F_FILENAME_TO_LOCALE (options->en_suffix); newname = e2_utils_strcat (oldname, s); F_FREE (s); } else use_same_name = TRUE; } else if (options->en_name_custom) { if (*options->en_name != '\0') { newname = D_FILENAME_TO_LOCALE (options->en_name); use_same_name = g_str_equal (oldname, newname); if (use_same_name) g_free (newname); } else use_same_name = TRUE; } else use_same_name = TRUE; //should never get here } if (check && !use_same_name) //no point in warning about re-use of same name { s = g_build_filename (dir, newname, NULL); #ifdef E2_VFS ddata.localpath = s; retval = _e2pcr_ow_check (&ddata, options->multisrc); #else retval = _e2pcr_ow_check (s, options->multisrc); #endif g_free (s); if (retval == YES_TO_ALL) options->owrite = FALSE; else if (retval == CANCEL || retval == NO_TO_ALL) { g_free (dir); g_free (oldname); // if (!use_same_name) g_free (newname); return retval; } } retval = _e2pcr_encrypt1 (localpath, dir, oldname, newname, use_same_name, check, options); } //end of encryption-specific section g_free (dir); g_free (oldname); // if (!use_same_name) cleared downstream // g_free (newname); return retval; } /** @brief callback function for recursive directory processing This is called for each non-directory item in the directory to be processed. Treewalk is breadth-first, not physical @param localpath absolute path of item to copy, localised string @param statptr pointer to struct stat with info about @a localpath @param status code from the walker, indicating what type of report it is @param user_data pointer to user-specified data @return E2TW_CONTINUE on success, others as appropriate */ static E2_TwResult _e2pcr_task_twcb_crypt (VPATH *localpath, const struct stat *statptr, E2_TwStatus status, E2P_CryptOpts *user_data) { E2_TwResult retval = E2TW_CONTINUE; switch (status) { DialogButtons cryptresult; mode_t mode; E2_DirEnt *dirfix; GList *member; #ifdef E2_VFS VPATH ddata; #endif case E2TW_DP: //dir completed //revert any altered dir permissions #ifdef E2_VFS ddata.spacedata = localpath->spacedata; #endif mode = statptr->st_mode & ALLPERMS; for (member = g_list_last (user_data->dirdata); member != NULL; member = member->prev) { dirfix = member->data; if (dirfix != NULL) { if (g_str_equal (dirfix->path, VPSTR (localpath))) { #ifdef E2_VFS ddata.localpath = dirfix->path; #endif if ((mode & ALLPERMS) != dirfix->mode && #ifdef E2_VFS e2_fs_chmod (&ddata, dirfix->mode E2_ERR_NONE())) #else e2_fs_chmod (localpath, dirfix->mode E2_ERR_NONE())) #endif retval = E2TW_STOP; //CHECKME - want cleanup of copied file to continue g_free (dirfix->path); DEALLOCATE (E2_DirEnt, dirfix); user_data->dirdata = g_list_delete_link (user_data->dirdata, member); break; } } // else //should never happen CHECKME ok when walking list ? // user_data->dirdata = g_list_delete_link (user_data->dirdata, member); } break; case E2TW_DM: //dir, not opened due to different file system (reported upstream) case E2TW_DL: //dir, not opened due to depth limit (reported upstream) case E2TW_DNR: //unreadable dir (for which, error is reported upstream) //eventual chmod for this will probably fail, but try anyhow //CHECKME report continue after problem // retval = E2TW_FIXME; break; case E2TW_DRR: //dir now readable case E2TW_D: //ensure dir is writable, if we can if (e2_fs_tw_adjust_dirmode (localpath, statptr, (W_OK | X_OK)) == 0) //failed to set missing W and/or X perm retval = E2TW_SKIPSUB; //can't process any item in the dir //CHECKME does DP report arrive ? else { //add this dir to list of items to revert afterwards //CHECKME what if newmode == oldmode ? (omit == scan full list in DP cb) dirfix = ALLOCATE (E2_DirEnt); CHECKALLOCATEDWARNT (dirfix, result = E2TW_STOP; break;) dirfix->path = g_strdup (VPSTR (localpath)); dirfix->mode = statptr->st_mode & ALLPERMS; user_data->dirdata = g_list_append (user_data->dirdata, dirfix); } break; case E2TW_F: //not directory or link if (S_ISREG (statptr->st_mode)) { struct stat sb; user_data->localpath = VPSTR (localpath); //ok to throw away the original sb = *statptr; //get a non-const statbuf user_data->statptr = &sb; cryptresult = _e2pcr_crypt1 (localpath, user_data); if (cryptresult == NO_TO_ALL || cryptresult == NO) //NO == error retval = E2TW_STOP; } break; case E2TW_SL: //symbolic link if (user_data->walklinks) { //get ultimate target of link gchar *target = g_strdup (VPSTR (localpath)); if (e2_fs_walk_link (&target E2_ERR_NONE())) { struct stat sb; #ifdef E2_VFS ddata.localpath = target; ddata.spacedata = localpath->spacedata; if (!e2_fs_stat (&ddata, &sb E2_ERR_NONE())) #else if (!e2_fs_stat (target, &sb E2_ERR_NONE())) #endif { user_data->localpath = VPSTR (localpath); //ok to throw away the original user_data->statptr = &sb; #ifdef E2_VFS cryptresult = _e2pcr_crypt1 (&ddata, user_data); #else cryptresult = _e2pcr_crypt1 (target, user_data); #endif } else cryptresult = NO; } else cryptresult = NO; g_free (target); if (cryptresult == NO_TO_ALL || cryptresult == NO) //NO == error retval = E2TW_STOP; } // case E2TW_SLN: //symbolic link targeting non-existent item // error message ?? // retval = E2TW_STOP; default: break; } return retval; } /** @brief apply the user's choice to @a localpath Error messages here and downstream expect BGL open @param options pointer to process parameters data @return TRUE if successful */ static DialogButtons _e2pcr_apply (E2P_CryptOpts *options) { #ifdef E2_VFS VPATH ddata; ddata.spacedata = options->spacedata; #endif if (S_ISDIR (options->statptr->st_mode)) { if (!options->recurse) return CANCEL; if ((options->decryptmode && options->de_name_same) || (!options->decryptmode && options->en_name_same)) return CANCEL; //CHECKME warning ? ok if only 1 file/link in dir ? //the path may be changed in the treewalk const gchar *savepath = options->localpath; //recursively process dir contents //walk-flags for: fix-DNR, no thru-links if appropriate E2_TwFlags exec_flags = E2TW_FIXDIR; if (!options->walklinks) exec_flags |= E2TW_PHYS; E2_ERR_DECLARE; //CHECKME allow continue after error? #ifdef E2_VFS ddata.localpath = options->localpath; gboolean retval = e2_fs_tw (&ddata, #else gboolean retval = e2_fs_tw ((gchar *)options->localpath, #endif _e2pcr_task_twcb_crypt, options, -1, exec_flags E2_ERR_PTR()); //normally no leftover dir data, but ensure cleanup ... GList *member; for (member = g_list_last (options->dirdata); member != NULL; member = member->prev) { E2_DirEnt *dirfix = member->data; if (dirfix != NULL) { #ifdef E2_VFS ddata.localpath = dirfix->path; if (e2_fs_chmod (&ddata, dirfix->mode E2_ERR_PTR())) #else if (e2_fs_chmod (dirfix->path, dirfix->mode E2_ERR_PTR())) #endif retval = FALSE; g_free (dirfix->path); DEALLOCATE (E2_DirEnt, dirfix); } } //handle errors if (!retval && E2_ERR_ISNOT(0)) //might be a user-abort, not an error { #ifdef E2_VFS ddata.localpath = savepath; #endif e2_fs_error_local (_("Cannot process all of %s"), #ifdef E2_VFS &ddata E2_ERR_MSGL()); #else (gchar *) savepath E2_ERR_MSGL()); #endif E2_ERR_CLEAR } return (retval) ? OK : NO_TO_ALL; //no need for a simple CANCEL } else //not a dir if (S_ISLNK (options->statptr->st_mode)) { //handle links separately, to manage 'look-through' if (!options->walklinks) return FALSE; //get ultimate target and remove any relativity DialogButtons cryptresult; gchar *target = g_strdup (options->localpath); if (e2_fs_walk_link (&target E2_ERR_NONE())) { struct stat sb; #ifdef E2_VFS ddata.localpath = target; if (!e2_fs_stat (&ddata, &sb E2_ERR_NONE())) #else if (!e2_fs_stat (target, &sb E2_ERR_NONE())) #endif { options->localpath = target; //ok to throw away the original options->statptr = &sb; #ifdef E2_VFS cryptresult = _e2pcr_crypt1 (&ddata, options); #else cryptresult = _e2pcr_crypt1 (target, options); #endif } else cryptresult = NO; } else cryptresult = NO; g_free (target); return cryptresult; // return (_e2pcr_crypt1 (target, options) == OK); } else //not dir or link #ifdef E2_VFS { ddata.localpath = options->localpath; return (_e2pcr_crypt1 (&ddata, options)); } #else return (_e2pcr_crypt1 (options->localpath, options)); #endif } /** @brief determine whether intended change is permitted @param rt pointer to dialog runtime data struct @return TRUE if change is permitted, or if current item is a dir with W,X permissions */ static gboolean _e2pcr_check_permission (E2P_CryptDlgRuntime *rt) { gchar target[PATH_MAX]; gchar *newpath, *localname, *dir; const gchar *localpath; struct stat *sp; #ifdef E2_VFS VPATH ddata; ddata.spacedata = rt->opts->spacedata; #endif localpath = rt->opts->localpath; sp = rt->opts->statptr; restart: #ifdef E2_VFS ddata.localpath = localpath; if (e2_fs_lstat (&ddata, sp E2_ERR_NONE())) #else if (e2_fs_lstat (localpath, sp E2_ERR_NONE())) #endif { rt->opts->permission = FALSE; return FALSE; } if (S_ISLNK (sp->st_mode)) { if ((rt->dlgopen && !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rt->linktarget_btn))) || !rt->opts->walklinks) { rt->opts->permission = FALSE; return FALSE; } gchar *checktarget = g_strdup (localpath); if (!e2_fs_walk_link (&checktarget E2_ERR_NONE())) { g_free (checktarget); rt->opts->permission = FALSE; return FALSE; } g_strlcpy (target, checktarget, sizeof (target)); g_free (checktarget); localpath = target; goto restart; } if (S_ISDIR (sp->st_mode)) { #ifdef E2_VFS rt->opts->permission = !e2_fs_access (&ddata, X_OK | W_OK E2_ERR_NONE()); #else rt->opts->permission = !e2_fs_access (localpath, X_OK | W_OK E2_ERR_NONE()); #endif return rt->opts->permission; } dir = g_path_get_dirname (localpath); //access _always_ traverses links #ifdef E2_VFS ddata.localpath = dir; if (!e2_fs_access (&ddata, X_OK | W_OK E2_ERR_NONE())) #else if (!e2_fs_access (dir, X_OK | W_OK E2_ERR_NONE())) #endif { if (rt->opts->decryptmode) { if (rt->opts->de_name_same) //decrypted file name = same as encrypted name #ifdef E2_VFS { ddata.localpath = localpath; rt->opts->permission = !e2_fs_access (&ddata, W_OK E2_ERR_NONE()); } #else rt->opts->permission = !e2_fs_access (localpath, W_OK E2_ERR_NONE()); #endif else { if (rt->opts->de_name_suffix) { localname = (rt->dlgopen) ? F_FILENAME_TO_LOCALE ( gtk_entry_get_text (GTK_ENTRY (rt->de_name_suffix_entry))): F_FILENAME_TO_LOCALE (rt->opts->de_suffix); newpath = g_strdup (localpath); if (*localname != '\0' && g_str_has_suffix (newpath, localname)) *(newpath + strlen (newpath) - strlen (localname)) = '\0'; #ifdef E2_VFS ddata.localpath = newpath; rt->opts->permission = e2_fs_access (&ddata, F_OK E2_ERR_NONE()) || !e2_fs_access (&ddata, W_OK E2_ERR_NONE()); #else rt->opts->permission = e2_fs_access (newpath, F_OK E2_ERR_NONE()) || !e2_fs_access (newpath, W_OK E2_ERR_NONE()); #endif F_FREE (localname); g_free (newpath); } else if (rt->opts->de_name_custom) { localname = (rt->dlgopen) ? F_FILENAME_TO_LOCALE ( gtk_entry_get_text (GTK_ENTRY (rt->de_name_custom_entry))): F_FILENAME_TO_LOCALE (rt->opts->de_name); newpath = g_build_filename (dir, localname, NULL); #ifdef E2_VFS ddata.localpath = newpath; rt->opts->permission = e2_fs_access (&ddata, F_OK E2_ERR_NONE()) || !e2_fs_access (&ddata, W_OK E2_ERR_NONE()); #else rt->opts->permission = e2_fs_access (newpath, F_OK E2_ERR_NONE()) || !e2_fs_access (newpath, W_OK E2_ERR_NONE()); #endif F_FREE (localname); g_free (newpath); } else rt->opts->permission = TRUE; } } else //encrypting { if (rt->opts->en_name_same) //encrypted file name = same as original #ifdef E2_VFS { ddata.localpath = localpath; rt->opts->permission = !e2_fs_access (&ddata, W_OK E2_ERR_NONE()); } #else rt->opts->permission = !e2_fs_access (localpath, W_OK E2_ERR_NONE()); #endif else { if (rt->opts->en_name_suffix) { localname = (rt->dlgopen) ? F_FILENAME_TO_LOCALE ( gtk_entry_get_text (GTK_ENTRY (rt->en_name_suffix_entry))): F_FILENAME_TO_LOCALE (rt->opts->en_suffix); newpath = g_strconcat (localpath, localname, NULL); #ifdef E2_VFS ddata.localpath = newpath; rt->opts->permission = e2_fs_access (&ddata, F_OK E2_ERR_NONE()) || !e2_fs_access (&ddata, W_OK E2_ERR_NONE()); #else rt->opts->permission = e2_fs_access (newpath, F_OK E2_ERR_NONE()) || !e2_fs_access (newpath, W_OK E2_ERR_NONE()); #endif F_FREE (localname); g_free (newpath); } else if (rt->opts->en_name_custom) { localname = (rt->dlgopen) ? F_FILENAME_TO_LOCALE ( gtk_entry_get_text (GTK_ENTRY (rt->en_name_custom_entry))): F_FILENAME_TO_LOCALE (rt->opts->en_name); newpath = g_build_filename (dir, localname, NULL); #ifdef E2_VFS ddata.localpath = newpath; rt->opts->permission = e2_fs_access (&ddata, F_OK E2_ERR_NONE()) || !e2_fs_access (&ddata, W_OK E2_ERR_NONE()); #else rt->opts->permission = e2_fs_access (newpath, F_OK E2_ERR_NONE()) || !e2_fs_access (newpath, W_OK E2_ERR_NONE()); #endif F_FREE (localname); g_free (newpath); } else rt->opts->permission = TRUE; } } } else rt->opts->permission = FALSE; g_free (dir); return rt->opts->permission; } /** @brief determine change-permission and set dialog button-sensitivities accordingly @param rt pointer to dialog data struct @return */ static void _e2pcr_set_buttons (E2P_CryptDlgRuntime *rt) { gboolean ok, encmode, custom; ok = _e2pcr_check_permission (rt); //enable or disable button(s) if (rt->opts->multisrc) { if (ok) { //disable yes-to-all button (if any) when a custom name is to be used encmode = //(rt->dlgopen) ? gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rt->mode_btn));// : !rt->opts->decryptmode; if (encmode) custom = //(rt->dlgopen) ? gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rt->en_name_btn_custom));// : rt->opts->en_name_custom ; else custom =// (rt->dlgopen) ? gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rt->de_name_btn_custom));// : rt->opts->de_name_custom ; } else custom = FALSE; //warning prevention gtk_dialog_set_response_sensitive (GTK_DIALOG (rt->dialog), E2_RESPONSE_YESTOALL, ok & !custom); } gtk_dialog_set_response_sensitive (GTK_DIALOG (rt->dialog), GTK_RESPONSE_OK, ok); ok = !((!rt->opts->decryptmode && rt->opts->en_name_custom) || (rt->opts->decryptmode && rt->opts->de_name_custom)); gtk_widget_set_sensitive (rt->recurse_btn, ok); } /** @brief key-release callback @param entry UNUSED the entry widget where the key was pressed @param event pointer to event data struct @param rt pointer to data struct for the search @return FALSE always */ static gboolean _e2pcr_keyrel_cb (GtkWidget *entry, GdkEventKey *event, E2P_CryptDlgRuntime *rt) { _e2pcr_set_buttons (rt); return FALSE; } /** @brief callback for toggle encryption (mode) button @param widget toggled encrypt button @param rt pointer to dialog data struct @return */ static void _e2pcr_toggle_mode_cb (GtkWidget *widget, E2P_CryptDlgRuntime *rt) { gboolean state = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)); if (state) { gtk_widget_hide (rt->decryptbox); gtk_widget_show (rt->encryptbox); gtk_widget_show (rt->confirmbox); gtk_widget_show (rt->en_properties_embed_btn); gtk_widget_show (rt->compress_btn); gtk_widget_hide (rt->properties_btn); gtk_widget_set_sensitive (rt->recurse_btn, !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rt->en_name_btn_custom))); } else { gtk_widget_hide (rt->encryptbox); gtk_widget_show (rt->decryptbox); gtk_widget_hide (rt->confirmbox); gtk_widget_hide (rt->en_properties_embed_btn); gtk_widget_hide (rt->compress_btn); gtk_widget_show (rt->properties_btn); gtk_widget_set_sensitive (rt->recurse_btn, !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rt->de_name_btn_custom))); } // gtk_widget_set_sensitive (rt->pwrt->pwentry2, state); rt->opts->decryptmode = !state; rt->pwrt->confirm = state; //make the password dialog check/not for matches //determine and handle permission _e2pcr_set_buttons (rt); } /** @brief callback for encryped name toggle buttons @param widget clicked button @param rt pointer to dialog data struct @return */ static void _e2pcr_toggle_encname_cb (GtkWidget *widget, E2P_CryptDlgRuntime *rt) { if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) { if (widget == rt->en_name_btn_suffix) { gtk_widget_set_sensitive (rt->en_name_suffix_entry, TRUE); gtk_widget_set_sensitive (rt->en_name_custom_entry, FALSE); } else if (widget == rt->en_name_btn_custom) { gtk_widget_set_sensitive (rt->en_name_custom_entry, TRUE); gtk_widget_set_sensitive (rt->en_name_suffix_entry, FALSE); } else //same name { gtk_widget_set_sensitive (rt->en_name_suffix_entry, FALSE); gtk_widget_set_sensitive (rt->en_name_custom_entry, FALSE); } gtk_widget_set_sensitive (rt->recurse_btn, widget != rt->en_name_btn_custom); //determine and handle permission _e2pcr_set_buttons (rt); } } /** @brief callback for decryped name toggle buttons Assumes BGL is off/open @param widget clicked button @param rt pointer to dialog data struct @return */ static void _e2pcr_toggle_decname_cb (GtkWidget *widget, E2P_CryptDlgRuntime *rt) { if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) { if (widget == rt->de_name_btn_suffix) { gtk_widget_set_sensitive (rt->de_name_suffix_entry, TRUE); gtk_widget_set_sensitive (rt->de_name_custom_entry, FALSE); } else if (widget == rt->de_name_btn_custom) { gtk_widget_set_sensitive (rt->de_name_custom_entry, TRUE); gtk_widget_set_sensitive (rt->de_name_suffix_entry, FALSE); } else //same name { gtk_widget_set_sensitive (rt->de_name_suffix_entry, FALSE); gtk_widget_set_sensitive (rt->de_name_custom_entry, FALSE); } gtk_widget_set_sensitive (rt->recurse_btn, widget != rt->de_name_btn_custom); //determine and handle permission _e2pcr_set_buttons (rt); } } /** @brief handle button click, window-close etc for crypt dialog This is the callback for "response" signals emitted from @a dialog @param dialog UNUSED the dialog where the response was generated @param response the response returned from the dialog @param rt pointer to data struct for the dialog @return */ static void _e2pcr_response_cb (GtkDialog *dialog, gint response, E2P_CryptDlgRuntime *rt) { gboolean finished; switch (response) { case E2_RESPONSE_YESTOALL: case GTK_RESPONSE_OK: //only end if the password(s) are ok finished = e2_password_dialog_confirm (rt->pwrt); break; default: finished = TRUE; break; } if (finished) { e2_password_dialog_backup (rt->pwrt); //backup static stuff switch (response) { case E2_RESPONSE_YESTOALL: rt->result = YES_TO_ALL; break; case GTK_RESPONSE_OK: rt->result = OK; break; case E2_RESPONSE_NOTOALL: rt->result = NO_TO_ALL; break; default: rt->result = CANCEL; break; } //do not cleanup widgets here - done in main code gtk_main_quit (); } } /** @brief create and run an encryption-change dialog @param options pointer to options data struct @return enumerator corresponding to user's choice of action */ static DialogButtons _e2pcr_crypt_dialog_run (E2P_CryptOpts *options) { const gchar *localpath = options->localpath; struct stat *sp = options->statptr; #ifdef E2_VFS VPATH ddata = { localpath, options->spacedata }; if (e2_fs_lstat (&ddata, sp E2_ERR_NONE())) #else if (e2_fs_lstat (localpath, sp E2_ERR_NONE())) #endif return CANCEL; if (!(S_ISREG (sp->st_mode) || S_ISDIR (sp->st_mode) || S_ISLNK (sp->st_mode))) return CANCEL; printd (DEBUG, "create crypt dialog"); E2P_CryptDlgRuntime crt; gchar *pw = NULL; //create a temporary container from which we can later plunder some widgets GtkWidget *tempbox = gtk_vbox_new (FALSE, 0); //create P/W widgets, with both entries, we'll hide one as appropriate //CHECKME make it tabular ? crt.pwrt = e2_password_dialog_setup (tempbox, TRUE, /*FALSE,*/ NULL, &pw); if (crt.pwrt == NULL) { //FIXME warn about memory error gtk_widget_destroy (tempbox); return NO_TO_ALL; } crt.dialog = e2_dialog_create (NULL, NULL, _("en/decrypt file"), _e2pcr_response_cb, &crt); crt.opts = options; gdk_threads_enter (); //seems more stable with a single lock GtkWidget *dialog_vbox = GTK_DIALOG (crt.dialog)->vbox; //things that go before the password entries GString *label_text = g_string_sized_new (NAME_MAX+20); gchar *type; switch (sp->st_mode & S_IFMT) { case S_IFDIR: type = _("Directory"); break; case S_IFLNK: type = _("Symbolic link"); break; // case S_IFREG: default: type = _("Filename"); break; } gchar *name = g_path_get_basename (localpath); gchar *utf = F_FILENAME_FROM_LOCALE (name); //needed for naming g_string_printf (label_text, "%s: %s", type, utf); if (S_ISLNK (sp->st_mode)) { gchar *target = g_strdup (localpath); if (e2_fs_walk_link (&target E2_ERR_NONE())) { gchar *utfname = F_DISPLAYNAME_FROM_LOCALE (target); g_string_append_printf (label_text, _(" to %s"), utfname); F_FREE (utfname); } g_free (target); } e2_widget_add_mid_label (dialog_vbox, label_text->str, 0, TRUE, E2_PADDING); //L, R padding GtkWidget *hbox = e2_widget_add_box (dialog_vbox, FALSE, 0, FALSE, TRUE, 0); //FIXME set initial mode according to extension of item crt.mode_btn = e2_button_add_radio (hbox, _("encrypt"), NULL, !options->decryptmode, FALSE, 0, NULL, NULL); //set callback later, after widgets created GSList *group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (crt.mode_btn)); e2_button_add_radio (hbox, _("decrypt"), group, options->decryptmode, FALSE, 0, NULL, NULL); crt.encryptbox = gtk_vbox_new (FALSE, 0); crt.en_name_btn_same = e2_button_add_radio (crt.encryptbox, _("encrypted file will have same name"), NULL, options->en_name_same, FALSE, 0, NULL, NULL); //set callback later, after widgets created; hbox = e2_widget_add_box (crt.encryptbox, TRUE, 0, FALSE, FALSE, E2_PADDING); group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (crt.en_name_btn_same)); crt.en_name_btn_suffix = e2_button_add_radio (hbox, _("append this to encrypted file name"), group, options->en_name_suffix, FALSE, 0, NULL, NULL); // printd (DEBUG, "create crypt dialog 3"); // gdk_threads_enter (); //latency reduction ? PROBABLY A CRASHER crt.en_name_suffix_entry = e2_widget_add_entry (hbox, options->en_suffix, TRUE, FALSE); //no use selecting it // gdk_threads_leave (); gtk_widget_set_size_request (crt.en_name_suffix_entry, 80, -1); gtk_widget_set_sensitive (crt.en_name_suffix_entry, options->en_name_suffix); //#ifdef E2_ASSISTED // e2_widget_set_label_relations (GTK_LABEL (label), crt.en_name_suffix_entry); //#endif //enable permissions change after any change g_signal_connect_after (G_OBJECT (crt.en_name_suffix_entry), "key-release-event", G_CALLBACK (_e2pcr_keyrel_cb), &crt); hbox = e2_widget_add_box (crt.encryptbox, TRUE, 0, FALSE, TRUE, 0); group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (crt.en_name_btn_same)); crt.en_name_btn_custom = e2_button_add_radio (hbox, _("encrypted file name will be"), group, options->en_name_custom, FALSE, 0, NULL, NULL); // printd (DEBUG, "create crypt dialog 4"); // gdk_threads_enter (); //latency reduction ? crasher ? crt.en_name_custom_entry = e2_widget_add_entry (hbox, utf, TRUE, FALSE); //no use selecting it // gdk_threads_leave (); // gtk_widget_set_size_request (crt.en_name_custom_entry, 120, -1); gtk_widget_set_sensitive (crt.en_name_custom_entry, options->en_name_custom); // printd (DEBUG, "create crypt dialog 4A"); //#ifdef E2_ASSISTED // e2_widget_set_label_relations (GTK_LABEL (label), crt.en_name_custom_entry); //#endif g_signal_connect_after (G_OBJECT (crt.en_name_custom_entry), "key-release-event", G_CALLBACK (_e2pcr_keyrel_cb), &crt); gtk_box_pack_start (GTK_BOX (dialog_vbox), crt.encryptbox, TRUE, TRUE, 0); //top, bottom padding crt.decryptbox = gtk_vbox_new (FALSE, 0); crt.de_name_btn_same = e2_button_add_radio (crt.decryptbox, _("decrypted file will have same name"), NULL, options->de_name_same, FALSE, 0, NULL, NULL); //set callback later, after widgets created; group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (crt.de_name_btn_same)); crt.de_name_btn_stored = e2_button_add_radio (crt.decryptbox, _("decrypted file will have embedded name"), group, options->de_name_stored, FALSE, 0, NULL, NULL); hbox = e2_widget_add_box (crt.decryptbox, TRUE, 0, FALSE, FALSE, 0); group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (crt.de_name_btn_same)); crt.de_name_btn_suffix = e2_button_add_radio (hbox, _("strip this from end of decrypted file name"), group, options->de_name_suffix, FALSE, 0, NULL, NULL); // printd (DEBUG, "create crypt dialog 5"); // gdk_threads_enter (); //latency reduction ? crasher ? crt.de_name_suffix_entry = e2_widget_add_entry (hbox, options->de_suffix, TRUE, FALSE); // gdk_threads_leave (); gtk_widget_set_size_request (crt.de_name_suffix_entry, 80, -1); gtk_widget_set_sensitive (crt.de_name_suffix_entry, options->de_name_suffix); //#ifdef E2_ASSISTED // e2_widget_set_label_relations (GTK_LABEL (label), crt.de_name_suffix_entry); //#endif // printd (DEBUG, "create crypt dialog 5A"); g_signal_connect_after (G_OBJECT (crt.de_name_suffix_entry), "key-release-event", G_CALLBACK (_e2pcr_keyrel_cb), &crt); hbox = e2_widget_add_box (crt.decryptbox, TRUE, 0, FALSE, TRUE, 0); group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (crt.de_name_btn_same)); crt.de_name_btn_custom = e2_button_add_radio (hbox, _("decrypted file name will be"), group, options->de_name_custom, FALSE, 0, NULL, NULL); // printd (DEBUG, "create crypt dialog 6"); // gdk_threads_enter (); //latency reduction ? crt.de_name_custom_entry = e2_widget_add_entry (hbox, utf, TRUE, FALSE); // gdk_threads_leave (); // gtk_widget_set_size_request (crt.de_name_custom_entry, 120, -1); gtk_widget_set_sensitive (crt.de_name_custom_entry, options->de_name_custom); //#ifdef E2_ASSISTED // e2_widget_set_label_relations (GTK_LABEL (label), crt.de_name_custom_entry); //#endif // printd (DEBUG, "create crypt dialog 6A"); g_signal_connect_after (G_OBJECT (crt.de_name_custom_entry), "key-release-event", G_CALLBACK (_e2pcr_keyrel_cb), &crt); gtk_box_pack_start (GTK_BOX (dialog_vbox), crt.decryptbox, TRUE, TRUE, E2_PADDING); //top, bottom padding hbox = e2_widget_add_box (dialog_vbox, FALSE, 0, FALSE, TRUE, 0); //the existing label in tempbox is centred, might as well set a new one e2_widget_add_mid_label (hbox, _("Enter password:"), 0, FALSE, E2_PADDING_SMALL); //L, R padding // printd (DEBUG, "create crypt dialog 6B"); //supply a previously-entered password (but not the confirmation one when encrypting?) if (options->plain_pw != NULL) { gtk_entry_set_text (GTK_ENTRY (crt.pwrt->pwentry1), options->plain_pw); gtk_editable_select_region (GTK_EDITABLE (crt.pwrt->pwentry1), 0, -1); } gtk_widget_reparent (crt.pwrt->pwentry1, hbox); crt.confirmbox = e2_widget_add_box (dialog_vbox, FALSE, 0, FALSE, TRUE, 0); e2_widget_add_mid_label (crt.confirmbox, _("Confirm password:"), 0, FALSE, E2_PADDING_SMALL); //L, R padding // printd (DEBUG, "create crypt dialog 7"); if (options->plain_pw != NULL) { gtk_entry_set_text (GTK_ENTRY (crt.pwrt->pwentry2), options->plain_pw); // gtk_editable_select_region (GTK_EDITABLE (rt->pwentry2), 0, -1); } gtk_widget_reparent (crt.pwrt->pwentry2, crt.confirmbox); //clear original dialog's box // hbox = g_object_get_data (G_OBJECT (rt->dialog), "e2-dialog-hbox"); // gtk_container_remove (GTK_CONTAINER (dialog_vbox), hbox); gtk_widget_destroy (tempbox); //finished with that now crt.properties_btn = e2_button_add_toggle (dialog_vbox, TRUE, options->de_props_stored, _("restore properties"), _("decrypted file will have stored owners, permissions and dates"), FALSE, 0, NULL, NULL); hbox = e2_widget_add_box (dialog_vbox, FALSE, 0, FALSE, TRUE, E2_PADDING_SMALL); #ifdef E2_MINICRYPT crt.compress_btn = e2_button_add_toggle (hbox, TRUE, options->compress, _("compress"), _("compress file before encryption"), FALSE, 0, NULL, NULL); #else crt.compress_btn = e2_button_add_toggle (hbox, TRUE, (options->compress && (compresslib & E2_CFLAGCOMPRESS)), _("compress"), _("compress file before encryption"), FALSE, 0, NULL, NULL); if (!(compresslib & E2_CFLAGCOMPRESS)) { options->compress = FALSE; gtk_widget_set_sensitive (crt.compress_btn, FALSE); } #endif crt.en_properties_embed_btn = e2_button_add_toggle (hbox, TRUE, options->en_properties_embed, _("store properties"), _("store current name, permissions etc in the encrypted file"), FALSE, 0, NULL, NULL); hbox = e2_widget_add_box (dialog_vbox, FALSE, 0, FALSE, TRUE, E2_PADDING_SMALL); crt.backup_btn = e2_button_add_toggle (hbox, TRUE, options->backup, _("backup"), _("backup an existing file with the same name as the processed file"), FALSE, 0, NULL, NULL); crt.preserve_btn = e2_button_add_toggle (hbox, TRUE, options->preserve, _("keep original"), _("do not remove the original file, after processing it"), FALSE, 0, NULL, NULL); hbox = e2_widget_add_box (dialog_vbox, FALSE, 0, FALSE, TRUE, E2_PADDING_SMALL); crt.linktarget_btn = e2_button_add_toggle (hbox, TRUE, options->walklinks, _("through links"), _("if file is a symlink, process its target"), FALSE, 0, NULL, NULL); crt.recurse_btn = e2_button_add_toggle (hbox, TRUE, options->recurse, _("recurse directories"), NULL, FALSE, 0, NULL, NULL); //now it's safe to connect toggle button callbacks g_signal_connect (G_OBJECT (crt.mode_btn), "toggled", G_CALLBACK (_e2pcr_toggle_mode_cb), &crt); g_signal_connect (G_OBJECT (crt.en_name_btn_same), "toggled", G_CALLBACK (_e2pcr_toggle_encname_cb), &crt); g_signal_connect (G_OBJECT (crt.en_name_btn_suffix), "toggled", G_CALLBACK (_e2pcr_toggle_encname_cb), &crt); g_signal_connect (G_OBJECT (crt.en_name_btn_custom), "toggled", G_CALLBACK (_e2pcr_toggle_encname_cb), &crt); g_signal_connect (G_OBJECT (crt.de_name_btn_same), "toggled", G_CALLBACK (_e2pcr_toggle_decname_cb), &crt); g_signal_connect (G_OBJECT (crt.de_name_btn_suffix), "toggled", G_CALLBACK (_e2pcr_toggle_decname_cb), &crt); g_signal_connect (G_OBJECT (crt.de_name_btn_custom), "toggled", G_CALLBACK (_e2pcr_toggle_decname_cb), &crt); g_signal_connect (G_OBJECT (crt.de_name_btn_stored), "toggled", G_CALLBACK (_e2pcr_toggle_decname_cb), &crt); if (options->decryptmode) { gtk_widget_show (crt.decryptbox); gtk_widget_hide (crt.confirmbox); gtk_widget_hide (crt.compress_btn); gtk_widget_hide (crt.en_properties_embed_btn); } else { gtk_widget_show (crt.encryptbox); gtk_widget_hide (crt.properties_btn); } if (options->multisrc) { e2_dialog_add_defined_button (crt.dialog, &E2_BUTTON_NOTOALL); e2_dialog_add_defined_button (crt.dialog, &E2_BUTTON_YESTOALL); } // E2_BUTTON_CANCEL.showflags |= E2_BTN_DEFAULT; E2_BUTTON_OK.showflags &= ~E2_BTN_DEFAULT; e2_dialog_add_defined_button (crt.dialog, &E2_BUTTON_CANCEL); e2_dialog_add_defined_button (crt.dialog, &E2_BUTTON_OK); _e2pcr_set_buttons (&crt); crt.pwrt->confirm = !options->decryptmode; //make the password dialog check/not for matches crt.dlgopen = TRUE; e2_dialog_setup (crt.dialog, app.main_window); //gboolean QQQQcheckmutexok2; gdk_threads_leave(); // pthread_mutex_lock (&pw_mutex); /* if (window_width == -1) window_width = MIN(400, app.window.panes_paned->allocation.width*2/3); gtk_window_resize (GTK_WINDOW(rt->dialog), window_width, -1); */ // g_static_rec_mutex_unlock (&pw_mutex); // pthread_mutex_unlock (&pw_mutex); gdk_threads_enter (); e2_dialog_run (crt.dialog, app.main_window, E2_DIALOG_DONT_SHOW_ALL); gtk_widget_grab_focus (crt.pwrt->pwentry1); gtk_main (); // gtk_widget_hide (rt->dialog); CRASHER gdk_threads_leave (); // gdk_threads_enter (); // gtk_widget_hide (rt->dialog); // gdk_threads_leave (); DialogButtons retval = crt.result; //remember most of the current dialog data options->decryptmode = !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.mode_btn)); options->en_name_same = //encrypted file name = same as onencrypted name gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.en_name_btn_same)); options->en_name_suffix = //encrypted file name has user-specified suffix (if any) gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.en_name_btn_suffix)); options->en_name_custom = //encrypted file name = user-specified gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.en_name_btn_custom)); // options->en_name_embed = //store filenama in encrypted file // gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.en_name_embed_btn)); options->en_properties_embed = //store filenama and statbuf in encrypted file gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.en_properties_embed_btn)); options->de_name_same = //decrypted file name = same as encrypted name gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.de_name_btn_same)); options->de_name_stored = //decrypted file name = embedded original name gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.de_name_btn_stored)); options->de_name_suffix = //decrypted file name omits user-specified suffix (if any) gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.de_name_btn_suffix)); options->de_name_custom = //decrypted file name = user-specified gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.de_name_btn_custom)); options->de_props_stored = //reinstate other properties of original file gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.properties_btn)); options->compress = //compress file before encryption gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.compress_btn)); options->backup = //preserve any file with same name as specified for the [de]crypted file gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.backup_btn)); options->preserve = //preserve the file to be [de]crypted, with alternate name if appropriate gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.preserve_btn)); options->recurse = //recursively process all files in any selected dir gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.recurse_btn)); options->walklinks =//process link targets gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.linktarget_btn)); if (options->en_name != NULL) g_free (options->en_name); options->en_name = g_strdup (gtk_entry_get_text (GTK_ENTRY (crt.en_name_custom_entry))); //user-specified suffix, freeable utf-8 if (options->en_suffix != NULL) g_free (options->en_suffix); options->en_suffix = g_strdup (gtk_entry_get_text (GTK_ENTRY (crt.en_name_suffix_entry))); //user-specified suffix, freeable utf-8 if (options->de_name != NULL) g_free (options->de_name); options->de_name = g_strdup (gtk_entry_get_text (GTK_ENTRY (crt.de_name_custom_entry))); //user-specified suffix, freeable utf-8 if (options->de_suffix != NULL) g_free (options->de_suffix); options->de_suffix = g_strdup (gtk_entry_get_text (GTK_ENTRY (crt.de_name_suffix_entry))); //user-specified suffix, freeable utf-8 //remember password only if a single-item is to be processed, //and permission is ok and password is not empty if (retval == OK || retval == YES_TO_ALL || (retval == CANCEL && options->multisrc)) { pw = *(crt.pwrt->passwd); if (pw == NULL || *pw == '\0') retval = CANCEL; else { if (retval != CANCEL) { _e2pcr_check_permission (&crt); if (!options->permission) retval = CANCEL; } if (options->plain_pw != NULL) g_free (options->plain_pw); options->plain_pw = pw; } } if (!(retval == OK || retval == YES_TO_ALL || (retval == CANCEL && options->multisrc))) { if (options->plain_pw != NULL) { g_free (options->plain_pw); options->plain_pw = NULL; } } F_FREE (utf); g_free (name); gdk_threads_enter (); gtk_widget_destroy (crt.dialog); gdk_threads_leave (); //do not free p/w string - it's saved at options->plain_pw DEALLOCATE (E2_PWDataRuntime, crt.pwrt); return retval; } /** @brief encryt or decrypt selected item(s) in active pane If > 1 item is selected, a dialog is created for each such item in turn (or until the user chooses stop or apply-to-all) @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if action completed successfully, else FALSE */ static gboolean _e2p_task_docrypt (gpointer from, E2_ActionRuntime *art) { return (e2_task_enqueue_task (E2_TASK_CRYPT, art, from, _e2p_task_docryptQ, e2_task_refresh_lists)); } static gboolean _e2p_task_docryptQ (E2_ActionTaskData *qed) { //printd (DEBUG, "task: crypt"); #ifdef E2_VFSTMP if (qed->currspace != NULL) //not a local space return FALSE; #endif struct stat sb; //statbuf for general use E2P_CryptOpts options = session_opts; options.permission = FALSE; options.ignore_suffix = FALSE; options.owrite = FALSE; options.en_suffix = g_strdup (session_opts.en_suffix); options.de_suffix = g_strdup (session_opts.de_suffix); options.plain_pw = NULL; //probably redundant options.statptr = &sb; options.dirdata = NULL; #ifdef E2_VFS options.spacedata = qed->currspace; #endif GPtrArray *names = qed->names; options.multisrc = names->len > 1; gchar *curr_local = qed->currdir; guint count; gboolean all = FALSE; E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata; //guess the initial mode //too bad if 1st item is a dir with the expected suffix gchar *utf = F_FILENAME_TO_LOCALE ((*iterator)->filename); options.decryptmode = (*session_opts.de_suffix != '\0' && g_str_has_suffix (utf, session_opts.de_suffix)); F_FREE (utf); GString *path = g_string_sized_new (PATH_MAX); #ifdef E2_REFRESH_DEBUG printd (DEBUG, "disable refresh, crypt task"); #endif e2_filelist_disable_refresh (); e2_task_advise (); #ifdef E2_VFS VPATH ddata; ddata.spacedata = qed->currspace; #endif for (count = 0; count < names->len; count++, iterator++) { DialogButtons choice; //".." entries filtered when names compiled //FIXME for single-setup: instead of the following, adjust file details in dialog, reset default button // gchar *itempath = e2_utils_dircat (curr_view, (*iterator)->filename, TRUE); g_string_printf (path, "%s%s", curr_local, (*iterator)->filename); //separator comes with dir options.localpath = path->str; if (all) { //check if we have permission to change this item E2P_CryptDlgRuntime crt; crt.opts = &options; crt.dlgopen = FALSE; if (_e2pcr_check_permission (&crt)) choice = OK; else { #ifdef E2_VFS ddata.localpath = (*iterator)->filename; #endif e2_fs_error_simple ( _("You do not have authority to modify %s"), #ifdef E2_VFS &ddata); #else (*iterator)->filename); #endif choice = CANCEL; } } else { #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, encrypt dialog"); #endif e2_filelist_enable_refresh (); //allow updates while we wait *qed->status = E2_TASK_PAUSED; choice = _e2pcr_crypt_dialog_run (&options); *qed->status = E2_TASK_RUNNING; #ifdef E2_REFRESH_DEBUG printd (DEBUG, "disable refresh, encrypt dialog"); #endif e2_filelist_disable_refresh (); } switch (choice) { case YES_TO_ALL: all = TRUE; // myuid = getuid (); //do this once, for speed choice = OK; case OK: if (options.permission) // && (axs_changes != NULL || def_changes != NULL)) { #ifdef E2_INCLIST if ((choice = _e2pcr_apply (&options)) == OK) { //FIXME update line in treeview } #else # ifdef E2_FAM choice = _e2pcr_apply (&options); # else if ((choice = _e2pcr_apply (&options)) == OK) # ifdef E2_VFS { if (ddata.spacedata == NULL) { ddata.localpath = qed->currdir; e2_fs_touchnow (&ddata E2_ERR_NONE()); } } # else //make the file-list refresher notice successful change e2_fs_touchnow (qed->currdir E2_ERR_NONE()); # endif # endif #endif /* if (!all) { CLEANUPS } */ } case CANCEL: break; default: choice = NO_TO_ALL; // break flag; break; } if (choice == NO_TO_ALL) break; } //backup relevant last-used option data g_free (session_opts.en_suffix); g_free (session_opts.de_suffix); if (options.en_name != NULL) { g_free (options.en_name); options.en_name = NULL; } if (options.de_name != NULL) { g_free (options.de_name); options.de_name = NULL; } if (options.plain_pw != NULL) { g_free (options.plain_pw); options.plain_pw = NULL; } session_opts = options; g_string_free (path, TRUE); e2_window_clear_status_message (); #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, acl task"); #endif e2_filelist_enable_refresh (); return TRUE; } //aname must be confined to this module static gchar *aname; static gpointer libhandle; /** @brief plugin initialization function, called by main program @param p ptr to plugin data struct @return TRUE if the initialization succeeds, else FALSE */ gboolean init_plugin (Plugin *p) { #define ANAME "crypt" aname = _("crypt"); p->signature = ANAME VERSION; p->menu_name = _("_En/decrypt.."); p->description = _("Encrypt or decrypt selected items"); p->icon = "plugin_"ANAME"_"E2IP".png"; //use icon file pathname if appropriate if (p->action == NULL) { //no need to free this gchar *action_name = g_strconcat (_A(5),".",aname,NULL); p->action = e2_plugins_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2p_task_docrypt, NULL, FALSE, 0, NULL); session_opts.en_suffix = g_strdup (".enc"); //no translation session_opts.de_suffix = g_strdup (".enc"); //no translation //setup for default compression (and incidentally, decompression) //first try, fastest performer, lzo if ((libhandle = dlopen ("liblzo2.so.2", RTLD_LAZY)) != NULL) { init_compress = dlsym (libhandle, "__lzo_init_v2"); //a #define in lzoconf.h if (init_compress != NULL) { compress_buf = dlsym (libhandle, "lzo1x_1_compress"); if (compress_buf != NULL) { decompress_buf = dlsym (libhandle, "lzo1x_decompress_safe"); if (decompress_buf != NULL) compresslib = E2_CFLAGCOMPRESS | E2_CFLAGLZO; else { init_compress = NULL; compress_buf = NULL; } } else init_compress = NULL; } if (compresslib == E2_CFLAGNONE) dlclose (libhandle); } //second, intermediate speed, libz if (compresslib == E2_CFLAGNONE && (libhandle = dlopen ("libz.so.1", RTLD_LAZY)) != NULL) { compress_buf = dlsym (libhandle, "compress2"); if (compress_buf != NULL) { decompress_buf = dlsym (libhandle, "uncompress"); if (decompress_buf != NULL) compresslib = E2_CFLAGCOMPRESS | E2_CFLAGZ; else compress_buf = NULL; } if (compresslib == E2_CFLAGNONE) dlclose (libhandle); } //last, libbz2 if (compresslib == E2_CFLAGNONE && (libhandle = dlopen ("libbz2.so.1", RTLD_LAZY)) != NULL) { compress_buf = dlsym (libhandle, "BZ2_bzBuffToBuffCompress"); if (compress_buf != NULL) { decompress_buf = dlsym (libhandle, "BZ2_bzBuffToBuffDecompress"); if (decompress_buf != NULL) compresslib = E2_CFLAGCOMPRESS | E2_CFLAGBZ2; else compress_buf = NULL; } if (compresslib == E2_CFLAGNONE) { dlclose (libhandle); libhandle = NULL; //don't do anything when plugin closes } } //no flags set in compresslib if nothing found printd (DEBUG, "masked lib-flags %0x", compresslib & E2_CFLAGLIBMASK); return TRUE; } return FALSE; } /** @brief cleanup transient things for this plugin @param p pointer to data struct for the plugin @return TRUE if all cleanups were completed */ gboolean clean_plugin (Plugin *p) { gchar *action_name = g_strconcat (_A(5),".",aname,NULL); gboolean ret = e2_plugins_action_unregister (action_name); g_free (action_name); if (ret) { g_free (session_opts.en_suffix); g_free (session_opts.de_suffix); if (libhandle != NULL) //disconnect from the compression library dlclose (libhandle); } return ret; } emelfm2-0.4.1/plugins/e2p_upgrade.c0000600000175000017500000007663710776374631016072 0ustar cairocairo/* $Id: e2p_upgrade.c 853 2008-04-07 10:38:17Z tpgww $ Copyright (C) 2005-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file plugins/e2p_upgrade.c @brief plugin for updating config files when a new emelFM2 version so requires This file contains functions that help upgrading the default configuration data file to the current version. Note that upgrading may also be needed for imported config data - refer to the config plugin */ #include "emelfm2.h" #include #include "e2_plugins.h" #include "e2_option.h" #include "e2_output.h" #include "e2_dialog.h" #include "e2_task.h" #include "e2_complete.h" static gboolean cancelled = FALSE; static gchar *default_msg = N_("Configuration arrangements for this version %s of %s are considerably " "different from those of old versions. To reliably ensure access to the " "program's current features, it is best to start with fresh settings.\n" "If you proceed, the superseded configuration files in\n %s will have '.save' " "appended to their names.\nFeel free to delete them." ); #if 0 static gchar *option_msg = N_("Several default configuration settings of this version %s of %s" " are different from those of recent versions (see changelog).\n" "If you click OK, those settings will be updated where possible.\n" "Or else you can Cancel, and later, via the configuration dialog, manually" "change individual settings, or change all settings to current defaults." ); #endif //0 static void _e2p_upgrade_reconfig (void) { //prevent attempts to clean non-existent backup data guint i; gpointer *walker; E2_OptionSet *set; for (i = 0, walker = options_array->pdata; i < options_array->len; i++, walker++) { set = *walker; if (set->type == E2_OPTION_TYPE_TREE) set->ex.tree.def = NULL; } //clear current option values e2_option_clear_data (); e2_option_default_register (); e2_option_file_read (); } //CHECKME assumes BGL is closed, native file only static void _e2p_upgrade_backup (gchar *file) { gchar *config_file = g_strdup_printf ("%s"G_DIR_SEPARATOR_S"%s", e2_cl_options.config_dir, file); gchar *local = F_FILENAME_TO_LOCALE (config_file); #ifdef E2_VFS VPATH ddata = { local, NULL }; if (e2_fs_access (&ddata, F_OK E2_ERR_NONE()) == 0) //traverse link, if any #else if (e2_fs_access (local, F_OK E2_ERR_NONE()) == 0) //traverse link, if any #endif { gchar *saved_file = g_strconcat (local, ".save", NULL); gdk_threads_leave (); //downstream errors invoke local mutex locking #ifdef E2_VFS VPATH sdata = { saved_file, NULL }; e2_task_backend_rename (&ddata, &sdata); #else e2_task_backend_rename (local, saved_file); #endif gdk_threads_enter (); g_free (saved_file); } g_free (config_file); F_FREE (local); } static gint _e2p_upgrade_dialog (gchar *msg) { //main button structs not created yet E2_Button E2P_BUTTON_OK = { N_("_OK"),"", #ifdef E2_IMAGECACHE //FIXME gtk images not yet available NULL, #else GTK_STOCK_OK, #endif NULL, E2_BTN_DEFAULT, E2_BTN_DEFAULT, GTK_RESPONSE_OK}; E2_Button E2P_BUTTON_CANCEL = { N_("_Cancel"),"", #ifdef E2_IMAGECACHE //FIXME gtk images not yet available NULL, #else GTK_STOCK_CANCEL, #endif NULL, E2_BTN_DEFAULT, E2_BTN_DEFAULT, GTK_RESPONSE_CANCEL}; GtkWidget *dialog = e2_dialog_create ( #ifdef E2_IMAGECACHE //FIXME gtk images not yet available NULL, #else GTK_STOCK_DIALOG_INFO, #endif msg, _("update information"), NULL, NULL); e2_dialog_show (dialog, NULL, 0, &E2P_BUTTON_CANCEL, &E2P_BUTTON_OK, NULL); gint choice = gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); cancelled = (choice != GTK_RESPONSE_OK); return choice; } //#if 0 static gchar *_e2p_upgrade_get_sed (void) { gchar *sed = g_find_program_in_path ("sed"); if (sed == NULL) { //FIXME should always warn the user about this printd (ERROR, "can't find 'sed' so i can't upgrade the config file"); cancelled = TRUE; } return sed; } //#endif //0 #ifdef E2_VERSIONDOCS static void _e2p_upgrade_numbers (void) { gchar *sed = _e2p_upgrade_get_sed (); if (sed != NULL) { gchar *cfg_file = g_build_filename (e2_cl_options.config_dir, default_config_file, NULL); gchar *local = F_FILENAME_TO_LOCALE (cfg_file); gchar *localtmp = e2_utils_get_tempname (local); gchar *command = g_strconcat ("cp -f ", local, " ", localtmp, ".save;", sed, " -e '1s/", app.cfgfile_version, "/"VERSION RELEASE"/'", " -e '2,$s/0\\.[0-9]\\.[0-9]/"VERSION"/'", " ",localtmp,".save >",local,NULL); system (command); g_free (sed); g_free (cfg_file); F_FREE (local); g_free (localtmp); g_free (command); } } #endif static void _e2p_upgrade_pre_0_1 (void) { gchar *msg = g_strdup_printf (gettext (default_msg), VERSION, PROGNAME, e2_cl_options.config_dir); gint choice = _e2p_upgrade_dialog (msg); g_free (msg); if (choice == GTK_RESPONSE_OK) { _e2p_upgrade_backup ("config"); // _e2p_upgrade_backup ("cache"); this one is ok to leave as is _e2p_upgrade_backup ("filetypes"); _e2p_upgrade_backup ("plugins"); _e2p_upgrade_backup ("settings"); e2_option_clear_data (); e2_option_default_register (); } else exit (1); } /* static void _e2p_upgrade_0_1 (void) { gchar *sed = _e2p_upgrade_get_sed (); if (sed != NULL) { //pseudo action to rename gchar *oldstr1 = _(""); gchar *newstr1 = g_strconcat (_A(5),".",_A(20),NULL); //keybinding to add gchar *oldstr2 = g_strconcat ("\t\t|",_("Delete"),"|false|",_A(5),".",_A(38),"|",NULL); //"delete" gchar *newstr2 = g_strconcat ("\t\t|",_("Delete"),"|false|",_A(1),".",_A(98),"|",NULL); //"trashempty" //filetype string to revise gchar *oldstr3 = g_strconcat ("<", _A(13), ".", _A(90),";<", _A(13), ".", _A(72), ";>bzip2 -d -c %F [\\]| tar xf -;<", _A(13), ".", _A(71), ";<", _A(13), ".", _A(90),NULL); gchar *newstr3 = g_strconcat (_A(13), ".", _A(72), ";>bzip2 -d -c %f ","\\\\","| tar -C %D -xf -;<", _A(13), ".", _A(71), NULL); gchar *cfg_file = g_build_filename (e2_cl_options.config_dir, default_config_file, NULL); gchar *command = g_strconcat ("cp -f ", cfg_file, " ", cfg_file, ".save;", sed, #ifndef E2_VERSIONDOCS " -e '1s/", app.cfgfile_version, "/", VERSION RELEASE, "/'", #endif //separate func for this " -e '2,$s/0\\.0\\.[0-9]/"VERSION"/'", " -e 's/",oldstr1,"/",newstr1,"/'", " -e '/",oldstr2,"$/a\\\n",newstr2,"'", " -e 's/",oldstr3,"/",newstr3,"/' ", cfg_file,".save >",cfg_file,NULL); // printd (DEBUG, command); // e2_command_run (command, E2_COMMAND_RANGE_DEFAULT); system (command); g_free (command); g_free (cfg_file); g_free (newstr1); g_free (newstr2); g_free (oldstr2); g_free (newstr3); g_free (oldstr3); g_free (sed); } } static void _e2p_upgrade_0_1_1 (void) { gchar *sed = _e2p_upgrade_get_sed (); if (sed != NULL) { //keybinding to replace //NOTE needs |false|, not || //dir line gchar *oldstr1 = g_strconcat("\t\t|",_("Insert"),"|false|",_A(1),".",_A(29),"|",NULL); //"clear" gchar *newstr1 = g_strconcat("\t\t|",_("Delete"),"|false|",_A(1),".",_A(29),"|", //"clear" "\\\n\t\t|",_("Delete"),"|false|",_A(1),".",_A(30),"|",NULL); //clear-history //keybindings to add //general gchar *oldstr2 = g_strconcat("\t|r","|false|",_A(13),".",_A(70),"|",NULL); //"refresh" gchar *newstr2 = g_strconcat ("\t|1","|false|",_A(4),".",_A(43),"|1\\\n""\t|2","|false|",_A(4),".",_A(43),"|2",NULL); //command line gchar *oldstr3 = g_strconcat("\t\t|",_("Delete"),"|false|",_A(1),".",_A(29),"|",NULL); //clear gchar *newstr3 = g_strconcat("\t\t|",_("Delete"),"|false|",_A(1),".",_A(30),"|",NULL); //clear-history //helpdoc locations maybe version-specific //e.g. usage-help-doc=/usr/share/doc/emelfm2-0.1.2/USAGE gchar *oldstr4 = g_strconcat ("\\(usage-help-doc=.*-\\)",app.cfgfile_version,"\\(\\/.*\\)$", NULL); gchar *newstr4 = g_strconcat ("\\","1",VERSION,"\\","2",NULL); //config-help-doc=/usr/share/doc/emelfm2-0.1.2/CONFIGURATION gchar *oldstr5 = g_strconcat ("\\(config-help-doc=.*-\\)",app.cfgfile_version,"\\(\\/.*\\)$", NULL); //commandbar button to add (NOTE no / in match-item path, sed hates it) gchar *oldstr6 = g_strconcat (_("su.."),"|.*.png|",_("Run something as root"),"|",_A(17),"|xterm -e 'su -c \"%{.*read'",NULL); gchar *newstr6 = g_strconcat (_("mts.."),"|mounts_"E2IP".png|",_("Mount or unmount a device"),"|",_A(57),".",_A(24),"|",_C(4), NULL); gchar *cfg_file = g_build_filename (e2_cl_options.config_dir, default_config_file, NULL); gchar *command = g_strconcat ("cp -f ", cfg_file, " ", cfg_file, ".save;", sed, #ifndef E2_VERSIONDOCS " -e '1s/", app.cfgfile_version, "/", VERSION RELEASE, "/'", #endif //separate func for this " -e '2,$s/0\\.0\\.[0-9]/"VERSION"/'", " -e 's/",oldstr4,"/",newstr4,"/'", " -e 's/",oldstr5,"/",newstr4,"/'", " -e 's/",oldstr1,"/",newstr1,"/'", " -e '/",oldstr2,"$/a\\\n",newstr2,"'", " -e '/",oldstr3,"$/a\\\n",newstr3,"' ", #ifdef E2_FS_MOUNTABLE "-e '/",oldstr6,"$/a\\\n",newstr6,"' ", //~ to avoid problems with / in paths #endif cfg_file,".save >",cfg_file,NULL); // printd (DEBUG, command); // e2_command_run (command, E2_COMMAND_RANGE_DEFAULT); system (command); g_free (command); g_free (cfg_file); g_free (newstr1); g_free (oldstr1); g_free (newstr2); g_free (oldstr2); g_free (newstr3); g_free (oldstr3); g_free (newstr4); g_free (oldstr4); g_free (oldstr5); #ifdef E2_FS_MOUNTABLE g_free (newstr6); g_free (oldstr6); #endif g_free (sed); } } static void _e2p_upgrade_0_1_2 (void) { gchar *sed = _e2p_upgrade_get_sed (); if (sed != NULL) { //get rid of icon file paths //keybinding to add gchar *oldstr2 = g_strconcat ("\t\t|a","|false|",_A(10),".",_A(95),"|",NULL); gchar *newstr2 = g_strconcat("\t\t|i","||",_A(10),".",_A(54),"|",NULL); gchar *cfg_file = g_build_filename (e2_cl_options.config_dir, default_config_file, NULL); gchar *command = g_strconcat ("cp -f ", cfg_file, " ", cfg_file, ".save;", #ifndef E2_VERSIONDOCS " -e '1s/", app.cfgfile_version, "/", VERSION, "/'", #endif //separate func for this " -e '2,$s/0\\.[0-1]\\.[0-9]/"VERSION"/'", " -e 's~"ICON_DIR"~~'", " -e '/",oldstr2,"$/a\\\n",newstr2,"' ", cfg_file,".save >",cfg_file,NULL); system (command); g_free (command); g_free (newstr2); g_free (oldstr2); g_free (cfg_file); g_free (sed); } } static void _e2p_upgrade_0_1_3 (void) { //one dialog only ! gchar *msg = g_strdup_printf (gettext (option_msg), VERSION, PROGNAME); gint choice = _e2p_upgrade_dialog (msg); g_free (msg); if (choice == GTK_RESPONSE_OK) { gchar *sed = _e2p_upgrade_get_sed (); if (sed != NULL) { //keybinding to add gchar *oldstr1 = g_strconcat ("\t",_C(32),"||false|",_A(17),"|",NULL); gchar *newstr1 = g_strconcat ("\t\t|space"),"|false|",_A(10),".",_A(96),"|",NULL); //actions to rename gchar *oldstr2 = g_strconcat (_A(13),".",_A(45), NULL); gchar *newstr2 = g_strconcat (_A(10),".",_A(45), NULL); gchar *oldstr3 = g_strconcat (_A(13),".",_A(46),NULL); gchar *newstr3 = g_strconcat (_A(10),".",_A(46),NULL); gchar *oldstr4 = g_strconcat (_A(13),".",_A(54),NULL); gchar *newstr4 = g_strconcat (_A(10),".",_A(54),NULL); gchar *oldstr5 = g_strconcat (_A(13),".",_A(81),NULL); gchar *newstr5 = g_strconcat (_A(10),".",_A(81),NULL); gchar *oldstr6 = g_strconcat (_A(13),".",_A(90),NULL); gchar *newstr6 = g_strconcat (_A(10),".",_A(90),NULL); gchar *oldstr7 = g_strconcat (_A(13),".",_A(80),NULL); gchar *newstr7 = g_strconcat (_A(10),".",_A(80),NULL); gchar *oldstr8 = g_strconcat (_A(13),".",_A(95),NULL); gchar *newstr8 = g_strconcat (_A(10),".",_A(95),NULL); gchar *cfg_file = g_build_filename (e2_cl_options.config_dir, default_config_file, NULL); gchar *command = g_strconcat ("cp -f ", cfg_file, " ", cfg_file, ".save;", sed, #ifndef E2_VERSIONDOCS " -e '1s/", app.cfgfile_version, "/", VERSION RELEASE, "/'", #endif //separate func for this " -e '2,$s/0\\.1\\.[0-9]/"VERSION"/'", " -e '/",oldstr1,"$/a\\\n",newstr1,"'", " -e 's/",oldstr2,"/",newstr2,"/'", " -e 's/",oldstr3,"/",newstr3,"/'", " -e 's/",oldstr4,"/",newstr4,"/'", " -e 's/",oldstr5,"/",newstr5,"/'", " -e 's/",oldstr6,"/",newstr6,"/g'", //this may have >1 per line " -e 's/",oldstr7,"/",newstr7,"/'", " -e 's/",oldstr8,"/",newstr8,"/' ", cfg_file,".save >",cfg_file,NULL); system (command); g_free (command); g_free (newstr1); g_free (oldstr1); g_free (newstr2); g_free (oldstr2); g_free (newstr3); g_free (oldstr3); g_free (newstr4); g_free (oldstr4); g_free (newstr5); g_free (oldstr5); g_free (newstr6); g_free (oldstr6); g_free (newstr7); g_free (oldstr7); g_free (newstr8); g_free (oldstr8); g_free (cfg_file); g_free (sed); } } } */ static void _e2p_upgrade_0_1_5 (void) { //one dialog only ! gchar *msg = g_strdup_printf (gettext (default_msg), VERSION, PROGNAME, e2_cl_options.config_dir); gint choice = _e2p_upgrade_dialog (msg); g_free (msg); if (choice == GTK_RESPONSE_OK) _e2p_upgrade_backup ("config"); } /* static void _e2p_upgrade_0_1_6 (void) { //one dialog only ! gchar *msg = g_strdup_printf (gettext (option_msg), VERSION, PROGNAME); gint choice = _e2p_upgrade_dialog (msg); g_free (msg); if (choice == GTK_RESPONSE_OK) { gchar *sed = _e2p_upgrade_get_sed (); if (sed != NULL) { //keybindings to add gchar *oldstr1 = g_strconcat("\t|F3|false|",_A(5),".",_A(101),"|",NULL); gchar *newstr1 = g_strconcat("\t|F3||",_A(5),".",_A(102),"|",NULL); gchar *oldstr2 = g_strconcat("\t|F4|false|",_A(5),".",_A(39),"|",NULL); gchar *newstr2 = g_strconcat("\t|F4||",_A(5),".",_A(40),"|",NULL); gchar *oldstr3 = g_strconcat("\t|F5|false|",_A(5),".",_A(33),"|",NULL); gchar *newstr3 = g_strconcat("\t|F5||",_A(5),".",_A(34),"|",NULL); gchar *oldstr4 = g_strconcat("\t|F6|false|",_A(5),".",_A(58),"|",NULL); gchar *newstr4 = g_strconcat("\t|F6||",_A(5),".",_A(59),"|",NULL); //keybindings to change gchar *oldstr5 = g_strconcat("\t\t|Up|false|",_A(17),NULL); //FIXME //_A(17) gchar *newstr5 = g_strconcat("\t\t|Up||",_A(17),NULL); gchar *oldstr6 = g_strconcat("\t|space||",_A(9),".",_A(68),NULL); gchar *newstr6 = g_strconcat("\t|g","||",_A(9),".",_A(68),NULL); gchar *oldstr7 = g_strconcat("\t\t|space|false|",_A(10),".",_A(96),NULL); gchar *newstr7 = g_strconcat("\t\t|space||",_A(10),".",_A(96),NULL); //action parameter to kill gchar *oldstr8 = _("expand"); gchar *cfg_file = g_build_filename (e2_cl_options.config_dir, default_config_file, NULL); gchar *command = g_strconcat ("cp -f ", cfg_file, " ", cfg_file, ".save;", sed, #ifndef E2_VERSIONDOCS " -e '1s/", app.cfgfile_version, "/", VERSION RELEASE, "/'", #endif //separate func for this " -e '2,$s/0\\.1\\.[0-9]/"VERSION"/'", " -e '/",oldstr1,"$/a\\\n",newstr1,"'", " -e '/",oldstr2,"$/a\\\n",newstr2,"'", " -e '/",oldstr3,"$/a\\\n",newstr3,"'", " -e '/",oldstr4,"$/a\\\n",newstr4,"'", " -e 's/",oldstr5,"/",newstr5,"/'", " -e 's/",oldstr6,"/",newstr6,"/'", " -e 's/",oldstr7,"/",newstr7,"/'", */ // " -e 's/",oldstr8,"/*/' ", /* cfg_file,".save >",cfg_file,NULL); system (command); g_free (command); g_free (oldstr1); g_free (newstr1); g_free (oldstr2); g_free (newstr2); g_free (oldstr3); g_free (newstr3); g_free (oldstr4); g_free (newstr4); g_free (oldstr5); g_free (newstr5); g_free (oldstr6); g_free (newstr6); g_free (oldstr7); g_free (newstr7); g_free (sed); } } } static void _e2p_upgrade_0_1_6_2 (void) { gchar *sed = _e2p_upgrade_get_sed (); if (sed != NULL) { gchar *cfg_file = g_build_filename (e2_cl_options.config_dir, default_config_file, NULL); //keybindings to add gchar *oldstr1 = g_strconcat("\t\t|h","|false|",_A(10),".",_A(80),"|",NULL); gchar *newstr1 = g_strconcat("\t\t|m","|false|",_A(10),".",_A(21),"|",NULL); gchar *oldstr2 = g_strconcat("\t",_C(5),"||false||",NULL); gchar *newstr2 = g_strconcat("\t\t|Return||",_A(1),".",_A(77),"|",NULL); gchar *command = g_strconcat ("cp -f ", cfg_file, " ", cfg_file, ".save;", sed, #ifndef E2_VERSIONDOCS " -e '1s/", app.cfgfile_version, "/", VERSION RELEASE, "/'", #endif //separate func for this " -e '2,$s/0\\.1\\.[0-9]/"VERSION"/'", " -e '/",oldstr1,"$/a\\\n",newstr1,"'", " -e '/",oldstr2,"$/a\\\n",newstr2,"'", //re-configure all tree-option name lines " -e 's/^<\\(.*\\)/\\1=",cfg_file,NULL); system (command); g_free (command); g_free (oldstr1); g_free (newstr1); g_free (oldstr2); g_free (newstr2); g_free (sed); } else _e2p_upgrade_0_1_5 (); //suggest default } static void _e2p_upgrade_0_1_6_3 (void) { gchar *sed = _e2p_upgrade_get_sed (); if (sed != NULL) { gchar *cfg_file = g_build_filename (e2_cl_options.config_dir, default_config_file, NULL); gchar *local = F_FILENAME_TO_LOCALE (cfg_file); //keybindings to add gchar *oldstr1 = g_strconcat("\t\t|m","|false|",_A(10),".",_A(21),"|",NULL); gchar *newstr1 = g_strconcat("\t\t|t","||",_A(10),".",_A(22),"|",NULL); gchar *oldstr9 = g_strconcat("\t|F5|false|",_A(5),".",_A(33),"|",NULL); gchar *newstr9 = g_strconcat("\t|F5||",_A(5),".",_A(36),"|",NULL); //context menu items to add gchar *oldstr10 = g_strconcat("\t",_("_Free space"),"||false|true|>stat",NULL); gchar *newstr10 = g_strconcat("\t||false|true|",_A(18),"|",NULL); gchar *newstr11 = g_strconcat("\t",_("_Edit user commands.."),"|gtk-preferences|false|true|",_A(2),".",_A(32),"|",_C(8),NULL); //toggle-action reformatting gchar *oldstr2 = g_strconcat("\\([^|]*",_A(108),"\\)|true",NULL); gchar *newstr2 = g_strconcat(_A(15),".",_A(111),"|\\1",NULL); gchar *oldstr3 = g_strconcat("\\([^|]*",_A(80),"\\)|true",NULL); gchar *newstr3 = g_strconcat(_A(15),".",_A(111),"|\\1",NULL); gchar *oldstr4 = g_strconcat("\\([^|]*",_A(22),"\\)|true",NULL); gchar *newstr4 = g_strconcat(_A(15),".",_A(111),"|\\1",NULL); gchar *oldstr5 = g_strconcat("\\([^|]*",_A(108),"\\)|false",NULL); gchar *newstr5 = g_strconcat(_A(15),".",_A(110),"|\\1",NULL); gchar *oldstr6 = g_strconcat("\\([^|]*",_A(80),"\\)|false",NULL); gchar *newstr6 = g_strconcat(_A(15),".",_A(110),"|\\1",NULL); gchar *oldstr7 = g_strconcat("\\([^|]*",_A(22),"\\)|false",NULL); gchar *newstr7 = g_strconcat(_A(15),".",_A(110),"|\\1",NULL); gchar *oldstr8 = g_strconcat (_A(9),".",_A(80),NULL); gchar *newstr8 = g_strconcat (_A(9),".",_A(79),NULL); gchar *command = g_strconcat ( "cp -f ", local, " ", local, ".save;", sed, #ifndef E2_VERSIONDOCS " -e '1s/", app.cfgfile_version, "/", VERSION RELEASE, "/'", #endif //separate func for this " -e '2,$s/0\\.1\\.[0-9]/"VERSION"/'", " -e '/",oldstr1,"$/a\\\n",newstr1,"'", " -e '/",oldstr9,"$/a\\\n",newstr9,"'", " -e '/",oldstr10,".*$/a\\\n",newstr10,"\\\n",newstr11,"'", //renamed actions FIXME later - remove specific _() so is not translated " -e 's/",_(""),"/",_A(22),"/'", " -e 's/",_("toggle_full"),"/",_A(108),"/'", " -e 's/",_("toggle_hidden"),"/",_A(80),"/'", " -e 's/",oldstr2,"/",newstr2,"/'", " -e 's/",oldstr3,"/",newstr3,"/'", " -e 's/",oldstr4,"/",newstr4,"/'", " -e 's/",oldstr5,"/",newstr5,"/'", " -e 's/",oldstr6,"/",newstr6,"/'", " -e 's/",oldstr7,"/",newstr7,"/'", " -e 's/",oldstr8,"/",newstr8,"/'", " -e 's/^\\(\t*\\)[\\]//'", " -e 's///' ", local,".save >",local,NULL); system (command); g_free (cfg_file); F_FREE (local); g_free (command); g_free (oldstr1); g_free (newstr1); g_free (oldstr2); g_free (newstr2); g_free (oldstr3); g_free (newstr3); g_free (oldstr4); g_free (newstr4); g_free (oldstr5); g_free (newstr5); g_free (oldstr6); g_free (newstr6); g_free (oldstr7); g_free (newstr7); g_free (oldstr8); g_free (newstr8); g_free (oldstr9); g_free (newstr9); g_free (oldstr10); g_free (newstr10); g_free (newstr11); g_free (sed); } else _e2p_upgrade_0_1_5 (); //suggest default } static void _e2p_upgrade_0_1_7_1 (void) { gchar *sed = _e2p_upgrade_get_sed (); if (sed != NULL) { //actions to rename gchar *oldstr1 = g_strconcat (_A(3),"\\.",_A(56), NULL); gchar *newstr1 = g_strconcat (_A(1),".",_A(56), NULL); //commandbar item re-assigned gchar *oldstr2 = g_strconcat(_("ps"),"|ps_"E2IP".png|",_("List child processes"),"|",_A(1),".",_("list_children"),"|",NULL); gchar *newstr2 = g_strconcat(_("ps"),"|ps_"E2IP".png|",_("Child processes"),"|",_A(28),".",_A(24),"|",NULL); gchar *oldstr7 = g_strconcat(_A(1),"\\.",_("list_children"),NULL); gchar *newstr7 = g_strconcat(_A(7),".",_A(28),NULL); //keybindings to add gchar *oldstr3 = g_strconcat("\t|F1|false|",_A(13),".",_A(70),"|",NULL); gchar *newstr3 = g_strconcat("\t|F1||",_A(7),".",_A(28),"|\\n" "\t|F1||",_A(7),".",_A(66),"|\\n" "\t|F1||",_A(7),".",_A(51),"|",NULL); gchar *oldstr4 = g_strconcat("\t\t|Tab|false|",_A(1),".",_A(31),"|",NULL); gchar *newstr4 = g_strconcat("\t\t|Insert||",_A(7),".",_A(28),"|",NULL); gchar *oldstr8 = g_strconcat("\t|F5|false|",_A(5),".",_A(36),"|",NULL); gchar *newstr8 = g_strconcat("\t|F5||",_A(5),".",_A(35),"|",NULL); //keybinding to delete gchar *oldstr5 = g_strconcat("\t\t|Home|false|cd|$HOME",NULL); //alias to delete gchar *oldstr6 = " *[^>].*[<>\\|].*||>"; gchar *cfg_file = g_build_filename (e2_cl_options.config_dir, default_config_file, NULL); gchar *local = D_FILENAME_TO_LOCALE (cfg_file); gchar *local2; if (e2_fs_access (local, R_OK E2_ERR_PTR())) { g_free (cfg_file); cfg_file = g_build_filename (e2_cl_options.config_dir, "config", NULL); local2 = F_FILENAME_TO_LOCALE (cfg_file); if (e2_fs_access (local2, R_OK E2_ERR_PTR())) { _e2p_upgrade_0_1_5 (); //suggest default return; } } else local2 = local; gchar *command = g_strconcat ("cp -f ", local2, " ", local2, ".save;", sed, #ifndef E2_VERSIONDOCS " -e '1s/", app.cfgfile_version, "/", VERSION RELEASE, "/'", #endif //separate func for this " -e '2,$s/0\\.1\\.[0-9]/"VERSION"/'", " -e 's/",oldstr1,"/",newstr1,"/'", " -e 's/",oldstr2,"/",newstr2,"/'", " -e 's/",oldstr7,"/",newstr7,"/'", " -e '/",oldstr3,"$/a\\\n",newstr3,"'", " -e '/",oldstr4,"$/a\\\n",newstr4,"'", " -e '/",oldstr8,"$/a\\\n",newstr8,"'", " -e '/",oldstr5,"/d'", " -e '/",oldstr6,"/d' ", local2,".save >",local,NULL); system (command); g_free (cfg_file); if (local != local2) F_FREE (local2); g_free (local); g_free (command); g_free (oldstr1); g_free (newstr1); g_free (oldstr2); g_free (newstr2); g_free (oldstr3); g_free (newstr3); g_free (oldstr4); g_free (newstr4); g_free (oldstr5); g_free (oldstr7); g_free (newstr7); g_free (oldstr8); g_free (newstr8); } else _e2p_upgrade_0_1_5 (); //suggest default } */ static void _e2p_upgrade_0_2_0 (void) { gchar *sed = _e2p_upgrade_get_sed (); if (sed != NULL) { gchar *cfg_file = g_build_filename (e2_cl_options.config_dir, default_config_file, NULL); gchar *local = F_FILENAME_TO_LOCALE (cfg_file); gchar *command = g_strconcat ("cp -f ", local, " ", local, ".save;", sed, #ifndef E2_VERSIONDOCS " -e '1s/", app.cfgfile_version, "/", VERSION RELEASE, "/'", #endif //separate func for this " -e '2,$s/0\\.1\\.[0-9]/"VERSION"/'", " -e 's~\t\t|\\(.*\\)|\\.\\/%f~\t\t|\\1|%f~' ", local,".save >",local,NULL); system (command); g_free (cfg_file); F_FREE (local); g_free (command); } else _e2p_upgrade_0_1_5 (); //suggest default } static void _e2p_upgrade_0_3_0 (void) { gchar *sed = _e2p_upgrade_get_sed (); if (sed != NULL) { //out-of-date bindings and commands gchar *oldstr1 = g_strconcat ("|",_("_Run"),"|\\./%f",NULL); gchar *newstr1 = g_strconcat ("|",_("_Run"),"|%p",NULL); gchar *oldstr4 = g_strconcat ("command-encoder=\\(.*\\)enca -L %{", _("File encoding:"),"} -x UTF-8 <%f", NULL); gchar *newstr4 = g_strconcat ("command-encoder=\\1enca -L %{(languages)@", _("File encoding:"),"} -x UTF-8 <%p", NULL); gchar *oldstr5 = g_strconcat ("%{",_("Editor command:"),"} %f", NULL); gchar *newstr5 = g_strconcat ("%{(editors)@",_("Editor command:"),"} %p", NULL); //superseded config options gchar *oldstr2 = "^fileop-background=.*$"; gchar *oldstr3 = "^address-completion=.*$"; //cache flags whose order has changed gchar *oldstr6 = "^find-plugin-flags=.*$"; //renamed option gchar *oldstr7 = "^color-drag-highlight"; gchar *newstr7 = "color-highlight"; gchar *cfg_file = g_build_filename (e2_cl_options.config_dir, default_config_file, NULL); gchar *local = F_FILENAME_TO_LOCALE (cfg_file); gchar *command = g_strconcat ("cp -f ", local, " ", local, ".save;", sed, #ifndef E2_VERSIONDOCS " -e '1s/", app.cfgfile_version, "/", VERSION RELEASE, "/'", #endif //separate func for this " -e '2,$s/0\\.2\\.[0-9]/"VERSION"/'", " -e 's~",oldstr1,"~",newstr1,"~'", //a pattern includes "/" " -e 's/",oldstr4,"/",newstr4,"/'", " -e 's/",oldstr5,"/",newstr5,"/'", " -e 's/",oldstr7,"/",newstr7,"/'", " -e '/",oldstr2,"/d'", " -e '/",oldstr3,"/d'", " ",local,".save >",local,NULL); system (command); g_free (cfg_file); F_FREE (local); g_free (command); cfg_file = g_build_filename (e2_cl_options.config_dir, "cache", NULL); //default_cache_file, NULL); local = F_FILENAME_TO_LOCALE (cfg_file); command = g_strconcat ("cp -f ", local, " ", local, ".save;", sed, " -e '/",oldstr6,"/d'", " ",local,".save >",local,NULL); system (command); g_free (oldstr1); g_free (newstr1); g_free (oldstr4); g_free (newstr4); g_free (oldstr5); g_free (newstr5); g_free (cfg_file); F_FREE (local); g_free (command); } else _e2p_upgrade_0_1_5 (); //suggest default } static void _e2p_upgrade_0_3_1 (void) { gchar *sed = _e2p_upgrade_get_sed (); if (sed != NULL) { //renamed actions gchar *oldlist = _(""); gchar *oldstr1 = g_strconcat (_A(66),".",_A(38),NULL); gchar *newstr1 = g_strconcat (_A(29),".",_A(66),NULL); gchar *oldstr2 = g_strconcat (_A(0),".",oldlist,NULL); gchar *newstr2 = g_strconcat (_A(0),".",_A(24),NULL); gchar *oldstr3 = g_strconcat (_A(28),".",oldlist,NULL); gchar *newstr3 = g_strconcat (_A(7),".",_A(28),NULL); gchar *oldstr4 = g_strconcat (_A(51),".",oldlist,NULL); gchar *newstr4 = g_strconcat (_A(7),".",_A(51),NULL); gchar *oldstr5 = g_strconcat (_A(57),".",oldlist,NULL); gchar *newstr5 = g_strconcat (_A(57),".",_A(24),NULL); gchar *oldstr6 = g_strconcat (_A(66),".",oldlist,NULL); gchar *newstr6 = g_strconcat (_A(7),".",_A(66),NULL); gchar *oldstr7 = g_strconcat (_A(14),".",oldlist,NULL); gchar *newstr7 = g_strconcat (_A(14),".",_A(24),NULL); gchar *cfg_file = g_build_filename (e2_cl_options.config_dir, default_config_file, NULL); gchar *local = F_FILENAME_TO_LOCALE (cfg_file); gchar *command = g_strconcat ("cp -f ", local, " ", local, ".save;", sed, #ifndef E2_VERSIONDOCS " -e '1s/", app.cfgfile_version, "/", VERSION RELEASE, "/'", #endif //separate func for this " -e '2,$s/0\\.[2-3]\\.[0-9]/"VERSION"/'", " -e 's/",oldstr1,"/",newstr1,"/'", " -e 's/",oldstr2,"/",newstr2,"/'", " -e 's/",oldstr3,"/",newstr3,"/'", " -e 's/",oldstr4,"/",newstr4,"/'", " -e 's/",oldstr5,"/",newstr5,"/'", " -e 's/",oldstr6,"/",newstr6,"/'", " -e 's/",oldstr7,"/",newstr7,"/'", " ",local,".save >",local,NULL); system (command); g_free (oldstr1); g_free (newstr1); g_free (oldstr2); g_free (newstr2); g_free (oldstr3); g_free (newstr3); g_free (oldstr4); g_free (newstr4); g_free (oldstr5); g_free (newstr5); g_free (oldstr6); g_free (newstr6); g_free (oldstr7); g_free (newstr7); g_free (cfg_file); F_FREE (local); g_free (command); } else _e2p_upgrade_0_1_5 (); //suggest default } #ifdef E2_TREEDIALOG static void _e2p_upgrade_0_3_3 (void) { gchar *sed = _e2p_upgrade_get_sed (); if (sed != NULL) { //keybinding to add gchar *oldstr1 = g_strconcat("\t|F9|false|",_A(5),".",_A(52),"|",NULL); gchar *newstr1 = g_strconcat("\t|F9||",_A(10),".",_A(99),"|",NULL); gchar *cfg_file = g_build_filename (e2_cl_options.config_dir, default_config_file, NULL); gchar *local = F_FILENAME_TO_LOCALE (cfg_file); gchar *command = g_strconcat ("cp -f ", local, " ", local, ".save;", sed, #ifndef E2_VERSIONDOCS " -e '1s/", app.cfgfile_version, "/", VERSION RELEASE, "/'", #endif //separate func for this " -e '2,$s/0\\.[2-3]\\.[0-9]/"VERSION"/'", " -e '/",oldstr1,"$/a\\\n",newstr1,"'", " ",local,".save >",local,NULL); system (command); g_free (oldstr1); g_free (newstr1); g_free (cfg_file); F_FREE (local); g_free (command); } else _e2p_upgrade_0_1_5 (); //suggest default } #endif static void _e2p_upgrade_0_3_6 (void) { //get rid of outdated cache flags for find plugin, which hasn't been loaded yet e2_cache_clean1 ("find-plugin-flags"); } gboolean init_plugin (Plugin *p) { #define ANAME "uprade" p->signature = ANAME VERSION; if (strcmp (app.cfgfile_version,"0.1") < 0) //this one's major - dump the lot and start afresh _e2p_upgrade_pre_0_1(); // else if (strcmp (app.cfgfile_version,"0.1.6") < 0) // else if (strcmp (app.cfgfile_version,"0.1.7") < 0) // _e2p_upgrade_0_1_5 (); else { /* //do all the recent 'little' uprades in sequence - ugly !! if (strcmp (app.cfgfile_version,"0.1.1") < 0) _e2p_upgrade_0_1 (); if (strcmp (app.cfgfile_version,"0.1.2") < 0) _e2p_upgrade_0_1_1 (); if (strcmp (app.cfgfile_version,"0.1.3") < 0) _e2p_upgrade_0_1_2 (); if (strcmp (app.cfgfile_version,"0.1.3.2") < 0) //.2 interim release _e2p_upgrade_0_1_3 (); if (strcmp (app.cfgfile_version,"0.1.5.2") < 0) _e2p_upgrade_0_1_5 (); else */ /* NO MORE DETAILED UPGRADES < 0.2.0 if (strcmp (app.cfgfile_version,"0.1.6.2") < 0) _e2p_upgrade_0_1_6_2 (); if (strcmp (app.cfgfile_version,"0.1.6.3") < 0) _e2p_upgrade_0_1_6_3 (); if (strcmp (app.cfgfile_version,"0.1.7.1") < 0) _e2p_upgrade_0_1_7_1 (); */ //NOTE check that config plugin uses this same definition #define OLDEST_UPGRADE "0.2.0" if (strcmp (app.cfgfile_version,"0.1.9") < 0) //some number < OLDEST_UPGRADE _e2p_upgrade_0_1_5 (); else { #ifdef E2_VERSIONDOCS _e2p_upgrade_numbers (); //always update docs version etc #endif if (strcmp (app.cfgfile_version,"0.2.0") < 0) _e2p_upgrade_0_2_0 (); if (strcmp (app.cfgfile_version,"0.3.0") < 0) _e2p_upgrade_0_3_0 (); if (strcmp (app.cfgfile_version,"0.3.0.1") < 0) _e2p_upgrade_0_3_1 (); #ifdef E2_TREEDIALOG if (strcmp (app.cfgfile_version,"0.3.3") < 0) _e2p_upgrade_0_3_3 (); #endif if (strcmp (app.cfgfile_version,"0.3.5.1") < 0) _e2p_upgrade_0_3_6 (); else cancelled = TRUE; } if (!cancelled) _e2p_upgrade_reconfig (); //implement all updates } return TRUE; } gboolean clean_plugin (Plugin *p) { return TRUE; } emelfm2-0.4.1/plugins/optional/0000700000175000017500000000000011015120161015277 5ustar cairocairoemelfm2-0.4.1/plugins/optional/plugin_track_48.png0000600000175000017500000001074210606121432021017 0ustar cairocairoPNG  IHDR00WsBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<_IDAThyxUչk}擑S@QpPۘcܶ ZZoRšㄈu@dpHd:{G81}|[kְ֚xd! L ܲd⒢p0 Ҏ?Ge_~чU~ wgy~dx9̀Kp <~wCGk eX9LYZS(O;A<X \ М6cF47̴^et,@SK]U"YU _J# i,JMv^T+5Lj&Vޗ%+OFL:.>{7Y.-H#(* ;=T76xUS2LQCh="ln/oT)kX'gQqIѷϸs[H eGn0}>]͕rѮ9wӣm壝?;i`q~q͹8JT7HRҮU):8W{Ͷc Qe[Pin][uB /=y3A\}{ɕ2#;whuD;ޚ,tQ7~`fYiym7ˮGLC^͡:i="baPc] Het3ˮ5.U%2ku^&~އ=%·Vk>ݲVUSx^ǂ+Ḿd$GR)2Ӳ4t5IǞ2 `T;J `32+ͻ>M*=۬s;9{Ro.|5pcdz+~tW>t+0%=o!qku(W 4w ,0/YT/Yl.X KUBƙW! dh)9aikQNR%ٛ^;}*-OwʌԨ-+.)k 7 it˃XvqMِzQ8}Ű<]6 9kw0}gm>N=Vk`ȘB&: 4uŚǮю-@ {nPBqg-F ?,~Qw# *@u4$:PSsa[d,`i҈⯵jnqaGiW?rr!F\֩xjO:ڛY5U_Mc^X 5ŠGpsDCP4qf>Go ͛079g3 oz/c ;)Ys&IGλz ZaX7v @J!mxfR@詠ȹo2(;l`'߽Ԓ[BfeX3_H$V/+yB+U !hmhۏvH!647"ГCdl#-n `BnT S~h;e N$w`/+-wlÎOCmwCWf@J7CI:8@ w3]9N_ElKiТlM!#PVZ^ǯU a\)M wx+⒢8?[H ~cxDr BHv ёBJ++o֋/~p>.T|w׍hƗS+c84_mVYXK|F0;#̢!9瞉 |YhEJA'?D6|2^9 *+-=xA+͉:t%`5 eη?"F߽z# ^rYF)q=G4ˉm|e8^{Z)@5Lkee\bE+/0u!!O*+_}k4KS }\~^y,[e^;%b+\ %R&\эAzz-[^x՟++-[O} \%,0ksShF]U7eǯҟ9{g)x]@5Wx>?i f6V%@puYi־"vR"_9# o_s۶U|a`4M9Hٙa8 0ii>u|֐鸡n) Hok ]j[VQʭ]K\q8O !|Vտ"fpg v\m(+ } This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "emelfm2.h" #include #include "e2_plugins.h" #include "e2_dialog.h" typedef struct _E2_TrackDlgData { GtkWidget *dialog; GtkWidget *service_combo; GtkWidget *query_combo; GtkWidget *type_radio; //1st member of search-type radio group GtkWidget *type_radio2; //2nd member of search-type radio group } E2_TrackDlgData; //tracker stuff, from various tracker 0.6.0 headers /* enum { SERVICE_FILES, SERVICE_FOLDERS, SERVICE_DOCUMENTS, SERVICE_IMAGES, SERVICE_MUSIC, SERVICE_VIDEOS, SERVICE_TEXT_FILES, SERVICE_DEVELOPMENT_FILES, SERVICE_OTHER_FILES, SERVICE_VFS_FILES, SERVICE_VFS_FOLDERS, SERVICE_VFS_DOCUMENTS, SERVICE_VFS_IMAGES, SERVICE_VFS_MUSIC, SERVICE_VFS_VIDEOS, SERVICE_VFS_TEXT_FILES, SERVICE_VFS_DEVELOPMENT_FILES, SERVICE_VFS_OTHER_FILES, SERVICE_CONVERSATIONS, SERVICE_PLAYLISTS, SERVICE_APPLICATIONS, SERVICE_CONTACTS, SERVICE_EMAILS, SERVICE_EMAILATTACHMENTS, SERVICE_APPOINTMENTS, SERVICE_TASKS, SERVICE_BOOKMARKS, SERVICE_HISTORY, SERVICE_PROJECTS, MAXSERVICES }; */ #define ACTIVE_TRACKER_SERVICES 11 static gchar *cmd_str [ACTIVE_TRACKER_SERVICES] = { //these service names are hardcoded in tracker-files, and not translated //same order here as enum // NULL, //not interested in Files // NULL, //not interested in "Folders", "Documents", "Images", "Music", "Videos", "Text", "Development", "Other", // NULL, //SERVICE_VFS_FILES, // NULL, //SERVICE_VFS_FOLDERS, // NULL, //SERVICE_VFS_DOCUMENTS, // NULL, //SERVICE_VFS_IMAGES, // NULL, //SERVICE_VFS_MUSIC, // NULL, //SERVICE_VFS_VIDEOS, // NULL, //SERVICE_VFS_TEXT_FILES, // NULL, //SERVICE_VFS_DEVELOPMENT_FILES, // NULL, //SERVICE_VFS_OTHER_FILES, "Conversations", //"GaimConversations", // NULL, //SERVICE_PLAYLISTS, "Applications", // NULL, //SERVICE_CONTACTS, "Emails", //"EvolutionEmails" "KMailEmails", "EmailAttachments", // "EvolutionAttachments" "KMailAttachments", // NULL, //SERVICE_APPOINTMENTS, // NULL, //SERVICE_TASKS, // NULL, //SERVICE_BOOKMARKS, // NULL, //SERVICE_HISTORY, // NULL, //SERVICE_PROJECTS, }; static gchar *object_names [ACTIVE_TRACKER_SERVICES] = { //these service strings are in same order as enum // N_("all files"), //not actually interested in "Files", this is a proxy // NULL, //not interested in "Folders", N_("office documents"), N_("images"), N_("music"), N_("videos"), N_("text files"), N_("development files"), N_("other files"), // NULL, //SERVICE_VFS_FILES, // NULL, //SERVICE_VFS_FOLDERS, // NULL, //SERVICE_VFS_DOCUMENTS, // NULL, //SERVICE_VFS_IMAGES, // NULL, //SERVICE_VFS_MUSIC, // NULL, //SERVICE_VFS_VIDEOS, // NULL, //SERVICE_VFS_TEXT_FILES, // NULL, //SERVICE_VFS_DEVELOPMENT_FILES, // NULL, //SERVICE_VFS_OTHER_FILES, N_("conversations"), // NULL, //N_("playlists") SERVICE_PLAYLISTS, N_("applications"), // NULL, //N_("people") SERVICE_CONTACTS, N_("emails"), //"EvolutionEmails" "KMailEmails", N_("email attachments"), //"EvolutionAttachments" "KMailAttachments", // NULL, //N_("appointments") SERVICE_APPOINTMENTS, // NULL, //N_("tasks") SERVICE_TASKS, // NULL, //N_("bookmarks") SERVICE_BOOKMARKS, // NULL, //N_("notes")SERVICE_HISTORY, // NULL, //N_("projects") SERVICE_PROJECTS, }; static gint service_index = -1; //undefined selection static GList *query_history = NULL; /** @brief response callback for plugin selection dialog @param dialog the dialog where the response was triggered @param response the response assigned to the activated button widget @param data ptr to plugins option data struct @return */ static void _e2p_track_choose_response_cb (GtkDialog *dialog, gint response, E2_TrackDlgData *rt) { if (response == E2_RESPONSE_USER1) return; //ignore toggles of hidden-items if (response == GTK_RESPONSE_OK) { //returns localized string gchar *local = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); if (local != NULL) { //no conversion error gchar *utf = F_FILENAME_FROM_LOCALE (local); gtk_entry_set_text (GTK_ENTRY (GTK_BIN (rt->query_combo)->child), utf); g_free (local); F_FREE (utf); } } gtk_widget_destroy (GTK_WIDGET (dialog)); gtk_widget_grab_focus (GTK_BIN (rt->query_combo)->child); } /** @brief bring up a system find-file window to choose a plugin @param button activated widget, UNUSED @param rt ptr to dialog data struct @return */ static void _e2p_track_choose_rdf_cb (GtkWidget *button, E2_TrackDlgData *rt) { //no need for vfs support here GtkWidget *dialog = gtk_file_chooser_dialog_new (NULL, GTK_WINDOW (rt->dialog), GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); e2_dialog_setup_chooser (dialog, _("choose query"), NULL, //CWD GTK_FILE_CHOOSER_ACTION_OPEN, FALSE, //hide-hidden FALSE, //single-selection GTK_RESPONSE_OK); //default response GtkFileFilter *filter = gtk_file_filter_new (); gtk_file_filter_set_name (GTK_FILE_FILTER (filter), _("rdf")); gtk_file_filter_add_pattern (filter, "*.rdf"); gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter); /* //hide the dialog's standard 'open' button GtkContainer *bbox = GTK_CONTAINER (GTK_DIALOG (dialog)->action_area); GList* children = gtk_container_get_children (bbox); GtkWidget *btn = children->data; gtk_widget_hide (btn); g_list_free (children); //add 2 buttons that we want e2_dialog_add_defined_button (dialog, &E2_BUTTON_APPLY); e2_dialog_add_defined_button (dialog, &E2_BUTTON_OK); */ g_signal_connect (G_OBJECT (dialog), "response", G_CALLBACK (_e2p_track_choose_response_cb), rt); e2_dialog_setup (dialog, rt->dialog); gtk_widget_show (dialog); } /* //get files with service (Documents, Music, Images, Videos, Text, Development, Other) tracker-files -s service //get files with mime type(s) tracker-files -m Mime1 [Mime2...] //search files for certain terms (ANDed) tracker-search [OPTION...] search terms ... - -l, --limit=limit limit the number of results showed -s, --service=service search from a specific service //perform an rdf query and return results with specified metadata fields tracker-query [OPTION...] RDFQueryFile [MetaDataField...] ... -s, --service=service search from a specific service -t, --search-term=search-term adds a fulltext search filter -k, --keyword=keyword adds a keyword filter */ /** @brief find and list matching items in output pane This is called from a callback, BGL closed @param rt pointer to data for dialog @return */ static void _e2p_track_list_results (E2_TrackDlgData *rt) { GtkWidget *entry = GTK_BIN (rt->query_combo)->child; const gchar *find = gtk_entry_get_text (GTK_ENTRY (entry)); gchar *command; gint result; if ((GTK_TOGGLE_BUTTON (rt->type_radio))->active) //search for service { gint type = gtk_combo_box_get_active (GTK_COMBO_BOX (rt->service_combo)); if (type == -1) return; if (*find == '\0' || g_str_equal (find, "*") || g_str_equal (find, _("all"))) command = g_strdup_printf ("tracker-files -s %s", cmd_str[service_index]); else command = g_strdup_printf ("tracker-search -s %s %s", cmd_str[service_index], find); } else if ((GTK_TOGGLE_BUTTON (rt->type_radio2))->active) //search for mimetype { if (*find == '\0') { e2_output_print_end (&app.tab, FALSE); return; } command = g_strdup_printf ("tracker-files -m %s", find); } else { if (*find == '\0') { e2_output_print_end (&app.tab, FALSE); return; } command = g_strdup_printf ("tracker-query %s", find); } #ifdef E2_COMMANDQ result = e2_command_run (command, E2_COMMAND_RANGE_DEFAULT, TRUE); #else result = e2_command_run (command, E2_COMMAND_RANGE_DEFAULT); #endif if (result == 0) e2_output_print_end (&app.tab, FALSE); g_free (command); } /** @brief callback for dialog's "response" signal @param dialog the dialog where the response was triggered @param response the response for the clicked button @param rt pointer to data for dialog @return */ static void _e2p_track_response_cb (GtkDialog *dialog, gint response, E2_TrackDlgData *rt) { switch (response) { case E2_RESPONSE_USER1: //show help on using tracker e2_utils_show_help ("tracker plugin"); //no translation unless help doc is translated gtk_widget_grab_focus (GTK_BIN (rt->query_combo)->child); break; case E2_RESPONSE_USER2: //clear query entry { GtkWidget *entry = GTK_BIN (rt->query_combo)->child; gtk_entry_set_text (GTK_ENTRY (entry), ""); gtk_widget_grab_focus (entry); } break; case E2_RESPONSE_FIND: //in case the user aborts during the search, update history stuff now service_index = gtk_combo_box_get_active (GTK_COMBO_BOX (rt->service_combo)); GtkWidget *entry = GTK_BIN (rt->query_combo)->child; const gchar *find = gtk_entry_get_text (GTK_ENTRY (entry)); if (*find != '\0') e2_list_update_history (find, &query_history, NULL, 30, FALSE); _e2p_track_list_results (rt); break; default: gtk_widget_destroy (rt->dialog); DEALLOCATE (E2_TrackDlgData, rt); break; } } /** @brief handle activation ( keypresses) in the query entry @param entry UNUSED the entry widget for the combo box @param rt pointer to dialog data struct @return */ static void _e2p_track_query_activated_cb (GtkEntry *entry, E2_TrackDlgData *rt) { _e2p_track_response_cb (GTK_DIALOG (rt->dialog), E2_RESPONSE_FIND, rt); } /** @brief create and run dialog @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if action completed successfully, else FALSE */ static gboolean _e2p_track (gpointer from, E2_ActionRuntime *art) { E2_TrackDlgData *rt = ALLOCATE (E2_TrackDlgData); CHECKALLOCATEDWARN (rt, return FALSE; ); rt->dialog = e2_dialog_create (NULL, NULL, _("tracker query"), _e2p_track_response_cb, rt); GtkWidget *hbox = e2_widget_add_box ((GTK_DIALOG (rt->dialog))->vbox, TRUE, E2_PADDING, FALSE, FALSE, E2_PADDING_SMALL); // rt->type_radio = e2_button_add_radio (hbox, NULL, NULL, FALSE, 0, NULL, NULL); // e2_widget_add_label (hbox, _("Search for"), 0.0, 0.5, FALSE, 0); rt->type_radio = e2_button_add_radio (hbox, _("_Search for"), NULL, TRUE, FALSE, 0, NULL, NULL); rt->service_combo = e2_combobox_add (hbox, FALSE, E2_PADDING, NULL, NULL, NULL, E2_COMBOBOX_MENU_STYLE); guint i; for (i = 0; i < ACTIVE_TRACKER_SERVICES; i++) gtk_combo_box_append_text (GTK_COMBO_BOX (rt->service_combo), gettext (object_names[i])); gtk_combo_box_set_active (GTK_COMBO_BOX (rt->service_combo), service_index); //USELESS? gtk_widget_set_tooltip_text ( // rt->service_combo, _("Select information service")); e2_widget_add_label (hbox, _("which match:"), 0.0, 0.5, FALSE, 0); gtk_widget_show (hbox); hbox = e2_widget_add_box ((GTK_DIALOG (rt->dialog))->vbox, TRUE, E2_PADDING, FALSE, FALSE, E2_PADDING_SMALL); GSList *group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (rt->type_radio)); // e2_button_add_radio (hbox, NULL, group, FALSE, 0, NULL, NULL); // e2_widget_add_label (hbox, _("Search for items whose _mimetype is any of:"), 0.0, 0.5, FALSE, 0); rt->type_radio2 = e2_button_add_radio (hbox, _("Search for items whose _mimetype is any of:"), group, FALSE, FALSE, 0, NULL, NULL); gtk_widget_show (hbox); hbox = e2_widget_add_box ((GTK_DIALOG (rt->dialog))->vbox, TRUE, E2_PADDING, FALSE, FALSE, E2_PADDING_SMALL); // group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (rt->type_radio)); e2_button_add_radio (hbox, _("Search for items using this rdf query:"), group, FALSE, FALSE, 0, NULL, NULL); e2_button_add (hbox, FALSE, E2_PADDING_LARGE, _("_Select"), GTK_STOCK_OPEN, _("Open query-selection dialog"), _e2p_track_choose_rdf_cb, rt); gtk_widget_show (hbox); rt->query_combo = e2_combobox_add ((GTK_DIALOG (rt->dialog))->vbox, FALSE, E2_PADDING, _e2p_track_query_activated_cb, rt, &query_history, E2_COMBOBOX_HAS_ENTRY | E2_COMBOBOX_FOCUS_ON_CHANGE | E2_COMBOBOX_CYCLE_HISTORY); gtk_widget_show (rt->query_combo); GtkWidget *btn = e2_dialog_add_undefined_button (rt->dialog, GTK_STOCK_HELP, _("_Help"), E2_RESPONSE_USER1); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif btn, _("Get help on constructing queries")); btn = e2_dialog_add_undefined_button (rt->dialog, GTK_STOCK_CLEAR, _("C_lear"), E2_RESPONSE_USER2); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif btn, _("Clear the current query")); btn = e2_dialog_add_undefined_button (rt->dialog, GTK_STOCK_FIND, _("_Find"), E2_RESPONSE_FIND); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif btn, _("Initiate a query using currently-specified criteria")); e2_dialog_set_negative_response (rt->dialog, GTK_RESPONSE_CLOSE); e2_dialog_show (rt->dialog, app.main_window, 0, &E2_BUTTON_CLOSE, NULL); GtkWidget *entry = GTK_BIN (rt->query_combo)->child; gtk_widget_grab_focus (entry); return TRUE; } //aname must be confined to this module static gchar *aname; /** @brief plugin initialization function, called by main program @param p ptr to plugin data struct @return TRUE if the initialization succeeds, else FALSE */ gboolean init_plugin (Plugin *p) { #define ANAME "track" aname = _("track"); p->signature = ANAME VERSION; p->menu_name = _("_Track.."); p->description = _("Find items in the tracker database"); p->icon = "plugin_"ANAME"_"E2IP".png"; //use icon file pathname if appropriate if (p->action == NULL) { //no need to free this gchar *action_name = g_strconcat (_A(1),".",aname,NULL); p->action = e2_plugins_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2p_track, NULL, FALSE, 0, NULL); e2_cache_int_register ("track-plugin-type", &service_index, 0); e2_cache_list_register ("track-plugin-history", &query_history); return TRUE; } return FALSE; } /** @brief cleanup transient things for this plugin @param p pointer to data struct for the plugin @return TRUE if all cleanups were completed */ gboolean clean_plugin (Plugin *p) { gchar *action_name = g_strconcat (_A(1),".",aname,NULL); gboolean ret = e2_plugins_action_unregister (action_name); g_free (action_name); if (ret) { //backup the cache data e2_cache_unregister ("track-plugin-type"); e2_cache_unregister ("track-plugin-history"); //cleanup ? } return ret; } emelfm2-0.4.1/plugins/optional/plugin_acl_48.png0000600000175000017500000000647110645277713020477 0ustar cairocairoPNG  IHDR00WsBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< IDATh՘{p\}?{]i`%rU^ Na¤LMG;i4)CB-1m q;D3iBMCx8#$?,yݻsN]zE=I{~s0bea/ofxDaPnK/Bop۲ZLs˖GCp˨eš8^g ѻrLWjfO6^/}{ùݐ9~ [4q5dt5{oMs`9 xD'{j v-юR=t!L(OLen{۶s4.7D!UDBarleɾ!`aۯNm=?U0X$V:`wt`%D#n=vx +[ͧ32**/B lHx2XKBs`̦㱈VKx;did,t_[=R{F&t&2JP+j,Gq@, uUޛ\(je^8/ t{Y1/KAX$Ng'V["EI/^%sp8kP`~Y!ձ2ۛ-k1h r>cgћR~$ބX #, V0Z񃤺{'fUƀW8|r/ߒJ4팁>c#ӕ +:YB6V2K]5l+~sod1* {[}H[<^YwU fxeoч6wJUw 1\8U})'btr{)6)Ku;mtP<8꣙DkP-ciGN"eQY p8{e6%k"ŒѪyV"0Aa `avJa>3@ens~4 ,uR&M+:&aDfmPa|Y֝꼚9 (?ך6R)Æh];`#-)Exʠq-C=lt13qBU;c#ojeڄϢL<2^.S.B `iH\>DkzDjC@3j~Os]U@ÙTLI g bĻ33l}TL_JAެ2,tǭT˯!6kp*4zP)T`./j }XbU"RN yBa]s4&ו睨 hD>OW8Q ֱj\Odcغ/!x }SОVjUJ&3@/]vTZ+6E xwo)~T1;Fw#ak|<}tڍVͿg\& K% Rue߶5ַn'&c KFs5v+bYxzp8l߿NԯY"7&T[ȿ[p4iO?n;*#%"*0L|\:1DO_;-}s(w˨o[3"y7FHKNkF Qos$FVd RTuhn>X. \9i$rYk VVR,|PG.?,K`4ѱ}:4Thn=KكBG[Pf_) \cI^| .|md% ak&iB_662fܑ1ۄKC9zu`ElR$U ^ve4DV-r~>š ߜx\+=.H,[|kp8x  fuC>r,թ5.[OhLt<+RjL|MPQMP",8taWMJˑ O߅ ӄ 7~4 xl*X,M?oB$ƻL(붾 L= !+s%߫g!hص_ika0Tr&Luo?4[k:zlsxyJ]M@s Ո~= BhRWd!1kWpw!*:Ln =;@(m>yK-Nƒe7KWP*0&hԶWXDŽ~ld// UU>p,녤3OcBmLJm@׺SNz&:ז慖X9LKXII<@{J2 !^3LZ,m^Oc"7: A3+ vr32r 󿊕g˜SC!_Ϯ}||Ԩ_7V2xٯjP{ a 162=Hw0mDB.7*\w^TX2֍"Nj/*ܛˍ:*N{r"v;B&LoDU. \w;$NB'q.7z4[:E+z'􅡺:Rˍ6;b'~+gZu\gi> Tem66qbDZY)iǨ82N@X r^@bzф0 C %\n4TWFQm!}VlC'Ft\p,o#?8ׁF/ ó`i <rڹaHǛ?s/.\fuD@;_DB@ZɋF This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file plugins/optional/e2p_thumbs.c @brief image-viewer plugin */ /*TODO selection transfers TO iconview? NO NEED a hook function to trigger auto refreshing without setting up an infinite loop of refreshing icons and filelist (for now, we just poll directory mtime) local sort funcs and icons-liststore rationalisation bias initial rendering order to visible rectangle ? bias rerendering order to visible rectangle NO NEED warn user about plugin-load failure ? confirm efficient cleanups (how to look into pixbufs?) efficient pixbuf saving ok? DnD ? */ #include "emelfm2.h" #include #include #include "e2_context_menu.h" #include "e2_filelist.h" #include "e2_task.h" #include "e2_filetype.h" #include "e2_plugins.h" #include "e2_dialog.h" #define E2_SOFTWARE_ID PROGNAME" file manager" #define E2_SCALEUP_SIZE 32 /*dialog data listore columns enumerator ... so that the same sort functions can be used for this store and the filelist store, we need to provide columns up to FINFO in this store, but provide different or non-uses for many of them */ enum { // FILENAME = 0 ... FINFO are enumerated elswhere PIXBUF = 1, THUMBNAIL, THUMBCOLCOUNT = FINFO+1 }; typedef struct _E2_ThumbDialogRuntime { GtkWidget *dialog; //the displayed dialog GtkWidget *iconview; //images iconview GtkListStore *store; //images data store GtkWidget *sortbtn; //action-area sort-button // GtkWidget *hiddenbtn; //action-area hide-button // gboolean show_hidden; //TRUE to show hidden items in the iconview ViewInfo *view; //data for displayed file pane gboolean clamp; //TRUE to clamp image display into range 32..128 px gboolean replicate; //TRUE to cause iconview selections to be replicated in related treeview gint sort_type; //to determine the correct sort func, corresponds to column in original store GtkSortType sort_order; gboolean blocked; //flag to prevent recursive refreshed guint refreshtimer_id; time_t dir_mtime; #ifdef E2_VFSTMP //FIXME path when dir not mounted local #else gchar *path; //path of dir currently displayed, same format as view->dir #endif GSList *oldstores; } E2_ThumbDialogRuntime; //aname must be confined to this module static gchar *aname; //static gboolean show_hidden = FALSE; //session-static value to use in dialogs //static assuming last-closed window sets size for next one in this session only static gint window_width = -1; static gint window_height = -1; #ifdef E2_TRANSIENTKEYS #include "e2_keybinding.h" static void _e2p_thumbs_keybindings (E2_OptionSet *set); #endif static void _e2p_thumbs_selection_change_cb (GtkIconView *iconview, E2_ThumbDialogRuntime *rt); //static gboolean _e2p_thumbs_refresh_hook (ViewInfo *view, // E2_ThumbDialogRuntime *rt); /*********************/ /***** utilities *****/ /*********************/ /** @brief create empty liststore to hold iconview data @return the new store */ static GtkListStore *_e2p_thumbs_make_store (void) { /* Most columns are for support data and are not displayed. FILENAME, NAMEKEY and FINFO are in the same positions as for the filelist stores, to allow the same sort functions to be used */ GtkListStore *store = gtk_list_store_new (THUMBCOLCOUNT, G_TYPE_STRING, //FILENAME displayed GDK_TYPE_PIXBUF, //PIXBUF displayed in place of SIZE GIMP_TYPE_THUMBNAIL, //THUMBNAIL in place of PERM G_TYPE_POINTER, //OWNER unused G_TYPE_POINTER, //GROUP unused G_TYPE_POINTER, //MODIFIED unused G_TYPE_POINTER, //ACCESSED unused G_TYPE_POINTER, //CHANGED unused G_TYPE_STRING, //NAMEKEY for i18n name sorts G_TYPE_POINTER //FINFO pr to FileInfo for the item ); return store; } /** @brief empty and destroy list store @param store pointer to the liststore to be killed @return */ static void _e2p_thumbs_clear_store (GtkListStore *store) { printd (DEBUG, "_e2p_thumbs_clear_store"); GtkTreeModel *mdl = GTK_TREE_MODEL (store); //FIXME BAD model warning GtkTreeIter iter; if (gtk_tree_model_get_iter_first (mdl, &iter)) { //it's not empty already //clear data in the store //CHECKME need to clear anything else? FileInfo *info; GdkPixbuf *pxb; GimpThumbnail *thm; do { gtk_tree_model_get (mdl, &iter, FINFO, &info, PIXBUF, &pxb, THUMBNAIL, &thm, -1); DEALLOCATE (FileInfo, info); // if (pxb != NULL) g_object_unref (G_OBJECT (pxb)); // if (thm != NULL) //CHECKME cache the pixbufs here instead of when filling the store g_object_unref (G_OBJECT (thm)); } while (gtk_tree_model_iter_next (mdl, &iter)); gtk_list_store_clear (store); //NEEDED ?? } g_object_unref (G_OBJECT (store)); } /** @brief idle function to clear and eliminate old liststores @param oldstores list of superseded liststores to be cleaned @return FALSE, to stop the callbacks */ static gboolean _e2p_thumbs_clear_old_stores (GSList *oldstores) { #ifdef DEBUG_MESSAGES gint debug = g_slist_length (oldstores); #endif GSList *member; for (member = oldstores; member != NULL; member = member->next) { _e2p_thumbs_clear_store ((GtkListStore *)member->data); } g_slist_free (oldstores); printd (DEBUG, "%d old images-liststore(s) cleared", debug); return FALSE; } /** @brief create, populate and apply an iconview-compatible liststore Rows are added for each relevant non-dir item in the corresponding filelist's liststore. The existing liststore is replaced, and queued for cleanup. BGL is expected to be open @param rt pointer to data struct for dialog @return pointer to the liststore, or NULL if there's a problem */ static GtkListStore *_e2p_thumbs_replace_store (E2_ThumbDialogRuntime *rt) { //FIXME some access here causes inotify to notice that refresh is needed rt->blocked = TRUE; //prevent re-entrance // printd (DEBUG, "start store fill"); GtkListStore *store = _e2p_thumbs_make_store (); if (store == NULL) { rt->blocked = FALSE; return NULL; } E2_ListChoice pane = (rt->view == &app.pane1_view) ? PANE1 : PANE2; e2_filelist_disable_one_refresh (pane); // gboolean ref = rt->view->refresh_requested; //transfer data from filelist store to iconview store GtkTreeIter itert; // GtkTreeModel *mdlt = GTK_TREE_MODEL (rt->view->store); //the child model (everything) GtkTreeModel *mdlt = rt->view->model; //the filter-model if (gtk_tree_model_get_iter_first (mdlt, &itert)) { FileInfo *info; GdkPixbuf *pxb; GimpThumbnail* thumbnail; GError *error; gboolean makepxb; gchar *filename, *namekey, *dlocal, *localpath; GtkTreeIter iteri; gdk_threads_enter (); e2_dialog_set_cursor (rt->dialog, GDK_WATCH); gdk_threads_leave (); //make new model unsorted to speed up addition GtkTreeSortable *sortablei = GTK_TREE_SORTABLE (store); gtk_tree_sortable_set_sort_column_id (sortablei, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING); do { gtk_tree_model_get (mdlt, &itert, FILENAME, &filename, NAMEKEY, &namekey, FINFO, &info, //FIXME handle refreshing which changes the address of info's -1); //do a bit of filtering to reduce irrelevant pixbuf attempts ... switch (info->statbuf.st_mode & S_IFMT) { case S_IFLNK: /* //no special treatment needed for links #ifdef E2_VFSTMP //FIXME vfs #else dlocal = F_FILENAME_TO_LOCALE (rt->view->dir); #endif localpath = e2_utils_strcat (dlocal, info->filename); len = strlen (localpath) - 1; if (localpath[len] == G_DIR_SEPARATOR) localpath[len] = '\0'; FIXME use e2_fs_walk_link (); len = e2_fs_readlink (localpath, target, sizeof (target)); if (len <= 0) { F_FREE (dlocal); g_free (localpath); break; } target[len] = '\0'; //check the real target, looking thru chained links if (e2_fs_stat (target, &statbuf2 E2_ERR_NONE()) //stat failed || !S_ISREG(statbuf2.st_mode)) { F_FREE (dlocal); g_free (localpath); break; } makepxb = TRUE; break; */ case S_IFREG: #ifdef E2_VFSTMP //FIXME vfs #else dlocal = F_FILENAME_TO_LOCALE (rt->view->dir); #endif localpath = e2_utils_strcat (dlocal, info->filename); makepxb = TRUE; break; default: makepxb = FALSE; break; } if (makepxb) { thumbnail = gimp_thumbnail_new (); error = NULL; if (!gimp_thumbnail_set_filename (thumbnail, localpath, &error)) { if (error != NULL) { //FIXME warn the user g_error_free (error); } makepxb = FALSE; } if (makepxb) { gint width, height; if (gdk_pixbuf_get_file_info (localpath, &width, &height) != NULL) { gboolean scaled; gint scaleto; recreate: if (rt->clamp) { if (width < E2_SCALEUP_SIZE && height < E2_SCALEUP_SIZE) { scaled = TRUE; scaleto = E2_SCALEUP_SIZE; } else if (width > GIMP_THUMB_SIZE_NORMAL || height > GIMP_THUMB_SIZE_NORMAL) { scaled = TRUE; scaleto = GIMP_THUMB_SIZE_NORMAL; } else { scaled = FALSE; scaleto = MAX (width, height); } } else { scaled = FALSE; scaleto = MAX (width, height); } // if (gimp_thumbnail_peek_thumb (thumbnail, scaleto) != GIMP_THUMB_STATE_EXISTS) if (gimp_thumbnail_check_thumb (thumbnail, scaleto) != GIMP_THUMB_STATE_OK) { rescale: if (scaled) pxb = gdk_pixbuf_new_from_file_at_scale (localpath, scaleto, scaleto, TRUE, &error); else pxb = gdk_pixbuf_new_from_file (localpath, &error); if (pxb != NULL) { // local save >> into CWD/.thumblocal/normal, maybe not creatable ?! BUG? // if (!gimp_thumbnail_save_thumb_local (thumbnail, pxb, //CHECKME save pixbuf later, so we can startup more quickly if (!gimp_thumbnail_save_thumb (thumbnail, pxb, E2_SOFTWARE_ID, &error)) { //warn the user if (error != NULL) g_error_free (error); makepxb = FALSE; } } else //pixbuf creation failed { //warn the user if (error != NULL) g_error_free (error); makepxb = FALSE; } } else //a maybe-valid cached image found { //some hacky stuff to work around the lib's approach to reporting //non-standard-size thumbnails ?? if (gimp_thumbnail_peek_image (thumbnail) == GIMP_THUMB_STATE_EXISTS) { width = MAX (thumbnail->image_width, thumbnail->image_height); if (width == 0) //0 is reprorted for non-standard-size ? { height = width = scaleto; goto rescale; } //if (width != 0 //0 is reprorted for non-standard-size ? // && else if (width < E2_SCALEUP_SIZE || width > GIMP_THUMB_SIZE_NORMAL || width != scaleto) { height = width; goto recreate; } } pxb = gimp_thumbnail_load_thumb (thumbnail, scaleto, &error); if (pxb == NULL) { //warn the user if (error != NULL) g_error_free (error); makepxb = FALSE; } } } else //no file info { //FIXME warn the user makepxb = FALSE; } } if (makepxb) { //copy the FileInfo, in case a refresh clobbers the original FileInfo *info2 = ALLOCATE (FileInfo); CHECKALLOCATEDWARN (info2, return NULL;); *info2 = *info; //store the data gtk_list_store_insert_with_values (store, &iteri, -1, FILENAME, filename, NAMEKEY, namekey, FINFO, info2, PIXBUF, pxb, THUMBNAIL, thumbnail, -1); } else g_object_unref (G_OBJECT (thumbnail)); //CHECKME any specific contents to clean ? F_FREE (dlocal); g_free (localpath); } g_free (filename); g_free (namekey); } while (gtk_tree_model_iter_next (mdlt, &itert)); //arrange to sort new store same as old one gtk_tree_sortable_set_sort_func (sortablei, FILENAME, e2_all_columns[rt->sort_type].sort_func, &rt->sort_order, NULL); //do the sort gtk_tree_sortable_set_sort_column_id (sortablei, FILENAME, rt->sort_order); g_object_ref (G_OBJECT (rt->store)); //preserve the store for proper cleanup in idle gdk_threads_enter (); //connect view to new model gtk_icon_view_set_model (GTK_ICON_VIEW (rt->iconview), GTK_TREE_MODEL (store)); gdk_threads_leave (); g_object_unref (G_OBJECT (store)); //kill the ref from model assignment //arrange to cleanup old store, later //FIXME manage shared access to superseded-stores list rt->oldstores = g_slist_append (rt->oldstores, rt->store); //&& some test for cleanup not underway now) g_idle_add ((GSourceFunc) _e2p_thumbs_clear_old_stores, rt->oldstores); rt->store = store; rt->oldstores = NULL; //start afresh gdk_threads_enter (); e2_dialog_set_cursor (rt->dialog, GDK_LEFT_PTR); gdk_threads_leave (); } rt->dir_mtime = rt->view->dir_mtime; //update refresh guide /*#ifdef E2_FAM # ifdef E2_VFSTMP //FIXME # else # ifdef E2_FAM_KERNEL //this is a bad hack to prevent extra refresh if (!ref) e2_fs_FAM_clean_reports (rt->view->dir); # endif # endif #endif */ e2_filelist_enable_one_refresh (pane); rt->blocked = FALSE; // printd (DEBUG, "finish icons store fill"); return store; } /** @brief refresh icons-view liststore The existing liststore is replaced, and queued for cleanup. BGL is expected to be open @param rt pointer to data struct for dialog @return */ static void _e2p_thumbs_refresh_store (E2_ThumbDialogRuntime *rt) { gchar *name; GtkTreePath *tp; GtkTreeModel *mdl = GTK_TREE_MODEL (rt->store); GtkTreeIter iter; GList *member, *selnames = NULL; GList *selpaths = gtk_icon_view_get_selected_items (GTK_ICON_VIEW (rt->iconview)); if (g_list_length (selpaths) > 0) { //record currently-selected data so we can re-select in new store for (member = selpaths; member != NULL; member = member->next) { tp = (GtkTreePath *)member->data; gtk_tree_model_get_iter (mdl, &iter, tp); gtk_tree_model_get (mdl, &iter, FILENAME, &name, -1); selnames = g_list_append (selnames, name); gtk_tree_path_free (tp); } g_list_free (selpaths); } //update the store _e2p_thumbs_replace_store (rt); //reselect things FIXME do this smarter if (g_list_length (selnames) > 0) { mdl = GTK_TREE_MODEL (rt->store); if (gtk_tree_model_iter_n_children (mdl, NULL)) { //reselect all paths where we can gtk_tree_model_get_iter_first (mdl, &iter); for (member = selnames; member != NULL; member = member->next) { name = (gchar *)member->data; if (e2_tree_find_iter_from_str_same (mdl, FILENAME, name, &iter)) { tp = gtk_tree_model_get_path (mdl, &iter); gdk_threads_enter (); gtk_icon_view_select_path (GTK_ICON_VIEW (rt->iconview), tp); gdk_threads_leave (); gtk_tree_path_free (tp); gtk_tree_model_get_iter_first (mdl, &iter); //ready for next search } g_free (name); } /* //goto former position if (0) //FIXME { tp = (GtkTreePath *) selpaths->data; gtk_icon_view_scroll_to_path (GTK_ICON_VIEW (rt->iconview), tp, TRUE, 0.3, 0.5); } */ } else g_list_foreach (selnames, (GFunc) g_free, NULL); g_list_free (selnames); } } /** @brief timer callback to do a store refresh if the dir looks dirty @param rt pointer to dialog's data struct @return TRUE unless the dialog is destroyed already */ static gboolean _e2p_thumbs_check_dirty (E2_ThumbDialogRuntime *rt) { printd (DEBUG, "_e2p_thumbs_check_dirty"); if (!GTK_IS_WIDGET (rt->dialog)) return FALSE; static gboolean busy = FALSE; if (!busy) { #ifdef E2_VFSTMP //FIXME when dir is not mounted local #endif if (rt->dir_mtime < rt->view->dir_mtime && !rt->blocked && g_str_equal (rt->path, rt->view->dir)) { busy = TRUE; _e2p_thumbs_refresh_store (rt); busy = FALSE; } } return TRUE; } /** @brief timer callback to turn auto-refresh back on @param rt pointer to dialog's data struct @return FALSE */ /*static gboolean _e2p_thumbs_enable_refresh (E2_ThumbDialogRuntime *rt) { if (GTK_IS_WIDGET (rt->dialog)) e2_hook_register (&rt->view->hook_refresh, _e2p_thumbs_refresh_hook, rt); return FALSE; } */ /** @brief timer callback to do a store replacement after refreshing is unblocked @param rt pointer to dialog's data struct @return TRUE if the associated-filelist refresh is still in progress */ static gboolean _e2p_thumbs_wait_to_replace (E2_ThumbDialogRuntime *rt) { if (GTK_IS_WIDGET (rt->dialog) && !rt->blocked) { LISTS_LOCK gboolean busy = rt->view->listcontrols.refresh_working || rt->view->listcontrols.cd_working; LISTS_UNLOCK if (busy) return TRUE; static gboolean working = FALSE; //simple blocker instead of killing timer //to prevent automatic refreshing of 2 views from feeding off each other, //suspend for a while the automatic refreshing here //(too bad if we miss something in the meantime) // printd (DEBUG, "doing deferred replication of CHANGE-DIR for icons liststore"); // e2_hook_unregister (&rt->view->hook_refresh, _e2p_thumbs_refresh_hook, rt, TRUE); if (!working) { //previous timer callback hasn't yet finished working = TRUE; _e2p_thumbs_replace_store (rt); working = FALSE; } /*#ifdef USE_GLIB2_14 g_timeout_add_seconds (10, #else g_timeout_add (10000, #endif (GSourceFunc)_e2p_thumbs_enable_refresh, rt); */ } return FALSE; } /** @brief hook function for app.paneX.hook_change_dir This is initiated with BGL off/open @param newpath UNUSED path of opened directory, utf-8 string @param rt pointer to dialog's data struct @return TRUE always */ static gboolean _e2p_thumbs_change_dir_hook (gchar *newpath, E2_ThumbDialogRuntime *rt) { printd (DEBUG, "replicating CHANGE-DIR for icons liststore"); g_free (rt->path); rt->path = g_strdup (newpath); LISTS_LOCK gboolean busy = rt->view->listcontrols.refresh_working || rt->view->listcontrols.cd_working; LISTS_UNLOCK if (!busy) { //to prevent automatic refreshing of 2 views from feeding off each other, //suspend for a while the automatic refreshing here //(too bad if we miss something in the meantime) // e2_hook_unregister (&rt->view->hook_refresh, _e2p_thumbs_refresh_hook, rt, TRUE); _e2p_thumbs_replace_store (rt); /*#ifdef USE_GLIB2_14 g_timeout_add_seconds (10, #else g_timeout_add (10000, #endif (GSourceFunc)_e2p_thumbs_enable_refresh, rt); */ } else { printd (DEBUG, "when the current filelist refresh is finished"); g_timeout_add (200, (GSourceFunc)_e2p_thumbs_wait_to_replace, rt); } return TRUE; } /** @brief timer callback to do a store refresh after refreshing is unblocked @param rt pointer to dialog's data struct @return TRUE if the associated-filelist refresh is still in progress */ static gboolean _e2p_thumbs_wait_to_refresh (E2_ThumbDialogRuntime *rt) { if (GTK_IS_WIDGET (rt->dialog) && !rt->blocked) { LISTS_LOCK gboolean disabled = rt->view->listcontrols.norefresh; //CHECKME ->listing ? LISTS_UNLOCK if (disabled) return TRUE; printd (DEBUG, "deferred replication of REFRESH of icons liststore"); //to prevent automatic refreshing of 2 views from feeding off each other, //suspend for a while the automatic refreshing here //(too bad if we miss something in the meantime) // e2_hook_unregister (&rt->view->hook_refresh, _e2p_thumbs_refresh_hook, rt, TRUE); static gboolean working = FALSE; //simple blocker instead of killing timer if (!working) { //previous timer callback hasn't yet finished working = TRUE; _e2p_thumbs_refresh_store (rt); working = FALSE; } /*#ifdef USE_GLIB2_14 g_timeout_add_seconds (10, #else g_timeout_add (10000, #endif (GSourceFunc)_e2p_thumbs_enable_refresh, rt); */ } return FALSE; } /* * @brief hook function for view.hook_refresh This is initiated from an idle-callback, so BGL is off @param view pointer to data struct for view whose filelist has been refeshed @param rt pointer to dialog's data struct @return TRUE always */ /*static gboolean _e2p_thumbs_refresh_hook (ViewInfo *view, E2_ThumbDialogRuntime *rt) { printd (DEBUG, "replicating REFRESH of icons liststore"); if (!rt->view->norefresh) //CHECKME ->listing ? { //to prevent automatic refreshing of 2 views from feeding off each other, //suspend for a while the automatic refreshing here //(too bad if we miss something in the meantime) e2_hook_unregister (&rt->view->hook_refresh, _e2p_thumbs_refresh_hook, rt, TRUE); _e2p_thumbs_refresh_store (rt); #ifdef USE_GLIB2_14 g_timeout_add_seconds (10, #else g_timeout_add (10000, #endif (GSourceFunc)_e2p_thumbs_enable_refresh, rt); } else { printd (DEBUG, "when the current filelist refresh is finished"); g_timeout_add (200, (GSourceFunc)_e2p_thumbs_wait_to_refresh, rt); } return TRUE; } */ /** @brief migrate iconview selection to associated filelist @param rt pointer to dialog's data struct @return TRUE if something was selected in the iconview */ static gboolean _e2p_thumbs_transfer_selection (E2_ThumbDialogRuntime *rt) { gboolean retval = FALSE; GtkTreeSelection *listsel = gtk_tree_view_get_selection (GTK_TREE_VIEW (rt->view->treeview)); gtk_tree_selection_unselect_all (listsel); GList *sel = gtk_icon_view_get_selected_items (GTK_ICON_VIEW (rt->iconview)); if (sel != NULL && g_list_length (sel) > 0) { gchar *name; GList *member; GtkTreePath *tp; GtkTreeModel *mdl = GTK_TREE_MODEL (rt->store); GtkTreeModel *listmdl = gtk_tree_view_get_model (GTK_TREE_VIEW (rt->view->treeview)); //filter model GtkTreeIter iter, listiter; if (gtk_tree_model_get_iter_first (listmdl, &listiter)) { for (member = sel; member != NULL; member = member->next) { tp = (GtkTreePath *)member->data; gtk_tree_model_get_iter (mdl, &iter, tp); gtk_tree_model_get (mdl, &iter, FILENAME, &name, -1); if (e2_tree_find_iter_from_str_same (listmdl, FILENAME, name, &listiter)) { gtk_tree_selection_select_iter (listsel, &listiter); gtk_tree_model_get_iter_first (listmdl, &listiter); } g_free (name); gtk_tree_path_free (tp); } } else g_list_foreach (sel, (GFunc) gtk_tree_path_free, NULL); g_list_free (sel); retval = TRUE; } return retval; } /** @brief iconview selection foreach function to reselect then clean each selected path This is needed because selection is cleared when view is disconnected from model @param data pointer to list item data, a gtk tree path @param rt pointer to dialog data struct @return */ static void _e2p_thumbs_cleanpath (GtkTreePath *data, E2_ThumbDialogRuntime *rt) { gtk_icon_view_select_path (GTK_ICON_VIEW (rt->iconview), data); gtk_tree_path_free (data); } /** @brief rotate or flip selected thumbnails in accord with @a type No change is made to cached data @param type enumerator of the type of change, rotate + or -, default is flip vertical @param rt pointer to dialog data struct @return */ static void _e2p_thumbs_transform (GdkPixbufRotation type, E2_ThumbDialogRuntime *rt) { GList *selpaths = gtk_icon_view_get_selected_items (GTK_ICON_VIEW (rt->iconview)); gboolean selection = (selpaths != NULL && g_list_length (selpaths) > 0); if (selection) { GtkTreePath *tp; GtkTreeIter iter; GdkPixbuf *oldpxb, *newpxb; GList *member; g_object_ref (G_OBJECT (rt->store)); gtk_icon_view_set_model (GTK_ICON_VIEW (rt->iconview), NULL); for (member = selpaths; member != NULL; member = member->next) { tp = (GtkTreePath *)member->data; gtk_tree_model_get_iter (GTK_TREE_MODEL (rt->store), &iter, tp); gtk_tree_model_get (GTK_TREE_MODEL (rt->store), &iter, PIXBUF, &oldpxb, -1); switch (type) { case GDK_PIXBUF_ROTATE_CLOCKWISE: newpxb = gdk_pixbuf_rotate_simple (oldpxb, GDK_PIXBUF_ROTATE_CLOCKWISE); break; case GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE: newpxb = gdk_pixbuf_rotate_simple (oldpxb, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE); break; default: newpxb = gdk_pixbuf_flip (oldpxb, FALSE); break; } if (newpxb != NULL) { g_object_unref (G_OBJECT (oldpxb)); gtk_list_store_set (rt->store, &iter, PIXBUF, newpxb, -1); } } gtk_icon_view_set_model (GTK_ICON_VIEW (rt->iconview), GTK_TREE_MODEL (rt->store)); g_object_unref (G_OBJECT (rt->store)); g_signal_handlers_block_by_func (G_OBJECT (rt->iconview), _e2p_thumbs_selection_change_cb, rt); g_list_foreach (selpaths, (GFunc)_e2p_thumbs_cleanpath, rt); g_signal_handlers_unblock_by_func (G_OBJECT (rt->iconview), _e2p_thumbs_selection_change_cb, rt); } g_list_free (selpaths); } /********************/ /*** context menu ***/ /********************/ /** @brief set popup-menu position This function is supplied when calling gtk_menu_popup(), to position the displayed menu, after a menu-key press. set @a push_in to TRUE for menu completely inside the screen, FALSE for menu clamped to screen size @param menu UNUSED the GtkMenu to be positioned @param x place to store gint representing the menu left @param y place to store gint representing the menu top @param push_in place to store pushin flag @param rt data struct for the dialog where the menu key was pressed @return */ static void _e2p_thumbs_set_menu_position (GtkMenu *menu, gint *x, gint *y, gboolean *push_in, E2_ThumbDialogRuntime *rt) { gint left, top; gtk_window_get_position (GTK_WINDOW (rt->dialog), &left, &top); GtkAllocation alloc = rt->iconview->allocation; *x = left + alloc.x + alloc.width/2; *y = top + alloc.y +alloc.height/2 - 30; *push_in = FALSE; } /** @brief execute action corresponding to item selected from filetype tasks menu This is the callback for handling a selection of a filetype action from the context menu @param widget the selected menu item widget @param rt data struct for the dialog where the menu key was pressed @return */ static void _e2p_thumbs_menu_choose_filetype_action_cb (GtkWidget *widget, E2_ThumbDialogRuntime *rt) { printd (DEBUG, "context menu choose filetype action cb"); GList *sel = gtk_icon_view_get_selected_items (GTK_ICON_VIEW (rt->iconview)); if (sel != NULL && g_list_length (sel) > 0) { GString *command; gchar *name; GList *member; GtkTreePath *tp; GtkTreeModel *mdl = GTK_TREE_MODEL (rt->store); GtkTreeIter iter; command = g_string_sized_new (512); command = g_string_assign (command, gtk_widget_get_name (widget)); for (member = sel; member != NULL; member = member->next) { tp = (GtkTreePath *)member->data; gtk_tree_model_get_iter (mdl, &iter, tp); gtk_tree_model_get (mdl, &iter, FILENAME, &name, -1); //FIXME there's a small chance that the dir may have changed //without syncing to the dialog content g_string_append_printf (command, " \"%s%s\"", rt->view->dir, name); g_free (name); gtk_tree_path_free (tp); } g_list_free (sel); #ifdef E2_COMMANDQ e2_command_run (command->str, E2_COMMAND_RANGE_DEFAULT, FALSE); #else e2_command_run (command->str, E2_COMMAND_RANGE_DEFAULT); #endif g_string_free (command, TRUE); } } /** @brief iconview un-select-all callback @param widget UNUSED the menu item widget which activated the callback @param rt pointer to dialog data struct @return */ static void _e2p_thumbs_unselect_all_cb (GtkWidget *widget, E2_ThumbDialogRuntime *rt) { gtk_icon_view_unselect_all (GTK_ICON_VIEW (rt->iconview)); } /** @brief iconview refresh callback @param widget UNUSED the menu item widget which activated the callback @param rt pointer to dialog data struct @return */ static void _e2p_thumbs_refresh_cb (GtkWidget *widget, E2_ThumbDialogRuntime *rt) { LISTS_LOCK gboolean busy = rt->view->listcontrols.norefresh; LISTS_UNLOCK if (!busy) { gdk_threads_leave (); _e2p_thumbs_refresh_store (rt); gdk_threads_enter (); } else g_timeout_add (200, (GSourceFunc)_e2p_thumbs_wait_to_refresh, rt); } /** @brief rotate selected thumbnails 90 degrees clockwise No change is made to cached data @param widget UNUSWED the menu item widget which activated the callback @param rt pointer to dialog data struct @return */ static void _e2p_thumbs_turn_clockwise_cb (GtkWidget *widget, E2_ThumbDialogRuntime *rt) { _e2p_thumbs_transform (GDK_PIXBUF_ROTATE_CLOCKWISE, rt); } /** @brief rotate selected thumbnails 90 degrees anti-clockwise No change is made to cached data @param widget UNUSED the menu item widget which activated the callback @param rt pointer to dialog data struct @return */ static void _e2p_thumbs_turn_anticlockwise_cb (GtkWidget *widget, E2_ThumbDialogRuntime *rt) { _e2p_thumbs_transform (GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE, rt); } /** @brief flip selected thumbnails top-to-bottom No change is made to cached data @param widget UNUSED the menu item widget which activated the callback @param rt pointer to dialog data struct @return */ static void _e2p_thumbs_flip_cb (GtkWidget *widget, E2_ThumbDialogRuntime *rt) { _e2p_thumbs_transform (GDK_PIXBUF_ROTATE_NONE, rt); } /** @brief iconview selection-replication callback @param widget the menu item widget which activated the callback @param rt pointer to dialog data struct @return */ static void _e2p_thumbs_toggle_replication_cb (GtkWidget *widget, E2_ThumbDialogRuntime *rt) { rt->replicate = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget)); //FIXME other adjustments if (rt->replicate) { _e2p_thumbs_transfer_selection (rt); } // else // { // } } /** @brief iconview clamp-image-size toggle callback @param widget the menu item widget which activated the callback @param rt pointer to dialog data struct @return */ static void _e2p_thumbs_toggle_clamp_cb (GtkWidget *widget, E2_ThumbDialogRuntime *rt) { rt->clamp = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget)); _e2p_thumbs_refresh_cb (NULL, rt); } /** @brief populate @a menu with items for the actions for a filetype Can't use the standard function for this, it initiates commands using %f, which may be wrong, and has no data for the callback Each member of @a actions is like "command" or "label@command" @param menu the menu widget to which the action menu-items are to be added @param actions NULL-terminated array of utf8 strings, each a command for a filetype @param rt data struct for the dialog where the menu key was pressed @return */ static void _e2p_thumbs_menu_filetype_actions (GtkWidget *menu, const gchar **actions, E2_ThumbDialogRuntime *rt) { gchar *sep; GtkWidget *menu_item; while (*actions != NULL) { if ((sep = strchr (*actions, '@')) != NULL) //if always ascii @, don't need g_utf8_strchr() { *sep = '\0'; menu_item = e2_menu_add (menu, (gchar *)*actions, NULL, NULL, _e2p_thumbs_menu_choose_filetype_action_cb, rt); *sep = '@'; //revert to original form (this is the 'source' data) gtk_widget_set_name (menu_item, sep+1); } else { menu_item = e2_menu_add (menu, (gchar *)*actions, NULL, NULL, _e2p_thumbs_menu_choose_filetype_action_cb, rt); gtk_widget_set_name (menu_item, *actions); } actions++; } } /** @brief construct and show dialog context menu This provides a subset of the filelist context menu, plus a couple of things @param iconview the widget where the click happened @param event_button which mouse button was clicked (0 for a menu key) @param event_time time that the event happened (0 for a menu key) @param rt runtime struct for the displayed dialog @return */ static void _e2p_thumbs_show_context_menu (GtkWidget *iconview, guint event_button, gint event_time, E2_ThumbDialogRuntime *rt) { GtkIconView *ivw = GTK_ICON_VIEW (iconview); GList *selpaths = gtk_icon_view_get_selected_items (ivw); gboolean selection = (selpaths != NULL && g_list_length (selpaths) > 0); GtkWidget *menu = gtk_menu_new (); GtkWidget *item; if (selection) { GtkTreeIter iter; GtkTreeModel *mdl = GTK_TREE_MODEL (rt->store); GtkTreePath *tpath = (GtkTreePath *) selpaths->data; if (gtk_tree_model_get_iter (mdl, &iter, tpath)) { gchar *filename, *ext; const gchar **actions; gtk_tree_model_get (mdl, &iter, FILENAME, &filename, -1); ext = filename; while ((ext = strchr (ext, '.')) != NULL) { if (ext == filename) { //hidden item, probably ext++; continue; } ext++; //skip discovered dot, ascii '.'. always single char actions = e2_filetype_get_actions (ext); if (actions != NULL) { _e2p_thumbs_menu_filetype_actions (menu, actions, rt); break; } } g_free (filename); } gchar *aname = g_strconcat (_A(5),".",_A(62), NULL); e2_menu_add_action (menu, _("Open _with.."),"open_with_"E2IP".png", NULL, aname, NULL, NULL); e2_menu_add_separator (menu); e2_menu_add (menu, _("Rotate _+"), NULL, _("Rotate selected images quarter-turn clockwise"), _e2p_thumbs_turn_clockwise_cb, rt); e2_menu_add (menu, _("Rotate _-"), NULL, _("Rotate selected images quarter-turn anti-clockwise"), _e2p_thumbs_turn_anticlockwise_cb, rt); e2_menu_add (menu, _("_Flip"), NULL, _("Flip selected images top-to-bottom"), _e2p_thumbs_flip_cb, rt); } e2_menu_add (menu, _("_Refresh"), GTK_STOCK_REFRESH, NULL, _e2p_thumbs_refresh_cb, rt); if (selection) { // item = e2_menu_add (menu, _("_Unselect all"), GTK_STOCK_CLEAR, NULL, _e2p_thumbs_unselect_all_cb, rt); // if (!selection) // gtk_widget_set_sensitive (item, FALSE); } item = e2_menu_add_check (menu, _("Replicate _selection"), rt->replicate, _e2p_thumbs_toggle_replication_cb, rt); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif item, _("If activated, items selected in this window will also be selected in the associated filelist")); item = e2_menu_add_check (menu, _("_Clamp size"), rt->clamp, _e2p_thumbs_toggle_clamp_cb, rt); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif item, _("If activated, thumbnails will be scaled up or down if needed, into the range 32 to 128 pixels")); /* item = e2_menu_add_check (menu, _("_Hidden items"), rt->show_hidden, _e2_treedlg_toggle_strict_cb, rt); if (!rt->show_hidden) #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif item, _("If activated, hidden image files will be displayed")); gtk_widget_set_sensitive (item, !rt->show_hidden); */ g_signal_connect (G_OBJECT (menu), "selection-done", G_CALLBACK (e2_menu_destroy_cb), NULL); if (event_button == 0) gtk_menu_popup (GTK_MENU (menu), NULL, NULL, (GtkMenuPositionFunc) _e2p_thumbs_set_menu_position, rt, 0, event_time); else //this was a button-3 click gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 3, event_time); if (selection) g_list_foreach (selpaths, (GFunc) gtk_tree_path_free, NULL); g_list_free (selpaths); } /*********************/ /***** callbacks *****/ /*********************/ /** @brief mouse button press callback @param iconview the widget where the button was pressed @param event gdk event data @param view rt data for the view to be worked on @return TRUE (stop other handlers) for btn 3 press, else FALSE */ static gboolean _e2p_thumbs_button_press_cb (GtkWidget *iconview, GdkEventButton *event, E2_ThumbDialogRuntime *rt) { printd (DEBUG, "callback: _e2p_thumbs mouse button press"); if (event->button == 3) { _e2p_thumbs_show_context_menu (iconview, 3, event->time, rt); return TRUE; } return FALSE; } /** @brief iconview key-press callback @param iconview UNUSED the focused treeview widget when the key was pressed @param event pointer to event data struct @param rt pointer to dialog data struct @return TRUE (stop other handlers) for menu key has, else FALSE */ /*static gboolean _e2p_thumbs_key_press_cb (GtkWidget *iconview, GdkEventKey *event, E2_ThumbDialogRuntime *rt) { printd (DEBUG, "callback: _e2p_thumbs key press"); return FALSE; } */ /** @brief menu-button press callback @param iconview the widget where the press happened @param rt dialog runtime data struct @return TRUE always */ static gboolean _e2p_thumbs_popup_menu_cb (GtkWidget *iconview, E2_ThumbDialogRuntime *rt) { gint event_time = gtk_get_current_event_time (); _e2p_thumbs_show_context_menu (iconview, 0, event_time, rt); return TRUE; } /** @brief iconview item-activated callback Activation is triggered when is pressed or when a double-click happens This causes the activated item to be opened @param iconview the widget where the activation happened @param path model path to the clicked item @param view data struct for the view to be worked on @return */ static void _e2p_thumbs_item_activated_cb (GtkIconView *iconview, GtkTreePath *tpath, ViewInfo *view) { printd (DEBUG, "callback: _e2p_thumbs_item_activated"); GtkTreeIter iter; GtkTreeModel *model = gtk_icon_view_get_model (iconview); if (gtk_tree_model_get_iter (model, &iter, tpath)) { //get the activated item gchar *localpath; FileInfo *info; gtk_tree_model_get (model, &iter, FINFO, &info, -1); localpath = e2_utils_dircat (view, info->filename, TRUE); #ifdef E2_VFS VPATH ddata = { localpath, view->spacedata }; e2_task_backend_open (&ddata, TRUE); #else e2_task_backend_open (localpath, TRUE); #endif g_free (localpath); } } /** @brief iconview selection-changed callback This allows selection to be migrated to the associated filelist treeview @param iconview the widget where the activation happened @param rt data struct for the dialog @return */ static void _e2p_thumbs_selection_change_cb (GtkIconView *iconview, E2_ThumbDialogRuntime *rt) { if (rt->replicate) _e2p_thumbs_transfer_selection (rt); } /** @brief iconview sort-column callback @param widget the menu item widget which activated the callback @param rt pointer to dialog data struct @return */ static void _e2p_thumbs_change_sortcol_cb (GtkWidget *widget, E2_ThumbDialogRuntime *rt) { gpointer col = g_object_get_data (G_OBJECT (widget), "sort-column"); rt->sort_type = GPOINTER_TO_INT (col) - 1; GtkTreeSortable *sortable = GTK_TREE_SORTABLE (rt->store); //arrange to sort new store same as old one gtk_tree_sortable_set_sort_func (sortable, FILENAME, e2_all_columns[rt->sort_type].sort_func, &rt->sort_order, NULL); gtk_tree_sortable_set_sort_column_id (sortable, FILENAME, rt->sort_order); } /** @brief iconview sort-order callback @param widget the menu item widget which activated the callback @param rt pointer to dialog data struct @return */ static void _e2p_thumbs_toggle_sortorder_cb (GtkWidget *widget, E2_ThumbDialogRuntime *rt) { gboolean newchoice = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget)); rt->sort_order = (newchoice) ? GTK_SORT_ASCENDING : GTK_SORT_DESCENDING; GtkTreeSortable *sortable = GTK_TREE_SORTABLE (rt->store); gtk_tree_sortable_set_sort_column_id (sortable, FILENAME, rt->sort_order); e2_button_set_image (rt->sortbtn, (newchoice) ? GTK_STOCK_SORT_DESCENDING : GTK_STOCK_SORT_ASCENDING); } /** @brief create sort-options menu for dialog @param rt data struct for dialog @return the menu widget */ static GtkWidget *_e2p_thumbs_create_sorting_menu (E2_ThumbDialogRuntime *rt) { GtkWidget *item; GtkWidget *menu = gtk_menu_new (); gint i; for (i = 0; i < MAX_COLUMNS; i++) { item = e2_menu_add_check (menu, gettext (e2_all_columns[i].title), (i == rt->sort_type), _e2p_thumbs_change_sortcol_cb, rt); //also pass the column no. (bumped to avoid 0=NULL) g_object_set_data (G_OBJECT (item), "sort-column", GINT_TO_POINTER (i+1)); } item = e2_menu_add_check (menu, _("Ascending"), (rt->sort_order == GTK_SORT_ASCENDING), _e2p_thumbs_toggle_sortorder_cb, rt); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif item, _("If activated, items are displayed in ascending order")); g_signal_connect (G_OBJECT (menu), "selection-done", G_CALLBACK (e2_menu_destroy_cb), NULL); return menu; } /** @brief set popup menu position This function is supplied when calling gtk_menu_popup(), to position the displayed menu. set @a push_in to TRUE for menu completely inside the screen, FALSE for menu clamped to screen size @param menu the GtkMenu to be positioned @param x place to store gint representing the menu left @param y place to store gint representing the menu top @param push_in place to store pushin flag @param button the activated dialog button @return */ static void _e2p_thumbs_set_sortmenu_position (GtkMenu *menu, gint *x, gint *y, gboolean *push_in, GtkWidget *button) { gint button_y; e2_utils_get_abs_pos (button, x, &button_y); GtkRequisition menu_size; gtk_widget_size_request (GTK_WIDGET (menu), &menu_size); //place below or above button, left-aligned if (button_y - menu_size.height <= 2) //> gdk_screen_height ()) *y = button_y + button->allocation.height + 2; else *y = button_y - menu_size.height - 2; *push_in = FALSE; } /** @brief handle button click, window-close etc for directory-tree dialog This is the callback for response signals emitted from @a dialog @param dialog UNUSED the dialog where the response was generated @param response the response returned from the dialog @param rt pointer to data struct for the dialog @return */ static void _e2p_thumbs_response_cb (GtkDialog *dialog, gint response, E2_ThumbDialogRuntime *rt) { switch (response) { /* case E2_RESPONSE_USER1: //toggle display of hidden items rt->show_hidden = !rt->show_hidden; _e2_treedlg_refresh_cb (NULL, rt); //do the content before changing button e2_button_set_image (rt->hiddenbtn, (rt->show_hidden) ? "hidden_noshow_"E2IP".png" : "hidden_show_"E2IP".png"); break; */ case E2_RESPONSE_USER2: //sort-button click { GtkWidget *menu = _e2p_thumbs_create_sorting_menu (rt); guint32 time = gtk_get_current_event_time (); gtk_menu_popup (GTK_MENU (menu), NULL, NULL, (GtkMenuPositionFunc) _e2p_thumbs_set_sortmenu_position, rt->sortbtn, 1, time); } break; default: g_source_remove (rt->refreshtimer_id); window_width = rt->dialog->allocation.width; window_height = rt->dialog->allocation.height; #ifdef E2_TRANSIENTKEYS gchar *category = g_strconcat (_C(11), ".", aname, NULL); //_(dialogs.view e2_keybinding_unregister (category, rt->iconview); g_free (category); #endif //to prevent leaks, ensure underlying store is not zapped with the dialog g_object_ref (G_OBJECT (rt->store)); gtk_widget_destroy (rt->dialog); GHookList thisview_cdhook = (rt->view == &app.pane1_view) ? app.pane1.hook_change_dir : app.pane2.hook_change_dir; e2_hook_unregister (&thisview_cdhook, _e2p_thumbs_change_dir_hook, rt, TRUE); // e2_hook_unregister (&rt->view->hook_refresh, _e2p_thumbs_refresh_hook, rt, TRUE); // show_hidden = rt->show_hidden; //backups for later use this session //FIXME manage shared access to this list rt->oldstores = g_slist_append (rt->oldstores, rt->store); g_idle_add ((GSourceFunc) _e2p_thumbs_clear_old_stores, rt->oldstores); DEALLOCATE (E2_ThumbDialogRuntime, rt); break; } } /** @brief establish and show icons view for contents of dir associated with @a view This is a thread function @param view data struct for file pane with which the iconview is to be associated @return NULL */ static gpointer _e2p_thumbs_dialog_run (ViewInfo *view) { printd (DEBUG, "create images preview dialog"); E2_ThumbDialogRuntime *rt = ALLOCATE (E2_ThumbDialogRuntime); CHECKALLOCATEDWARN (rt, return NULL;); //create empty liststore framework for the dialog rt->store = _e2p_thumbs_make_store (); if (rt->store == NULL) { //FIXME warn user DEALLOCATE (E2_ThumbDialogRuntime, rt); return NULL; } // rt->show_hidden = show_hidden; //before dialog is filled CHECKME use view->show_hidden ? rt->replicate = TRUE; //cause iconview selections to be replicated in related treeview rt->clamp = TRUE; //cause thumbs to be scaled up or down if outside 32..128 px rt->sort_type = view->sort_column; rt->sort_order = view->sort_order; rt->blocked = FALSE; /* no need for this, before the store is initially filled GtkTreeSortable *sortable = GTK_TREE_SORTABLE (rt->store); gtk_tree_sortable_set_sort_func (sortable, FILENAME, e2_all_columns[rt->sort_type].sort_func, &rt->sort_order, NULL); //set initial sort arrangment before store is filled gtk_tree_sortable_set_sort_column_id (sortable, FILENAME, rt->sort_order); */ rt->view = view; rt->path = g_strdup (view->dir); rt->oldstores = NULL; gchar *title = (view == &app.pane1_view) ? _("pane 1 images") : _("pane 2 images") ; rt->dialog = e2_dialog_create (NULL, NULL, title, _e2p_thumbs_response_cb, rt); //scrolled window for the treeview GtkWidget *sw = e2_widget_get_sw (GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, GTK_SHADOW_ETCHED_IN); gtk_scrolled_window_set_placement (GTK_SCROLLED_WINDOW (sw), e2_option_int_get ("scrollbar-position")); gtk_container_add ((GTK_CONTAINER (GTK_DIALOG (rt->dialog)->vbox)), sw); /*now create iconsview */ // gchar *fontstr = (e2_option_bool_get ("custom-list-font")) ? // e2_option_str_get ("list-font") : NULL; //NULL will cause default font GtkTreeModel *mdl = GTK_TREE_MODEL (rt->store); //create iconview for the pane related to @a view rt->iconview = gtk_icon_view_new_with_model (mdl); gtk_container_add (GTK_CONTAINER (sw), rt->iconview); g_object_unref (G_OBJECT (rt->store)); //kill the ref from view creation //allow non-sorted display using GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID // GtkTreeSortable *sortable = GTK_TREE_SORTABLE (mdl); // gtk_tree_sortable_set_default_sort_func (sortable, NULL, NULL, NULL); //set general iconview properties GtkIconView *iconview = GTK_ICON_VIEW (rt->iconview); gtk_icon_view_set_text_column (iconview, FILENAME); gtk_icon_view_set_pixbuf_column (iconview, PIXBUF); gtk_icon_view_set_reorderable (iconview, TRUE); gtk_icon_view_set_selection_mode (iconview, GTK_SELECTION_MULTIPLE); #ifdef USE_GTK2_12 //this is for pango >= 1.16, Gtk 2.10.8? PangoContext *context = gtk_widget_get_pango_context (rt->iconview); const PangoMatrix *matrix = pango_context_get_matrix (context); PangoGravity grav = pango_gravity_get_for_matrix (matrix); if PANGO_GRAVITY_IS_VERTICAL(grav) gtk_icon_view_set_orientation (iconview, GTK_ORIENTATION_HORIZONTAL); //VERTICAL is the default #endif // gtk_icon_view_set_columns (iconview, gint columns); // gtk_icon_view_set_item_width (iconview, gint item_width); // gtk_icon_view_set_spacing (iconview, gint spacing); // gtk_icon_view_set_row_spacing (iconview, gint row_spacing); // gtk_icon_view_set_column_spacing (iconview, gint column_spacing); // gtk_icon_view_set_margin (iconview, gint margin); // gtk_icon_view_select_path (iconview, GtkTreePath *path); // gtk_icon_view_scroll_to_path (iconview, GtkTreePath *path, // gboolean use_align, gfloat row_align, gfloat col_align); // gtk_icon_view_set_cursor (iconview, GtkTreePath *path, // GtkCellRenderer *cell, gboolean start_editing); g_signal_connect (G_OBJECT (rt->iconview), "popup-menu", G_CALLBACK (_e2p_thumbs_popup_menu_cb), rt); #ifdef E2_TRANSIENTKEYS //add dialog-specific key bindings, before the key-press callback gchar *category = g_strconcat (_C(11), ".", aname, NULL); //_(dialogs.view e2_keybinding_register_transient (category, rt->iconview, _e2p_thumbs_keybindings); g_free (category); #endif // g_signal_connect_after (G_OBJECT (rt->iconview), "key-press-event", // G_CALLBACK (_e2_treedlg_key_press_cb), rt); g_signal_connect (G_OBJECT (rt->iconview), "button-press-event", G_CALLBACK (_e2p_thumbs_button_press_cb), rt); g_signal_connect (G_OBJECT (rt->iconview), "item-activated", G_CALLBACK (_e2p_thumbs_item_activated_cb), view); g_signal_connect (G_OBJECT (rt->iconview), "selection-changed", G_CALLBACK (_e2p_thumbs_selection_change_cb), rt); /* //by default, type-ahead searching is enabled on column 0 gtk_tree_view_set_search_equal_func (GTK_ICON_VIEW (tvw), (GtkTreeViewSearchEqualFunc)_e2_fileview_match_filename, view, NULL); //DnD connections // gtk_drag_source_set (tvw, GDK_BUTTON1_MASK, target_table, n_targets, //-1 if XDS is last, // GDK_ACTION_COPY); //can't use 2 of this fn, it seems gtk_drag_source_set (tvw, GDK_BUTTON1_MASK | GDK_BUTTON2_MASK, target_table, n_targets, //-1, //the last target (XDS) is supported for dest only GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK); gtk_drag_dest_set (tvw, GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP, target_table, n_targets, GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK); g_signal_connect (G_OBJECT (tvw), "drag-begin", G_CALLBACK (e2_dnd_drag_begin_cb), view); g_signal_connect (G_OBJECT (tvw), "drag-data-get", G_CALLBACK (e2_dnd_drag_data_get_cb), view); g_signal_connect (G_OBJECT (tvw), "drag-motion", G_CALLBACK (e2_dnd_drag_motion_cb), view); g_signal_connect (G_OBJECT (tvw), "drag-leave", G_CALLBACK (e2_dnd_drag_leave_cb), view); //needed if GTK_DEST_DEFAULT_DROP not set, above // g_signal_connect (G_OBJECT (tvw), "drag-drop", // G_CALLBACK (e2_dnd_drag_drop_cb), view); g_signal_connect (G_OBJECT (tvw), "drag-data-received", G_CALLBACK(e2_dnd_drag_data_received_cb), view); //FIXME do these once only // atom_text_uri_list = gdk_atom_intern (target_table[0,0], FALSE); // atom_text_plain = gdk_atom_intern (target_table[1,0], FALSE); // atom_XdndDirectSave0 = gdk_atom_intern (target_table[2,0], FALSE); // g_signal_connect (G_OBJECT (tvw), "drag-data-delete", // G_CALLBACK (e2_dnd_drag_delete_cb), view); //pick up any key-bindings before the general key-press //BUT CHECKME order probably does not matter g_signal_connect (G_OBJECT (rt->iconview), "cursor-changed", G_CALLBACK (_e2_fileview_cursor_change_cb), view); */ //relate initial size to last-used, or if first, to filepanes size if (window_width == -1) window_width = view->treeview->allocation.width; if (window_height == -1) window_height = view->treeview->allocation.height; gtk_window_resize (GTK_WINDOW (rt->dialog), window_width, window_height); /* rt->hiddenbtn = e2_dialog_add_undefined_button_custom (rt->dialog, FALSE, E2_RESPONSE_USER1, _("_Hidden"), (rt->show_hidden) ? "hidden_noshow_"E2IP".png" : "hidden_show_"E2IP".png", _("Toggle display of hidden directories"), NULL, NULL); */ rt->sortbtn = e2_dialog_add_undefined_button_custom (rt->dialog, FALSE, E2_RESPONSE_USER2, _("_Sort"), (rt->sort_order == GTK_SORT_ASCENDING) ? GTK_STOCK_SORT_DESCENDING : GTK_STOCK_SORT_ASCENDING, NULL, NULL, NULL); E2_BUTTON_CLOSE.showflags |= E2_BTN_DEFAULT; //set default button e2_dialog_show (rt->dialog, app.main_window, E2_DIALOG_THREADED, &E2_BUTTON_CLOSE, NULL); //get the actual data for the iconview LISTS_LOCK gboolean busy = rt->view->listcontrols.refresh_working || rt->view->listcontrols.cd_working; LISTS_UNLOCK if (!busy) _e2p_thumbs_replace_store (rt); else g_timeout_add_full (G_PRIORITY_HIGH, 100, (GSourceFunc)_e2p_thumbs_wait_to_replace, rt, NULL); //show and select the startup row corresponding to displayed dir // _e2_treedlg_show_path (rt->view->dir, TRUE, rt); GHookList thisview_cdhook = (view == &app.pane1_view) ? app.pane1.hook_change_dir : app.pane2.hook_change_dir; e2_hook_register (&thisview_cdhook, _e2p_thumbs_change_dir_hook, rt); // e2_hook_register (&rt->view->hook_refresh, _e2p_thumbs_refresh_hook, rt); rt->refreshtimer_id = #ifdef USE_GLIB2_14 g_timeout_add_seconds (E2_FILESCHECK_INTERVAL_S, #else g_timeout_add (E2_FILESCHECK_INTERVAL, #endif (GSourceFunc) _e2p_thumbs_check_dirty, rt); return NULL; } /** @brief show tree dialog action This creates a thread to produce the dialog, because the directories scan can be slow @param from UNUSED the button, menu item etc which was activated @param art UNUSED action runtime data @return TRUE */ static gboolean _e2p_thumbs_show_action (gpointer from, E2_ActionRuntime *art) { g_thread_create ((GThreadFunc) _e2p_thumbs_dialog_run, curr_view, FALSE, NULL); return TRUE; } #ifdef E2_TRANSIENTKEYS /** @brief function to setup default key-bindings for icon-browser dialog This is just to provide placeholders, the actual bindings are meaningless @param set pointer to option data struct @return */ static void _e2p_thumbs_keybindings (E2_OptionSet *set) { //the key name strings are parsed by gtk, and no translation is possible e2_option_tree_setup_defaults (set, g_strdup("keybindings=<"), //internal name //the category string(s) here need to match the binding name g_strconcat(_C(11),"||||",NULL), //_("dialogs" g_strconcat("\t",aname,"||||",NULL), //_("thumbs" g_strconcat("\t\t|j","||",_A(119),".",_A(120),"|a",NULL), g_strconcat("\t\t|k","||",_A(119),".",_A(120),"|c",NULL), g_strdup(">"), NULL); } #endif /****************/ /**** public ****/ /****************/ /** @brief plugin initialization function, called by main program @param p ptr to plugin data struct @return TRUE if the initialization succeeds, else FALSE */ gboolean init_plugin (Plugin *p) { #define ANAME "thumbnailer" aname = _("thumbs"); p->signature = ANAME VERSION; p->menu_name = _("_Thumbnail.."); p->description = _("Display thumbnails of image files in the active pane"); p->icon = "plugin_thumbs_"E2IP".png"; //use icon file pathname if appropriate if (p->action == NULL) { if (gimp_thumb_init (E2_SOFTWARE_ID, NULL)) { //no need to free this gchar *action_name = g_strconcat (_A(10),".",aname,NULL); p->action = e2_plugins_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2p_thumbs_show_action, NULL, FALSE, 0, NULL); return TRUE; } } return FALSE; } /** @brief cleanup transient things for this plugin @param p pointer to data struct for the plugin @return TRUE if all cleanups were completed */ gboolean clean_plugin (Plugin *p) { #ifdef E2_TRANSIENTKEYS gchar *category = g_strconcat (_C(11), ".", aname, NULL); //_(dialogs.view e2_keybinding_unregister (category, NULL); g_free (category); #endif gchar *action_name = g_strconcat (_A(10),".",aname,NULL); gboolean ret = e2_plugins_action_unregister (action_name); g_free (action_name); return ret; } emelfm2-0.4.1/plugins/optional/e2p_acl.c0000600000175000017500000041026610717142462017003 0ustar cairocairo/* $Id: e2p_acl.c 728 2007-11-15 22:16:18Z tpgww $ Copyright (C) 2007 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file plugins/optional/e2p_acl.c @brief plugin for updating item access-control, and related functions */ /*TODO support E2ACL_POSIX_RESTRICTED probably with string form acl converted to walkable array _e2p_acl_find_entry _e2p_acl_create_entry _e2p_acl_get_for_mode _e2p_acl_apply_shown _e2p_acl_apply_modified _e2p_acl_fill_store _e2p_acl_reset_mode_fields debugging */ //allow non-standard store-filling //#define FAKEACLSTORE //prevent any actual change to the filesystem //#define DISABLEACLCHANGE #define ACLEXTRA_ACTIONS #include "emelfm2.h" #include #include #include #include #include "e2_plugins.h" #include "e2_dialog.h" #include "e2_permissions_dialog.h" #include "e2_ownership_dialog.h" #include "e2_task.h" #include "e2_filelist.h" //define the scope of libacl functions which are available for use here #ifdef __linux__ # define E2ACL_POSIX_EXTENDED #else //pick one of these //# define E2ACL_POSIX_EXTENDED # define E2ACL_POSIX_FULL //# define E2ACL_POSIX_RESTRICTED not yet supported #endif #ifdef E2ACL_POSIX_EXTENDED # include # define ACL_GET_PERM acl_get_perm #else # include # ifdef HAVE_ACL_GET_PERM # define ACL_GET_PERM acl_get_perm # else # ifdef HAVE_ACL_GET_PERM_NP # define ACL_GET_PERM acl_get_perm_np # else # warning in the absence of anything else more obviously suitable, a linux-derived # warning version of function acl_get_perm() will be used. You MUST CHECK that''s ok # define ACL_GET_PERM _e2p_get_perm # endif # endif #endif //limit on entries per ACL #ifdef _POSIX_ACL #define MAX_ACL_ENTRIES _POSIX_ACL_MAX #else #define MAX_ACL_ENTRIES 16 #endif //acl-array enumerator enum { AXSNOW, DEFNOW, MAXACLS }; //liststore columns enumerator enum { CLASS, QUAL, READ, WRITE, EXEC, WHOLE, SORTKEY, MAXACLCOLS }; //change-process flags typedef enum { //types of "base" data E2_ACL_SHOWN = 1, //changes based only on the data entered into the treeview(s) E2_ACL_SYSTEM = 1 << 1, //changes based only on converted form of normal mode permissions E2_ACL_SYSMOD = 1 << 2, //changes based on converted form of normal mode permissions, //as modified by data in the treeview(s) //types of entry-change E2_ACL_NUKE = 1 << 3, //revert to minimum ACL E2_ACL_SET = 1 << 4, //replace current, or add if missing from current (this may be the only valid one on some systems) E2_ACL_ADD = 1 << 5, //add whole if appropriate or add entry permissions E2_ACL_REMOVE = 1 << 6, //delete whole if appropriate or delete entry permissions //only for remembering button-state between dialogs E2_ACL_WHOLE = 1 << 7, //types of recursion E2_ACL_NODOWN = 1 << 8, //no recursion into any directory E2_ACL_DIR = 1 << 9, //recurse dirs, apply things in accord with E2_ACL_DIRAXS, E2_ACL_DIRDEF E2_ACL_OTHER = 1 << 10, //recurse dirs, apply acesss-ACL only to non-dirs E2_ACL_DIRAXS = 1 << 11, //apply access-ACL to any relevant dir E2_ACL_DIRDEF = 1 << 12 //default-ACL to any relevant dir } E2_ACLTask; //data for recursive changes typedef struct _E2_ChACLData { //pointers to some dialog data E2_ACLTask task; GPtrArray *axs_changes; GPtrArray *def_changes; GList *dirdata; //list of E2P_DirEnt's for visited dirs, to be processed later gboolean continued_after_problem; } E2_ChACLData; typedef struct _E2_CopyACLData { E2_ACLTask task; gint oldroot_len; //length of original localpath, for filtering descendants VPATH *otherdir; //localised, with trailer GList *dirdata; //list of E2P_DirEnt's for visited dirs, to be processed later gboolean continued_after_problem; } E2_CopyACLData; //data for an entry in a changes-array typedef struct _E2_EntryChangeData { acl_tag_t tag; //= type id_t id; //uid_t or gid_t acl_perm_t perms; //acl_permset_t points to one of these gboolean whole; //TRUE = apply change to whole entry, FALSE = apply to perms only } E2_EntryChangeData; typedef struct _E2_ACLDlgRuntime { GtkWidget *dialog; GtkWidget *axs_view; //permissions presentation GtkWidget *def_view; GtkWidget *treeview; //= axs_view or def_view for current use GtkListStore *axs_store; GtkListStore *def_store; GtkListStore *store; //= axs_store or def_store for current use GtkListStore *classes; //valid names for cells in the CLASS column of the main store GtkListStore *users; //valid names for cells in the QUAL column of the main store //radio buttons which decide what to show GtkWidget *use_axsacl_btn; GtkWidget *use_defacl_btn; //toggle and radio buttons which decide what to do GtkWidget *shown_perms_btn; GtkWidget *system_perms_btn; GtkWidget *sysmod_perms_btn; GtkWidget *dir_axs_btn; GtkWidget *dir_def_btn; GtkWidget *remove_all_btn; GtkWidget *set_data_btn; GtkWidget *add_data_btn; GtkWidget *remove_data_btn; //toggle to setup automatic whole-field completion when row is added etc GtkWidget *change_whole_btn; GtkWidget *recurse_btn; GtkWidget *recurse_dirs_btn; GtkWidget *recurse_other_btn; //some of the action-area widgets GtkWidget *add_row_btn; GtkWidget *remove_row_btn; gchar *itempath; //localised path of item being processed gboolean thisis_dir; //whether itempath is a directory gboolean permission; //whether the user is authorised to change permission(s) of itempath acl_t acls [MAXACLS]; //existing acl(s) of itempath, or NULL E2_ACLTask task; GPtrArray *axs_changes; GPtrArray *def_changes; DialogButtons result; } E2_ACLDlgRuntime; static void _e2p_acl_cell_edit_start_cb (GtkCellRenderer *renderer, GtkCellEditable *editable, gchar *path_string, E2_ACLDlgRuntime *rt); static void _e2p_acl_cell_edit_stop_cb (GtkCellRenderer *renderer, E2_ACLDlgRuntime *rt); static void _e2p_acl_cell_edited_cb (GtkCellRendererText *cell, gchar *path_string, gchar *new_text, E2_ACLDlgRuntime *rt); static void _e2p_acl_toggle_cb (GtkCellRendererToggle *cell, const gchar *path_str, E2_ACLDlgRuntime *rt); static gint _e2p_acl_view_sort (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data); static void _e2p_acl_fill_sortkey (GtkTreeModel *model, GtkTreeIter *iter); extern pthread_mutex_t task_mutex; extern gpointer chaclfunc; //stored in task_backend file, a pointer to for preserving acl's when copying #ifdef ACLEXTRA_ACTIONS static gboolean _e2p_task_aclcopyQ (E2_ActionTaskData *qed); #endif static gboolean _e2p_task_aclQ (E2_ActionTaskData *qed); //no. of visible columns in associated treeview #define VISCOLS SORTKEY static gchar *colnames[VISCOLS] = { "", //unused "UID/GID", //untranslated N_("Read"), N_("Write"), N_("Exec"), N_("Whole") }; static E2_ACLTask saved_task = E2_ACL_NODOWN | E2_ACL_SET | E2_ACL_SHOWN; //for reinstating settings from last dialog #define CLASSCOUNT 4 static gchar *classorder [CLASSCOUNT] = { "1", "2", "3", "4" }; //for sorting store-rows static gchar *classnames [CLASSCOUNT] = { N_("User"), N_("Group"), N_("Mask"), N_("Other") }; static gchar *classinames [CLASSCOUNT]; //translated classnames, for runtime comparisons /** @brief get the state of permission flag @a perm in @a permset_d This is an adaptation of a linux libacl function, courtesy of Andreas Gruenbacher @param permset_d @param perm one or more of ACL_READ | ACL_WRITE | ACL_EXECUTE @return 1 if (any flag in) @a perm is set in @param permset_d, 0 if not set, -1 on error */ #ifndef E2ACL_POSIX_EXTENDED # ifndef HAVE_ACL_GET_PERM # ifndef HAVE_ACL_GET_PERM_NP static gint _e2p_get_perm (acl_permset_t permset, acl_perm_t perm) { if (permset == NULL) return -1; //initial (and only) member of the struct is an acl_perm_t return (*((acl_perm_t *)permset) & perm) ? 1 : 0; } # endif # endif #endif /** @brief apply the ACL(s) of @a src to @a dest This func is called during copy-tasks or move-tasks that have reverted to a between-device copy VPATH's are used, though not needed until the acl lib supports non-local items @param src absolute path string, localised, of item whose ACL's are to be copied @param statprt pointer to stafbuf for @a src, filled by lstat() @param dest absolute path string, localised, of item whose ACL's are to be set @return TRUE if the process was completed successfully */ static gboolean _e2p_acl_copyacls (VPATH *src, const struct stat *statptr, VPATH *dest) { #ifdef E2_VFSTMP //FIXME decide if only local items can have acls managed if (src->spacedata != NULL || src->spacedata != NULL) return FALSE; #endif gpointer workspace = acl_init (1); //working heapspace for retrieved access and default ACLs if (workspace == NULL) //FIXME warn user return FALSE; gboolean retval; acl_t acl = acl_get_file (VPSTR(src), ACL_TYPE_ACCESS); retval = (acl != NULL) ? (acl_set_file (VPSTR(dest), ACL_TYPE_ACCESS, acl) == 0) : TRUE; if (S_ISDIR (statptr->st_mode)) { acl_t acl = acl_get_file (VPSTR(src), ACL_TYPE_DEFAULT); if (acl != NULL) retval = retval && (acl_set_file (VPSTR(dest), ACL_TYPE_DEFAULT, acl) == 0); } acl_free (workspace); return retval; } /** @brief callback function for recursive directory ACL-copying Tree is being walked breadth-first, not physical. Dirs are made accessible and writable if not already so and it's permitted, dirs are added to a list to be processed after all the tree has been traversed, other items are changed as requested (if possible) Error messages assume BGL is open/off @param localpath absolute path of item to change, localised string @param statptr pointer to struct stat with info about @a localpath @param status code from the walker, indicating what type of report it is @param user_data pointer to user-specified data @return E2TW_CONTINUE on success, others as appropriate */ static E2_TwResult _e2p_acl_twcb_copyacl (VPATH *localpath, const struct stat *statptr, E2_TwStatus status, E2_CopyACLData *user_data) { E2_TwResult retval = E2TW_CONTINUE; //default error code = none #ifdef E2_VFS VPATH ddata; ddata.spacedata = user_data->otherdir->spacedata; #endif gchar *dest; if (status != E2TW_DP) { struct stat othersb; //end-portion of localpath appended to user_data->otherdir dest = e2_utils_strcat (VPSTR (user_data->otherdir), VPSTR(localpath) + user_data->oldroot_len); #ifdef E2_VFS ddata.localpath = dest; if (e2_fs_lstat (&ddata, &othersb E2_ERR_NONE()) #else if (e2_fs_lstat (dest, &othersb E2_ERR_NONE()) #endif || ((othersb.st_mode & S_IFMT) != (statptr->st_mode & S_IFMT))) { g_free (dest); return retval; } } else dest = NULL; //warning prevention switch (status) { E2P_DirEnt *dirfix; GList *member; case E2TW_DP: //for a matched dir, chacl its new mode, cleanup for (member = g_list_last (user_data->dirdata); member != NULL; member = member->prev) { dirfix = member->data; if (dirfix != NULL) { if (g_str_equal (dirfix->path, localpath)) { #ifdef E2_VFS ddata.localpath = dirfix->path; if (!_e2p_acl_copyacls (localpath, statptr, &ddata)) #else if (!_e2p_acl_copyacls (localpath, statptr, dest)) #endif retval = E2TW_FIXME; g_free (dirfix->path); DEMALLOCATE (E2P_DirEnt, dirfix); user_data->dirdata = g_list_delete_link (user_data->dirdata, member); break; } } // else //should never happen CHECKME ok when walking list ? // user_data->dirdata = g_list_delete_link (user_data->dirdata, member); } break; case E2TW_DRR: //directory now readable retval |= E2TW_DRKEEP; //no permission reversion in walker case E2TW_D: //ensure dir is writable, if we can if (e2_fs_tw_adjust_dirmode (localpath, statptr, (W_OK | X_OK)) == 0) { //failed to set missing W and/or X perm //take a shot at doing any change, anyhow, probably fails #ifdef E2_VFS _e2p_acl_copyacls (localpath, statptr, &ddata); #else _e2p_acl_copyacls (localpath, statptr, dest); #endif //FIXME warn user if either change fails retval |= E2TW_SKIPSUB; //don't try to do any descendant } else //dir can be processed { //add this dir to list of items to chacl afterwards dirfix = MALLOCATE (E2P_DirEnt); CHECKALLOCATEDWARNT (dirfix, retval=E2TW_STOP;break;) dirfix->path = g_strdup (VPSTR (localpath)); dirfix->mode = statptr->st_mode & ALLPERMS; //may want to restore the original value user_data->dirdata = g_list_append (user_data->dirdata, dirfix); } break; case E2TW_DM: //dir not opened (reported upstream) case E2TW_DL: //ditto case E2TW_DNR: //unreadable directory (for which, error is reported upstream) //touch for this will probably fail, but try anyhow //ensure dir is writable, if we can, don't need X permission if (e2_fs_tw_adjust_dirmode (localpath, statptr, W_OK) == 0) { //failed to set missing W perm //take a shot at doing any change, anyhow, probably fails #ifdef E2_VFS _e2p_acl_copyacls (localpath, statptr, &ddata); #else _e2p_acl_copyacls (localpath, statptr, dest); #endif //FIXME warn user about failure retval = E2TW_FIXME; } else //dir can be processed if (user_data->task & (E2_ACL_DIRAXS | E2_ACL_DIRDEF)) { #ifdef E2_VFS if (!_e2p_acl_copyacls (localpath, statptr, &ddata)) #else if (!_e2p_acl_copyacls (localpath, statptr, dest)) #endif //FIXME warn user about failure retval = E2TW_FIXME; } break; case E2TW_SL: //no mode changes for links case E2TW_SLN: break; case E2TW_F: #ifdef E2_VFS if (!_e2p_acl_copyacls (localpath, statptr, &ddata)) #else if (!_e2p_acl_copyacls (localpath, statptr, dest)) #endif //FIXME warn user about failure retval = E2TW_FIXME; break; case E2TW_NS: //un-statable item (for which, error is reported upstream) retval = E2TW_FIXME; break; default: retval = E2TW_STOP; break; } if (dest != NULL) g_free (dest); if (retval & E2TW_SKIPSUB) user_data->continued_after_problem = TRUE; if (retval & E2TW_FIXME) { user_data->continued_after_problem = TRUE; retval &= ~E2TW_FIXME; //continue after bad item } return retval; } /***************************/ /* execution-related stuff */ /***************************/ /** @brief convert data in @a acl to a shortform acl-string for later use @param acl the data to process, may be NULL @return newly-allocated string, or NULL if acl is NULL or upon error */ static gchar *_e2p_acl_create_mode_string_for_acl (acl_t acl) { gchar *acl_text, *result; if (acl == NULL) return NULL; #ifdef E2ACL_POSIX_EXTENDED acl_text = acl_to_any_text (acl, NULL, ',', TEXT_ABBREVIATE); #else acl_text = acl_to_text (acl, NULL); #endif if (acl_text == NULL) result = NULL; else { #ifdef E2ACL_POSIX_EXTENDED result = g_strdup (acl_text); #else /* condense the string like user::rw- user:lisa:rw- #effective:r-- group::r-- group:444:rw- #effective:r-- mask::r-- other::r-- */ gchar *freeme = e2_utils_str_replace (acl_text, "user:", "u:"); result = freeme; freeme = e2_utils_str_replace (result, "group:", "g:"); g_free (result); result = freeme; freeme = e2_utils_str_replace (result, "mask:", "m:"); g_free (result); result = freeme; freeme = e2_utils_str_replace (result, "other:", "o:"); g_free (result); result = freeme; gchar *s, *p; //eliminate whitespace p = result; while ((s = e2_utils_find_whitespace (p)) != NULL) { p = e2_utils_pass_whitespace (s); if (p == NULL) break; memmove (s, p, strlen(p) + 1); p = s; } //eliminate comments p = result; while ((s = strchr (p, '#')) != NULL) { p++; while (p[0] != '\n' && p[0] != '\0') p++; memmove (s, p, strlen(p) + 1); p = s; } //eliminate newlines p = result; while ((s = strchr (p, '\n')) != NULL) { *p = ','; p++; } #endif acl_free (acl_text); } acl_free (acl); return result; } /** @brief convert ACL data in @a liststore to an array for later use @param store pointer to liststore to process, may be NULL @param rt pointer to dialog data struct @return new array of changes data, or NULL if none found */ static GPtrArray *_e2p_acl_convert_store (GtkListStore *store, E2_ACLDlgRuntime *rt) { GPtrArray *array = NULL; GtkTreeIter iter; if (store != NULL && gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter)) { E2_EntryChangeData *changes; gboolean rd, wr, ex, wh; // gboolean bigwhole = GTK_TOGGLE_BUTTON (rt->change_whole_btn)->active; BAD, can be set in invalid contexts gchar *qualifier, *key; acl_perm_t perms; array = g_ptr_array_sized_new (8); do { changes = ALLOCATE (E2_EntryChangeData); CHECKALLOCATEDWARN (changes, return array;); gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, QUAL, &qualifier, READ, &rd, WRITE, &wr, EXEC, &ex, WHOLE, &wh, SORTKEY, &key, -1); //convert data switch (key[0]) { case '1': //user if (key[1] == '\0') changes->tag = ACL_USER_OBJ; else changes->tag = ACL_USER; break; case '2': //group if (key[1] == '\0') changes->tag = ACL_GROUP_OBJ; else changes->tag = ACL_GROUP; break; case '3': //mask if (key[1] == '\0') changes->tag = ACL_MASK; else changes->tag = ACL_UNDEFINED_TAG; //igonre this bad entry break; case '4': //other if (key[1] == '\0') changes->tag = ACL_OTHER; else changes->tag = ACL_UNDEFINED_TAG; //igonre this bad entry break; default: changes->tag = ACL_UNDEFINED_TAG; //igonre this bad entry break; } if (changes->tag != ACL_UNDEFINED_TAG) { if (qualifier == NULL || *qualifier == '\0') changes->id = ACL_UNDEFINED_ID; else { gchar *idstring = e2_utf8_to_locale (qualifier); if (idstring != NULL) { if (changes->tag == ACL_USER) { struct passwd *pw_buf; if ((pw_buf = getpwnam (idstring)) != NULL) changes->id = (id_t) pw_buf->pw_uid; else { changes->id = (id_t) strtol (idstring, NULL, 10); if (errno == EINVAL) { //FIXME user 0 probably not relevant } } } else { struct group *grp_buf; if ((grp_buf = getgrnam (idstring)) != NULL) changes->id = (id_t) grp_buf->gr_gid; else { changes->id = (id_t) strtol (idstring, NULL, 10); if (errno == EINVAL) { //FIXME group 0 probably not relevant } } } g_free (idstring); } } perms = 0; if (rd) perms |= ACL_READ; if (wr) perms |= ACL_WRITE; if (ex) perms |= ACL_EXECUTE; changes->perms = perms; changes->whole = wh; //|| bigwhole; invalid in many cases g_ptr_array_add (array, changes); } if (qualifier != NULL) g_free (qualifier); g_free (key); } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter)); } return array; } /** @brief verify some ACL data in @a liststore if we can This checks types of entries, but not permissions @param store pointer to liststore to process, may be NULL @param task flags describing the type of change @return TRUE if content is valid, FALSE if invalid NULL store or empty store, or if error in store */ static gboolean _e2p_acl_verify_store (GtkListStore *store, E2_ACLTask task) { /*entry-related rules: MUST HAVES user_obj unless the task is set+system or nuke (those use existing system data) group_obj unless the task is set+system or nuke other unless the task is set+system or nuke PROHIBITED CHANGES user_obj whole addition if there's already one (key "1") user_obj (key "1") whole removal user whole addition if there's one with the same qualifier (= keys "1+..") group_obj whole addition if there's already one (key "2") group_obj (key "2") whole removal group whole addition if there's one with the same qualifier (= keys "2+..") mask whole addition if there's already one (key "3") mask whole (key "3") removal if there's any user (key "1+..") or group (key "2+..") other whole addition if there's already one (key "4") other (key "4") whole removal */ GtkTreeIter iter; if (store != NULL && gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter)) { gboolean retval = TRUE; gboolean user, group, mask, other; //the unique entries gboolean whole; gchar *qualifier, *key; GtkTreeIter srchiter; user = group = mask = other = FALSE; do { gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, QUAL, &qualifier, WHOLE, &whole, SORTKEY, &key, -1); if (key != NULL) { switch (key[0]) { case '1': //user if (key[1] == '\0') //unqualified entry { if (user) retval = FALSE; //duplicate entry found else { retval = (!whole || (task & (E2_ACL_SET | E2_ACL_NUKE))); user = TRUE; } } else //qualified entry if (whole && (task & (E2_ACL_SET | E2_ACL_ADD))) { //check for no duplicated user/qualifier srchiter = iter; //starting here finds itself if (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &srchiter) && e2_tree_find_iter_from_str_same (GTK_TREE_MODEL (store), SORTKEY, key, &srchiter)) retval = FALSE; //CHECKME does the store order have an impact here ? } break; case '2': //group if (key[1] == '\0') { if (group) retval = FALSE; else { retval = (!whole || (task & (E2_ACL_SET | E2_ACL_NUKE))); group = TRUE; } } else if (whole && (task & (E2_ACL_SET | E2_ACL_ADD))) { //check for no duplicated group/qualifier srchiter = iter; //starting here finds itself if (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &srchiter) && e2_tree_find_iter_from_str_same (GTK_TREE_MODEL (store), SORTKEY, key, &srchiter)) retval = FALSE; } break; case '3': //mask if (key[1] == '\0') { if (mask) retval = FALSE; else { retval = (!whole || (task & (E2_ACL_SET | E2_ACL_ADD | E2_ACL_REMOVE))); mask = TRUE; } } else { retval = FALSE; } break; case '4': //other if (key[1] == '\0') { if (other) retval = FALSE; else { retval = (!whole || (task & (E2_ACL_SET | E2_ACL_NUKE))); other = TRUE; } } else { retval = FALSE; } break; default: retval = FALSE; break; } g_free (key); } else retval = FALSE; if (qualifier != NULL) g_free (qualifier); if (!retval) return FALSE; } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter)); if (user && group && other) return TRUE; } return (((task & E2_ACL_NUKE) || (task & (E2_ACL_SET | E2_ACL_SYSTEM)))); } /** @brief find the first entry in @a acl which matches @a type and @a id @param acl the data to scan @param type the type of entry that is wanted @param id UID or GID for a qualified entry, or ACL_UNDEFINED_ID if unqualified @return the matching entry in @a acl, or NULL if no match */ static acl_entry_t _e2p_acl_find_entry (acl_t acl, acl_tag_t type, id_t id) { #if defined (E2ACL_POSIX_EXTENDED) || defined (E2ACL_POSIX_FULL) acl_entry_t entry; # ifdef FOREACH_ACL_ENTRY FOREACH_ACL_ENTRY (entry, acl) # else gint result = acl_get_entry (acl, ACL_FIRST_ENTRY, &entry); while (result == 1) # endif { acl_tag_t thistype; acl_get_tag_type (entry, &thistype); if (type == thistype) { if (id == ACL_UNDEFINED_ID) return entry; else { id_t *this_id; this_id = acl_get_qualifier (entry); if (this_id != NULL) { if (*this_id == id) { acl_free (this_id); return entry; } acl_free (this_id); } } } # ifndef FOREACH_ACL_ENTRY result = acl_get_entry (acl, ACL_NEXT_ENTRY, &entry); # endif } #endif //defined () .... #ifdef E2ACL_POSIX_RESTRICTED # warning _e2p_acl_find_entry() is inoperative #endif return NULL; } /** @brief create an entry in @a acl with parameters in accord with supplied arguments @param aclptr address to store acl which is being updated @param entryptr address to store created entry @param tag the type of entry that is wanted @param id UID or GID for a qualified entry, or ACL_UNDEFINED_ID if unqualified @param perms flags for permissions in the new entry @return TRUE if creation succeeded */ static gboolean _e2p_acl_create_entry (acl_t *aclptr, acl_entry_t *entryptr, acl_tag_t tag, id_t id, acl_perm_t perms) { #if defined (E2ACL_POSIX_EXTENDED) || defined (E2ACL_POSIX_FULL) if (acl_create_entry (aclptr, entryptr)) { //FIXME warn user, clean entry if appropriate return FALSE; } else { gpointer qualifier; acl_permset_t permset; acl_set_tag_type (*entryptr, tag); printd (DEBUG, "_e2p_acl_create_entry - tag %d", tag); if (tag == ACL_USER || tag == ACL_GROUP) { qualifier = acl_get_qualifier (*entryptr); if (qualifier != NULL) { *((id_t *)qualifier) = id; //ok ?? acl_set_qualifier (*entryptr, qualifier); acl_free (qualifier); //?? } else { acl_free (entryptr); return FALSE; } } acl_get_permset (*entryptr, &permset); // if (permset != NULL) // { acl_clear_perms (permset); acl_add_perm (permset, perms); acl_set_permset (*entryptr, permset); acl_free (permset); //?? // } // else // { WARNING; acl_free (entryptr); return FALSE; } return TRUE; } #endif //defined () .... #ifdef E2ACL_POSIX_RESTRICTED # warning _e2p_acl_create_entry() is inoperative return FALSE; #endif } /* * @brief revert an access-ACL to mode-equivalent values or delete default-ACL if one exists for @a localpath @param localpath localised absolute path string of item to process @param type enumerator of type of ACL to be cleared @return 0 if successful, -1 on error */ //CHECKME do both ACL's if item is a dir ?? /*static gint _e2p_acl_delete_acl (gchar *localpath, acl_type_t type) { gint result; if (type == ACL_TYPE_ACCESS) { //convert access-ACL to mode-equivalent form //CHECKME get current mode and explicitly set corresponding perms in retained entries acl_t acl = acl_get_file (localpath, ACL_TYPE_ACCESS); if (acl == NULL) return -1; acl_entry_t entry; acl_tag_t tag; #ifdef FOREACH_ACL_ENTRY FOREACH_ACL_ENTRY (entry, acl) #else result = acl_get_entry (acl, ACL_FIRST_ENTRY, &entry); while (result == 1) #endif { acl_get_tag_type (entry, &tag); switch (tag) { case ACL_USER: case ACL_MASK: case ACL_GROUP: acl_delete_entry (acl, entry); default: break; } #ifndef FOREACH_ACL_ENTRY result = acl_get_entry (acl, ACL_NEXT_ENTRY, &entry); #endif } result = acl_set_file (localpath, ACL_TYPE_ACCESS, acl); } else //want ACL_TYPE_DEFAULT result = acl_delete_def_file (localpath); return result; } */ /** @brief create an acl which conforms to @a mode @param mode mode_t flags as returned by stat() or ~umask() @param external TRUE to migrate the created acl to user-land before returning it @return the created acl or NULL upon error */ static acl_t _e2p_acl_get_for_mode (mode_t mode) //, gboolean external) { #if defined (E2ACL_POSIX_EXTENDED) || defined (E2ACL_POSIX_FULL) # ifdef E2ACL_POSIX_EXTENDED return (acl_from_mode (mode)); /* acl_t acl = acl_from_mode (mode); if (acl == NULL) return NULL; if (!external) return acl; ssize_t bsize = acl_size (acl); gpointer buf = g_try_malloc (bsize * 2); //CHECKME this extra space needed for manipulation ? CHECKALLOCATEDWARN (buf, ) if (buf == NULL) { acl_free (acl); return NULL; } acl_copy_ext (buf, acl, bsize); acl_free (acl); return (acl_t)buf; */ # else acl_t acl; //pointer acl_entry_t entry; //pointer acl_permset_t permset; //pointer acl = acl_init (1); if (acl == NULL) return NULL; if (acl_create_entry (&acl, &entry)) goto failed3; if (acl_get_permset (entry, &permset)) goto failed2; if (mode & S_IRUSR) acl_add_perm (permset, ACL_READ); if (mode & S_IWUSR) acl_add_perm (permset, ACL_WRITE); if (mode & S_IXUSR) acl_add_perm (permset, ACL_EXECUTE); acl_set_permset (entry, permset); acl_set_tag_type (entry, ACL_USER_OBJ); if (acl_create_entry (&acl, &entry)) goto failed; acl_free (permset); acl_free (entry); if (acl_create_entry (&acl, &entry)) goto failed3; if (acl_get_permset (entry, &permset)) goto failed2; if (mode & S_IRGRP) acl_add_perm (permset, ACL_READ); if (mode & S_IWGRP) acl_add_perm (permset, ACL_WRITE); if (mode & S_IXGRP) acl_add_perm (permset, ACL_EXECUTE); acl_set_permset (entry, permset); acl_set_tag_type (entry, ACL_GROUP_OBJ); if (acl_create_entry (&acl, &entry)) goto failed; acl_free (permset); acl_free (entry); if (acl_create_entry (&acl, &entry)) goto failed3; if (acl_get_permset (entry, &permset)) goto failed2; if (mode & S_IROTH) acl_add_perm (permset, ACL_READ); if (mode & S_IWOTH) acl_add_perm (permset, ACL_WRITE); if (mode & S_IXOTH) acl_add_perm (permset, ACL_EXECUTE); acl_set_permset (entry, permset); acl_set_tag_type (entry, ACL_OTHER); if (acl_create_entry (&acl, &entry)) goto failed; acl_free (permset); acl_free (entry); return acl; /* if (!external) return acl; ssize_t bsize = acl_size (acl); gpointer buf = g_try_malloc (bsize * 2); //extra space needed for manipulation ? CHECKALLOCATEDWARN (buf, ) if (buf == NULL) { acl_free (acl); return NULL; } acl_copy_ext (buf, acl, bsize); acl_free (acl); return (acl_t)buf; */ failed: acl_free (permset); failed2: acl_free (entry); failed3: acl_free (acl); return NULL; # endif #endif //defined () .... #ifdef E2ACL_POSIX_RESTRICTED # warning _e2p_acl_get_for_mode() is inoperative return NULL; #endif } /** @brief set ACL of type @a type for @a localpath This assumes BGL is on/closed @param localpath localised absolute path string of item to process @param type enumerator of type of ACL to be applied @param acl the acl to apply @return TRUE if successful */ static gboolean _e2p_acl_apply (gchar *localpath, acl_type_t type, acl_t acl) { #ifdef DISABLEACLCHANGE printd (DEBUG, "application of ACL changes is disabled"); return FALSE; #else if (acl_set_file (localpath, type, acl)) { gchar *typename = (type == ACL_TYPE_ACCESS) ? _("General ACL") : _("Directory ACL"); gchar *stracl = _e2p_acl_create_mode_string_for_acl (acl); if (stracl == NULL) stracl = ""; //this causes hiccup in message gchar *utf = F_DISPLAYNAME_FROM_LOCALE (localpath); gchar *msg = g_strdup_printf (_("Cannot apply %s '%s' for %s"), typename, stracl, utf); e2_output_print_error (msg, TRUE); if (*stracl != '\0') g_free (stracl); F_FREE (utf); return FALSE; printd (DEBUG, "_e2p_acl_apply FAILED"); } printd (DEBUG, "_e2p_acl_apply succeeded"); return TRUE; #endif } /** @brief check that @a acl is ok to apply This is always used immediately before tha application function, and prevents that if the acl is invalid This assumes BGL is on/closed @param localpath item path, localised string, for any error message @param type enumerator of type of ACL @param acl the acl to test @return TRUE if test is passed */ static gboolean _e2p_acl_validate (gchar *localpath, acl_type_t type, acl_t acl) { if (acl_valid (acl)) { gchar *typename = (type == ACL_TYPE_ACCESS) ? _("General ACL") : _("Directory ACL"); gchar *stracl = _e2p_acl_create_mode_string_for_acl (acl); if (stracl == NULL) stracl = ""; gchar *utf = F_DISPLAYNAME_FROM_LOCALE (localpath); gchar *longmsg = g_strdup_printf (_("Cannot apply %s '%s' for %s - Invalid"), typename, stracl, utf); e2_output_print_error (longmsg, TRUE); if (*stracl != '\0') g_free (stracl); F_FREE (utf); return FALSE; } return TRUE; } /** @brief in accord with @a task, apply to @a localpath an acl based only on its system permissions This is called only for nuke task or set/system task when there is no existing acl for @a localpath @param localpath localised absolute path string of item to process @param statptr pointer to stafbuf for @a localpath, filled by lstat() @param type enumerator of the type of acl to use @param task flags dictating type of change to be done @return TRUE if successful */ static gboolean _e2p_acl_apply_basic (gchar *localpath, const struct stat *statptr, acl_type_t type, E2_ACLTask task) { //only call here with (task & E2_ACL_NUKE) OR (task & E2_ACL_SYSTEM) printd (DEBUG, "_e2p_acl_apply_basic: type %d, task %d", type, task); gboolean retval = TRUE; if (!S_ISLNK (statptr->st_mode)) //ignore links { acl_t acl; if (type == ACL_TYPE_ACCESS) { if (!S_ISDIR (statptr->st_mode) || (task & E2_ACL_DIRAXS)) { acl = acl_get_file (localpath, ACL_TYPE_ACCESS); if (acl != NULL || (task & (E2_ACL_SET | E2_ACL_SYSTEM))) { if (acl != NULL) acl_free (acl); acl = _e2p_acl_get_for_mode (statptr->st_mode); if (acl != NULL) { //don't bother with validation ?? //retval = (_e2p_acl_validate (localpath, ACL_TYPE_ACCESS, acl) // && _e2p_acl_apply (localpath, ACL_TYPE_ACCESS, acl)); if (!_e2p_acl_apply (localpath, ACL_TYPE_ACCESS, acl)) retval = FALSE; acl_free (acl); } else retval = FALSE; } } } else if (type == ACL_TYPE_DEFAULT //should never fail && S_ISDIR (statptr->st_mode) //ditto && (task & E2_ACL_DIRDEF)) { if (task & E2_ACL_NUKE) { acl = acl_get_file (localpath, ACL_TYPE_DEFAULT); if (acl != NULL) { acl_free (acl); if (acl_delete_def_file (localpath)) retval = FALSE; } } else //not a nuke { mode_t mode = umask (0); umask (mode); acl = _e2p_acl_get_for_mode (~mode); if (acl != NULL) { //don't bother with validation ?? //retval = (_e2p_acl_validate (localpath, ACL_TYPE_DEFAULT, acl) // && _e2p_acl_apply (localpath, ACL_TYPE_DEFAULT, acl)); if (!_e2p_acl_apply (localpath, ACL_TYPE_DEFAULT, acl)) retval = FALSE; acl_free (acl); } else retval = FALSE; } } } return retval; } /** @brief in accord with @a task, apply to @a localpath an acl based only on @a changedata @param localpath absolute path string, localised, of item to update @param statptr pointer to stafbuf for @a localpath, filled by lstat() @param type enumerator to determine access- or extended-ACL @param task flags dictating the type of change to make @param changedata pointer-array of change-data structs for @a type (possibly NULL) @return TRUE if the procedure was completed successfully */ static gboolean _e2p_acl_apply_shown (gchar *localpath, const struct stat *statptr, acl_type_t type, E2_ACLTask task, GPtrArray *changedata) { /* if type IS ACL_TYPE_ACCESS AND (non-dir OR (dir AND applying axs)) OR if type IS ACL_TYPE_DEFAULT AND dir AND applying def if no existing acl if remove ... do nothing if add or set ... try for new acl, if ok, populate it with array data if existing acl foreach member of array seek match in acl if set ... if found, replace perms (whole is irrelevant) if set ... if NOT found, AND whole-flag, add it whole per change data (can't just add perms) if add ... if found, add perms (whole is irrelevant) if add ... if NOT found, AND whole-flag, add it whole per change data (can't just add perms) if remove ... if found, remove whole or perms if remove ... if NOT found, do nothing if any change, verify acl if ok, apply return TRUE report error return FALSE */ gboolean retval = TRUE; if (!S_ISLNK (statptr->st_mode) && changedata != NULL) { if ( (type == ACL_TYPE_ACCESS && (!S_ISDIR (statptr->st_mode) || (task & E2_ACL_DIRAXS))) || (type == ACL_TYPE_DEFAULT && S_ISDIR (statptr->st_mode) && (task & E2_ACL_DIRDEF)) ) { guint count; acl_entry_t entry; E2_EntryChangeData **iterator; gboolean set, add, remove, dirty; add = (task & E2_ACL_ADD); remove = (add) ? FALSE : (task & E2_ACL_REMOVE); set = !(add || remove) || (task & E2_ACL_SET); acl_t acl = acl_get_file (localpath, type); if (acl == NULL) { printd (DEBUG, "_e2p_acl_apply_shown - NO acl exists"); if (remove) { retval = TRUE; } else { acl = acl_init (1); if (acl == NULL) //MESSAGE ?? retval = FALSE; else { dirty = FALSE; //populate acl from array iterator = (E2_EntryChangeData **) changedata->pdata; for (count = 0; count < changedata->len; count++, iterator++) { if ((*iterator)->whole) { if (_e2p_acl_create_entry (&acl, &entry, (*iterator)->tag, (*iterator)->id, (*iterator)->perms)) dirty = TRUE; else retval = FALSE; } } if (dirty && retval) retval = (_e2p_acl_validate (localpath, type, acl) && _e2p_acl_apply (localpath, type, acl)); //FIXME acl_free (entry); somewhere acl_free (acl); } } } else //acl exists { dirty = FALSE; acl_permset_t permset; iterator = (E2_EntryChangeData **) changedata->pdata; for (count = 0; count < changedata->len; count++, iterator++) { entry = _e2p_acl_find_entry (acl, (*iterator)->tag, (*iterator)->id); if (entry != NULL) { #if defined (E2ACL_POSIX_EXTENDED) || defined (E2ACL_POSIX_FULL) //whole is irrelevant for set, add to existing entry if (set || add || (remove && !(*iterator)->whole)) { acl_get_permset (entry, &permset); if (permset != NULL) { if (set) //replace perms acl_clear_perms (permset); if (set || add) acl_add_perm (permset, (*iterator)->perms); else acl_delete_perm (permset, (*iterator)->perms); acl_set_permset (entry, permset); acl_free (permset); dirty = TRUE; } else { retval = FALSE; } } else //remove whole { if (!acl_delete_entry (acl, entry)) dirty = TRUE; else retval = FALSE; } #endif //defined () .... #ifdef E2ACL_POSIX_RESTRICTED # warning _e2p_acl_apply_shown() is disfunctional #endif } else //no matching entry now { if ((set || add) && (*iterator)->whole) { if (_e2p_acl_create_entry (&acl, &entry, (*iterator)->tag, (*iterator)->id, (*iterator)->perms)) { dirty = TRUE; } else { retval = FALSE; } } } } if (dirty && retval) retval = (_e2p_acl_validate (localpath, type, acl) && _e2p_acl_apply (localpath, type, acl)); //FIXME acl_free (entry); somewhere acl_free (acl); } } } return retval; } /** @brief in accord with @a task, apply to @a localpath an acl based system data modified by @a changedata @param localpath absolute path string, localised, of item to update @param statprt pointer to stafbuf for @a localpath, filled by lstat() @param type enumerator to determine access- or extended-ACL @param task flags dictating the type of change to make @param changedata pointer-array of change-data structs for @a type, to be applied (possibly NULL) @return TRUE if the procedure was completed successfully */ static gboolean _e2p_acl_apply_modified (gchar *localpath, const struct stat *statptr, acl_type_t type, E2_ACLTask task, GPtrArray *changedata) { /* if type IS ACL_TYPE_ACCESS AND (non-dir OR (dir AND applying axs)) AND item's axsacl exists or, if adding, a mode-derived acl can be created OR if type IS ACL_TYPE_DEFAULT AND (dir AND applying def) AND its defacl exists or, if adding, a umask-derived acl can be created get existing acl or, if adding, a new one based on mode|umask foreach member of array seek match in acl if set ... if found, replace perms (whole-flag is irrelevant) if set ... if NOT found, AND whole-flag, add it whole per change data if set ... if NOT found, AND NOT whole-flag, do nothing if add/remove ... if found, change it in part or whole per change data (add perms, remove whole or perms) if add/remove ... if NOT found, do nothing if any change, verify acl if ok, apply return TRUE report error return FALSE */ gboolean retval = TRUE; if (!S_ISLNK (statptr->st_mode) && changedata != NULL) { mode_t mode; gboolean set, add, remove; add = (task & E2_ACL_ADD); remove = (add) ? FALSE : (task & E2_ACL_REMOVE); set = !(add || remove) || (task & E2_ACL_SET); acl_t acl = acl_get_file (localpath, type); if (acl == NULL) { if (add) { if (type == ACL_TYPE_ACCESS || !S_ISDIR (statptr->st_mode)) mode = statptr->st_mode; else { mode = umask (0); umask (mode); mode = ~mode & ALLPERMS; } acl = _e2p_acl_get_for_mode (mode); retval = (acl != NULL); } } if (acl != NULL) { if ( (type == ACL_TYPE_ACCESS && (!S_ISDIR (statptr->st_mode) || (task & E2_ACL_DIRAXS))) || (type == ACL_TYPE_DEFAULT && S_ISDIR (statptr->st_mode) && (task & E2_ACL_DIRDEF)) ) { guint count; acl_entry_t entry; acl_permset_t permset; E2_EntryChangeData **iterator; gboolean dirty = FALSE; iterator = (E2_EntryChangeData **) changedata->pdata; for (count = 0; count < changedata->len; count++, iterator++) { entry = _e2p_acl_find_entry (acl, (*iterator)->tag, (*iterator)->id); if (entry != NULL) { #if defined (E2ACL_POSIX_EXTENDED) || defined (E2ACL_POSIX_FULL) //whole is irrelevant for set, add to existing entry //CHECKME ignore set for sysmod ? if (set || add || (remove && !(*iterator)->whole)) { //replace perms (whole is irrelevant) acl_get_permset (entry, &permset); if (permset != NULL) { //modifying, not replacing if (set) // acl_clear_perms (permset); if (set || add) acl_add_perm (permset, (*iterator)->perms); else acl_delete_perm (permset, (*iterator)->perms); acl_set_permset (entry, permset); acl_free (permset); dirty = TRUE; } else { retval = FALSE; } } else //remove whole { if (!acl_delete_entry (acl, entry)) dirty = TRUE; else retval = FALSE; } #endif //defined () .... #ifdef E2ACL_POSIX_RESTRICTED # warning _e2p_acl_apply_modified() is disfunctional #endif } else //no existing entry { if (set && (*iterator)->whole) { if (_e2p_acl_create_entry (&acl, &entry, (*iterator)->tag, (*iterator)->id, (*iterator)->perms)) dirty = TRUE; else retval = FALSE; } } } if (dirty && retval) retval = (_e2p_acl_validate (localpath, type, acl) && _e2p_acl_apply (localpath, type, acl)); //FIXME acl_free (entry); somewhere } acl_free (acl); } } return retval; } /** @brief apply changes expressed in @a task and @a changes to @a localpath This is used when changing a single item, or recursively changing items. Possible errors: ENOTSUP this ACL operation isn't supported @param localpath absolute path string, localised, of item to update @param statprt pointer to stafbuf for @a localpath, filled by lstat() @param type enumerator to determine access- or extended-ACL @param task flags dictating the type of change to make @param array pointer-array of change-data structs to be applied, possibly NULL @return TRUE if the procedure was completed successfully */ static gboolean _e2p_acl_change1 (gchar *localpath, const struct stat *statptr, acl_type_t type, E2_ACLTask task, GPtrArray *array) { /* mode_t mode = umask (0); umask (mode); acl_t debug = _e2p_acl_get_for_mode (~mode & ALLPERMS, TRUE); if (debug == NULL) printd (DEBUG, "external acl creation failed"); else { printd (DEBUG, "external acl creation succeeded"); g_free (debug); } debug = _e2p_acl_get_for_mode (~mode, FALSE); if (debug == NULL) printd (DEBUG, "internal acl creation failed"); else { printd (DEBUG, "internal acl creation succeeded"); acl_free (debug); } */ gboolean retval; //some tasks use basic data only if (task & E2_ACL_NUKE) retval = _e2p_acl_apply_basic (localpath, statptr, type, task); //add/system and remove/system are invalid options //(should never happen, and ignored if they do) // else if (task & (E2_ACL_SET | E2_ACL_SYSTEM)) else if (task & E2_ACL_SYSTEM) { /* acl_t acl = acl_get_file (localpath, type); if (acl == NULL) { */ retval = _e2p_acl_apply_basic (localpath, statptr, type, task); /* } else { //no point in setting things that are already set acl_free (acl); retval = TRUE; } */ } else if (task & E2_ACL_SHOWN) retval = _e2p_acl_apply_shown (localpath, statptr, type, task, array); else if (task & E2_ACL_SYSMOD) //should never fail retval = _e2p_acl_apply_modified (localpath, statptr, type, task, array); else retval = TRUE; return retval; } /** @brief callback function for recursive directory ACL-change Tree is being walked breadth-first, not physical. Dirs are made accessible and writable if not already so and it's permitted, dirs are added to a list to be processed after all the tree has been traversed, other items are changed as requested (if possible) Error messages assume BGL is open/off @param localpath absolute path of item to change, localised string @param statptr pointer to struct stat with info about @a localpath @param status code from the walker, indicating what type of report it is @param user_data pointer to user-specified data @return E2TW_CONTINUE on success, others as appropriate */ static E2_TwResult _e2p_acl_twcb_chacl (VPATH *localpath, const struct stat *statptr, E2_TwStatus status, E2_ChACLData *user_data) { E2_TwResult retval = E2TW_CONTINUE; //default error code = none switch (status) { E2P_DirEnt *dirfix; #ifdef E2_VFS VPATH ddata; ddata.spacedata = localpath->spacedata; #endif GList *member; case E2TW_DP: //chacl dir for its new mode, cleanup for (member = g_list_last (user_data->dirdata); member != NULL; member = member->prev) { dirfix = member->data; if (dirfix != NULL) { if (g_str_equal (dirfix->path, localpath)) { if (user_data->task & (E2_ACL_DIRAXS | E2_ACL_DIRDEF)) { gboolean dirsuccess; if (user_data->task & E2_ACL_DIRAXS) dirsuccess = _e2p_acl_change1 (dirfix->path, statptr, ACL_TYPE_ACCESS, user_data->task, user_data->axs_changes); else dirsuccess = TRUE; if (user_data->task & E2_ACL_DIRDEF) dirsuccess = dirsuccess && _e2p_acl_change1 (dirfix->path, statptr, ACL_TYPE_DEFAULT, user_data->task, user_data->def_changes); if (!dirsuccess) user_data->continued_after_problem = TRUE; } else { //just reinstate original mode E2_ERR_DECLARE #ifdef E2_VFS ddata.localpath = dirfix->path; if (e2_fs_chmod (&ddata, dirfix->mode E2_ERR_PTR()) #else if (e2_fs_chmod (dirfix->path, dirfix->mode E2_ERR_PTR()) #endif && E2_ERR_ISNOT (ENOENT)) //FIXME vfs user_data->continued_after_problem = TRUE; E2_ERR_CLEAR } g_free (dirfix->path); DEMALLOCATE (E2P_DirEnt, dirfix); user_data->dirdata = g_list_delete_link (user_data->dirdata, member); break; } } // else //should never happen CHECKME ok when walking list ? // user_data->dirdata = g_list_delete_link (user_data->dirdata, member); } break; case E2TW_DRR: //directory now readable retval |= E2TW_DRKEEP; //no permission reversion in walker case E2TW_D: //ensure dir is writable, if we can if (e2_fs_tw_adjust_dirmode (localpath, statptr, (W_OK | X_OK)) == 0) { //failed to set missing W and/or X perm //take a shot at doing any change, anyhow, probably fails if (user_data->task & E2_ACL_DIRAXS) _e2p_acl_change1 ((gchar *)localpath, statptr, ACL_TYPE_ACCESS, user_data->task, user_data->axs_changes); if (user_data->task & E2_ACL_DIRDEF) _e2p_acl_change1 ((gchar *)localpath, statptr, ACL_TYPE_DEFAULT, user_data->task, user_data->def_changes); //FIXME warn user if either change fails retval |= E2TW_SKIPSUB; //don't try to do any descendant } else //dir can be processed { //add this dir to list of items to chacl afterwards dirfix = MALLOCATE (E2P_DirEnt); CHECKALLOCATEDWARNT (dirfix, retval=E2TW_STOP;break;) dirfix->path = g_strdup (VPSTR (localpath)); dirfix->mode = statptr->st_mode & ALLPERMS; //may want to restore the original value user_data->dirdata = g_list_append (user_data->dirdata, dirfix); } break; case E2TW_DM: //dir not opened (reported upstream) case E2TW_DL: //ditto case E2TW_DNR: //unreadable directory (for which, error is reported upstream) //touch for this will probably fail, but try anyhow //ensure dir is writable, if we can, don't need X permission if (e2_fs_tw_adjust_dirmode (localpath, statptr, W_OK) == 0) { //failed to set missing W perm //take a shot at doing any change, anyhow, probably fails if (user_data->task & E2_ACL_DIRAXS) _e2p_acl_change1 ((gchar *)localpath, statptr, ACL_TYPE_ACCESS, user_data->task, user_data->axs_changes); if (user_data->task & E2_ACL_DIRDEF) _e2p_acl_change1 ((gchar *)localpath, statptr, ACL_TYPE_DEFAULT, user_data->task, user_data->def_changes); //FIXME warn user about failure retval = E2TW_FIXME; } else //dir can be processed if (user_data->task & (E2_ACL_DIRAXS | E2_ACL_DIRDEF)) { gboolean dirsuccess; if (user_data->task & E2_ACL_DIRAXS) dirsuccess = _e2p_acl_change1 ((gchar *)localpath, statptr, ACL_TYPE_ACCESS, user_data->task, user_data->axs_changes); else dirsuccess = TRUE; if (user_data->task & E2_ACL_DIRDEF) dirsuccess = dirsuccess && _e2p_acl_change1 ((gchar *)localpath, statptr, ACL_TYPE_DEFAULT, user_data->task, user_data->def_changes); if (!dirsuccess) //FIXME warn user about failure retval = E2TW_FIXME; } break; case E2TW_SL: //no mode changes for links case E2TW_SLN: break; case E2TW_F: if (user_data->task & E2_ACL_OTHER) { if (!_e2p_acl_change1 ((gchar *)localpath, statptr, ACL_TYPE_ACCESS, user_data->task, user_data->axs_changes)) //FIXME warn user about failure retval = E2TW_FIXME; } break; case E2TW_NS: //un-statable item (for which, error is reported upstream) retval = E2TW_FIXME; break; default: retval = E2TW_STOP; break; } if (retval & E2TW_SKIPSUB) user_data->continued_after_problem = TRUE; if (retval & E2TW_FIXME) { user_data->continued_after_problem = TRUE; retval &= ~E2TW_FIXME; //continue after bad item } return retval; } /** @brief change extended permissions of item @a path to @a mode Only an item's owner (as judged by the effective uid of the process) or a privileged user, can change item permissions. If invoked on a non-dir, or a dir without recursion, it is processed here. If recursion is invoked on a dir, a ntfw funtion is invoked. By that, all nested dir access permissons will be set to include x, non-dir items will be have their new permissions set when they are 'reported'. Finally, here, dirs will be set to their proper permissions (bottom-up order) at the end of the process Recursive chmod works on the host filesystem only. Links are not affected, their target is processed. (OK ??) Assumes BGL is open @param localpath absolute path of item to process, localised string @param axs_changes array of change-data for access-ACL, or NULL @param def_changes array of change-data for default-ACL, or NULL @param task flags indicating the way the operation is to be done @return TRUE if operation succeeds */ static gboolean _e2p_acl_change (VPATH *localpath, GPtrArray *axs_changes, GPtrArray *def_changes, E2_ACLTask task) { gboolean retval; E2_ChACLData data; struct stat statbuf; E2_ERR_DECLARE if (!(task & E2_ACL_NODOWN)) { //decide whether src is a dir or not if (e2_fs_stat (localpath, &statbuf E2_ERR_PTR())) //looks _through_ links { //abort if we can't find the item e2_fs_error_local (_("Cannot get information about %s"), localpath E2_ERR_MSGL()); E2_ERR_CLEAR return FALSE; } if (S_ISDIR (statbuf.st_mode)) { //recursive chacl //park parameters where they can be accessed by helpers data.continued_after_problem = FALSE; data.task = task; data.axs_changes = axs_changes; data.def_changes = def_changes; data.dirdata = NULL; //no processed dirs yet retval = e2_fs_tw (localpath, _e2p_acl_twcb_chacl, &data, -1, //flags for: no thru-links, this filesystem only, breadth-first E2TW_MOUNT | E2TW_PHYS E2_ERR_NONE()); if (data.dirdata != NULL) { //change/revert dir permissions, LIFO (=bottom-up) order GList *member; for (member = g_list_last (data.dirdata); member!= NULL; member = member->prev) { gboolean dirsuccess; E2P_DirEnt *dirfix = member->data; if (task & (E2_ACL_DIRAXS | E2_ACL_DIRDEF)) { if (task & E2_ACL_DIRAXS) dirsuccess = _e2p_acl_change1 (dirfix->path, &statbuf, ACL_TYPE_ACCESS, task, axs_changes); else dirsuccess = TRUE; if (task & E2_ACL_DIRDEF) dirsuccess = dirsuccess && _e2p_acl_change1 (dirfix->path, &statbuf, ACL_TYPE_DEFAULT, task, def_changes); if (!dirsuccess) data.continued_after_problem = TRUE; } else { //just reinstate original mode #ifdef E2_VFS VPATH ddata = { dirfix->path, localpath->spacedata }; if (e2_fs_chmod (&ddata, dirfix->mode E2_ERR_PTR()) #else if (e2_fs_chmod (dirfix->path, dirfix->mode E2_ERR_PTR()) #endif && E2_ERR_ISNOT (ENOENT)) //FIXME vfs data.continued_after_problem = TRUE; } g_free (dirfix->path); DEMALLOCATE (E2P_DirEnt, dirfix); } g_list_free (data.dirdata); } if (!retval) { //FIXME handle error E2_ERR_CLEAR } if (data.continued_after_problem) { e2_fs_error_simple (_("Cannot change permission(s) of all of %s"), localpath); retval = FALSE; } return retval; } else //not dir, handle without recurse task |= E2_ACL_NODOWN; } if (task & E2_ACL_NODOWN) { if (e2_fs_lstat (localpath, &statbuf E2_ERR_PTR())) //no link pass-thru { e2_fs_error_local (_("Cannot get information about %s"), localpath E2_ERR_MSGL()); E2_ERR_CLEAR return FALSE; } if (S_ISLNK (statbuf.st_mode)) { return FALSE; //link permissions can't be changed } retval = _e2p_acl_change1 (VPSTR (localpath), &statbuf, ACL_TYPE_ACCESS, task, axs_changes); if (S_ISDIR (statbuf.st_mode) && def_changes != NULL) retval = retval && _e2p_acl_change1 (VPSTR (localpath), &statbuf, ACL_TYPE_DEFAULT, task, def_changes); return retval; } return FALSE; //warning prevention only } /** @brief cleanup item in array of change-data @param data pointer to array element @param user_data UNUSED pointer to user-specified data @return */ static void _e2p_acl_clean_changes (E2_EntryChangeData *data, gpointer user_data) { DEALLOCATE (E2_EntryChangeData, data); data = NULL; } /**********************/ /** UI-related stuff **/ /**********************/ /** @brief ensure row for @a iter is visible in the displayed part of @a tvw @param tvw pointer to treeview @param iter pointer to data for row to be focused, in @a tvw @return TRUE if the view was scrolled */ static gboolean _e2p_acl_show_row (GtkTreeView *tvw, GtkTreeIter *iter) { #ifdef USE_GTK2_8 gboolean scroll = FALSE; GtkTreeModel *model = gtk_tree_view_get_model (tvw); GtkTreePath *tpath, *start_path, *end_path; if (gtk_tree_view_get_visible_range (tvw, &start_path, &end_path)) { tpath = gtk_tree_model_get_path (model, iter); if (tpath != NULL) { if (gtk_tree_path_compare (tpath, start_path) == -1 || gtk_tree_path_compare (tpath, end_path) >= 0) { scroll = TRUE; gtk_tree_view_scroll_to_cell (tvw, tpath, NULL, FALSE, 0, 0); gtk_tree_view_set_cursor (tvw, tpath, NULL, FALSE); } gtk_tree_path_free (tpath); } gtk_tree_path_free (start_path); gtk_tree_path_free (end_path); } #else GtkTreeModel *model = gtk_tree_view_get_model (tvw); GtkTreePath *tpath, *check_path; tpath = gtk_tree_model_get_path (model, iter); if (tpath == NULL) return FALSE; GdkRectangle vis_rect; gtk_tree_view_get_visible_rect (tvw, &vis_rect); gboolean scroll = FALSE; gint wx, wy; //#ifdef USE_GTK2_12 useless if not USE_GTK2_12 // gtk_tree_view_convert_tree_to_widget_coords //#else gtk_tree_view_tree_to_widget_coords //#endif (tvw, vis_rect.x, vis_rect.y, &wx, &wy); gtk_tree_view_get_path_at_pos (tvw, wx, wy, &check_path, NULL, NULL, NULL); if (check_path != NULL) { if (gtk_tree_path_compare (tpath, check_path) == -1) scroll = TRUE; gtk_tree_path_free (check_path); } if (!scroll) { /*#ifdef USE_GTK2_12 gtk_tree_view_convert_tree_to_widget_coords #else gtk_tree_view_tree_to_widget_coords #endif (tvw, vis_rect.x, vis_rect.y + vis_rect.height, &wx, &wy); */ wy += vis_rect.height; gtk_tree_view_get_path_at_pos (tvw, wx, wy, &check_path, NULL, NULL, NULL); if (check_path != NULL) { if (gtk_tree_path_compare (tpath, check_path) >= 0) scroll = TRUE; gtk_tree_path_free (check_path); } } if (scroll) { gtk_tree_view_scroll_to_cell (tvw, tpath, NULL, FALSE, 0, 0); gtk_tree_view_set_cursor (tvw, tpath, NULL, FALSE); } gtk_tree_path_free (tpath); #endif return scroll; } /** @brief get class, user/group names for combo renderers in the qaalifier column of the treeview @param rt pointer to dialog data struct @return */ static void _e2p_acl_fill_combo_models (E2_ACLDlgRuntime *rt) { GtkTreeIter iter; if (rt->classes == NULL) { rt->classes = gtk_list_store_new (1, G_TYPE_STRING, -1); guint i; for (i = 0; i < CLASSCOUNT; i++) { gtk_list_store_insert_with_values (rt->classes, &iter, -1, 0, (classinames [i]), -1); } } if (rt->users == NULL) { rt->users = gtk_list_store_new (1, G_TYPE_STRING, -1); gchar *utf; struct passwd *pw_buf; struct group *grp_buf; gint myuid = getuid (); setpwent (); while ((pw_buf = getpwent ()) != NULL) { //screen out superfluous names if ( (myuid > 0) //user is not root && ((guint) pw_buf->pw_uid > 0) //buffer entry is not root's && ((guint) pw_buf->pw_uid < UID_LOWLIMIT) //but is less than the system-user threshold ) continue; //ignore it utf = e2_utf8_from_locale (pw_buf->pw_name); if (utf == NULL) utf = g_strdup_printf ("%d", (guint) pw_buf->pw_uid); gtk_list_store_insert_with_values (rt->users, &iter, -1, 0, utf, -1); g_free (utf); } setpwent (); setgrent (); if (myuid == 0) { //root user sees all groups while ((grp_buf = getgrent ()) != NULL) { //FIXME duplicates utf = e2_utf8_from_locale (grp_buf->gr_name); if (utf == NULL) utf = g_strdup_printf ("%d", (guint) grp_buf->gr_gid); gtk_list_store_insert_with_values (rt->users, &iter, -1, 0, utf, -1); g_free (utf); } } else //not root { gid_t grp_ids[REPORTABLE_GROUPS]; gint n = getgroups (REPORTABLE_GROUPS, grp_ids); gint ctr; for (ctr = 0; ctr < n; ctr++) { if ((grp_buf = getgrgid (grp_ids[ctr])) != NULL) { utf = e2_utf8_from_locale (grp_buf->gr_name); if (utf == NULL) utf = g_strdup_printf ("%d", (guint) grp_buf->gr_gid); gtk_list_store_insert_with_values (rt->users, &iter, -1, 0, utf, -1); g_free (utf); } } } setgrent (); } } /** @brief fill the dialog liststore with ACL data, if any, of the current item @param store pointer to liststore to be populated @param acl ACL holding data to be parsed (may be NULL) @return */ static void _e2p_acl_fill_store (GtkListStore *store, acl_t acl) { #ifdef FAKEACLSTORE GtkTreeModel *mdl = GTK_TREE_MODEL (store); GtkTreeIter iter; gtk_list_store_insert_with_values (store, &iter, -1, CLASS, _("User"), READ, TRUE, WRITE, TRUE, EXEC, FALSE, -1); _e2p_acl_fill_sortkey (mdl, &iter); gtk_list_store_insert_with_values (store, &iter, -1, CLASS, _("Group"), READ, TRUE, WRITE, FALSE, EXEC, FALSE, -1); _e2p_acl_fill_sortkey (mdl, &iter); gtk_list_store_insert_with_values (store, &iter, -1, CLASS, _("Mask"), READ, FALSE, WRITE, FALSE, EXEC, FALSE, -1); _e2p_acl_fill_sortkey (mdl, &iter); gtk_list_store_insert_with_values (store, &iter, -1, CLASS, _("Other"), READ, FALSE, WRITE, TRUE, EXEC, FALSE, -1); _e2p_acl_fill_sortkey (mdl, &iter); #else if (acl != NULL) { //NOTE this approach may not be greatly portable ... acl_entry_t entry; acl_permset_t permset; acl_tag_t tag; gint rd, wr, ex; //NOTE linux function may return TRUE, not necessarily +1, when flag is set gchar *class, *name; struct passwd *pw; struct group *grp_buf; GtkTreeIter iter; GtkTreeModel *mdl = GTK_TREE_MODEL (store); # if defined (E2ACL_POSIX_EXTENDED) || defined (E2ACL_POSIX_FULL) # ifdef FOREACH_ACL_ENTRY FOREACH_ACL_ENTRY (entry, acl) # else gint result = acl_get_entry (acl, ACL_FIRST_ENTRY, &entry); while (result == 1) # endif { acl_get_tag_type (entry, &tag); switch (tag) { case ACL_USER_OBJ: class = classinames[0]; name = ""; break; case ACL_USER: class = classinames[0]; //get the qualifier uid_t *UID = (uid_t *) acl_get_qualifier (entry); if ((pw = getpwuid (*UID)) != NULL) name = e2_utf8_from_locale (pw->pw_name); else name = NULL; if (name == NULL) //ascii number, no need to convert to utf-8 name = g_strdup_printf ("%d", (guint) *UID); acl_free (UID); break; case ACL_GROUP_OBJ: class = classinames[1]; name = ""; break; case ACL_GROUP: class = classinames[1]; //get the qualifier gid_t *GID = (gid_t *) acl_get_qualifier (entry); if ((grp_buf = getgrgid (*GID)) != NULL) name = e2_utf8_from_locale (grp_buf->gr_name); else name = NULL; if (name == NULL) //ascii number, no need to convert to utf-8 name = g_strdup_printf ("%d", (guint) *GID); acl_free (GID); break; case ACL_MASK: class = classinames[2]; name = ""; break; case ACL_OTHER: class = classinames[3]; name = ""; break; default: class = NULL; //prevent inclusion name = NULL; //warning prevention break; } if (class != NULL) { acl_get_permset (entry, &permset); rd = ACL_GET_PERM (permset, ACL_READ); if (rd == -1) //watch out if the func returns TRUE!! { //FIXME handle error rd = 0; } wr = ACL_GET_PERM (permset, ACL_WRITE); if (wr == -1) { //FIXME handle error wr = 0; } ex = ACL_GET_PERM (permset, ACL_EXECUTE); if (ex == -1) { //FIXME handle error ex = 0; } //store them ... gtk_list_store_insert_with_values (store, &iter, -1, CLASS, class, QUAL, name, READ, rd, WRITE, wr, EXEC, ex, -1); _e2p_acl_fill_sortkey (mdl, &iter); if (*name != '\0') g_free (name); } # ifndef FOREACH_ACL_ENTRY result = acl_get_entry (acl, ACL_NEXT_ENTRY, &entry); # endif } # endif //defined () .... # ifdef E2ACL_POSIX_RESTRICTED # warning _e2p_acl_fill_store() is inoperative # endif } #endif //def FAKEACLSTORE } /** @brief setup scrolled window with treeview and empty liststore @param type enumerator for general or default data @param rt pointer to dialog data struct @return the scrolled window widget */ static GtkWidget *_e2p_acl_create_view (acl_type_t type, E2_ACLDlgRuntime *rt) { GtkTreeSortable *sortable; if (type == ACL_TYPE_ACCESS || rt->thisis_dir) { rt->store = gtk_list_store_new (MAXACLCOLS, G_TYPE_STRING, //CLASS G_TYPE_STRING, //QUAL G_TYPE_BOOLEAN, //READ G_TYPE_BOOLEAN, //WRITE G_TYPE_BOOLEAN, //EXEC G_TYPE_BOOLEAN, //WHOLE G_TYPE_STRING, //SORTKEY (not displayed) -1); sortable = GTK_TREE_SORTABLE (rt->store); //start without sorting // gtk_tree_sortable_set_sort_column_id (sortable, // GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING); gtk_tree_sortable_set_sort_func (sortable, SORTKEY, (GtkTreeIterCompareFunc) _e2p_acl_view_sort, NULL, NULL); gtk_tree_sortable_set_sort_column_id (sortable, SORTKEY, GTK_SORT_ASCENDING); if (type == ACL_TYPE_ACCESS) rt->axs_store = rt->store; else rt->def_store = rt->store; } else //should never happen return NULL; //get class, user/group names for combo renderers (only works once) _e2p_acl_fill_combo_models (rt); rt->treeview = gtk_tree_view_new (); if (type == ACL_TYPE_ACCESS) rt->axs_view = rt->treeview; else rt->def_view = rt->treeview; //set general treeview properties gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (rt->treeview), TRUE); GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (rt->treeview)); gtk_tree_selection_set_mode (sel, GTK_SELECTION_BROWSE); //set columns' properties guint i; gchar *name; gchar *fontstr = (e2_option_bool_get ("custom-list-font")) ? e2_option_str_get ("list-font") : NULL; //NULL will cause default font GtkCellRenderer *renderer; GtkTreeViewColumn *column; for (i = CLASS; i < VISCOLS; i++) { if (i < READ) { renderer = gtk_cell_renderer_combo_new (); if (i == CLASS) g_object_set (G_OBJECT (renderer), "model", rt->classes, "has-entry", FALSE, NULL); else g_object_set (G_OBJECT (renderer), "model", rt->users, NULL); g_object_set (G_OBJECT (renderer), "text-column", 0, "editable", TRUE, "yalign", 0.0, "font", fontstr, NULL); if (rt->permission) { g_signal_connect (G_OBJECT (renderer), "editing-started", G_CALLBACK (_e2p_acl_cell_edit_start_cb), rt); g_signal_connect (G_OBJECT (renderer), "editing-canceled", G_CALLBACK (_e2p_acl_cell_edit_stop_cb), rt); g_signal_connect (G_OBJECT (renderer), "edited", G_CALLBACK (_e2p_acl_cell_edited_cb), rt); } column = gtk_tree_view_column_new_with_attributes ( colnames[i], //name renderer, "text", i, NULL); } else { name = gettext (colnames[i]); renderer = gtk_cell_renderer_toggle_new (); g_object_set (G_OBJECT (renderer), "activatable", TRUE, // "indicator-size", 15, "xalign", 0.5, "yalign", 0.0, NULL); if (rt->permission) g_signal_connect (G_OBJECT (renderer), "toggled", G_CALLBACK (_e2p_acl_toggle_cb), rt); column = gtk_tree_view_column_new_with_attributes ( name, renderer, // "alignment", 0.5, do this later, here it crashes "active", i, NULL); } g_object_set_data (G_OBJECT (renderer), "column", GUINT_TO_POINTER (i)); gtk_tree_view_column_set_resizable (column, TRUE); gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_GROW_ONLY); // if (i < READ) gtk_tree_view_column_set_expand (column, TRUE); // else if (i >= READ) gtk_tree_view_column_set_alignment (column, 0.5); gtk_tree_view_append_column (GTK_TREE_VIEW (rt->treeview), column); } // gtk_tree_sortable_set_sort_column_id (sortable, SORTKEY, GTK_SORT_ASCENDING); gtk_tree_view_set_model (GTK_TREE_VIEW (rt->treeview), GTK_TREE_MODEL (rt->store)); g_object_unref (G_OBJECT (rt->store)); gtk_widget_show (rt->treeview); GtkWidget *sw = e2_widget_get_sw (GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, GTK_SHADOW_ETCHED_IN); gtk_scrolled_window_set_placement (GTK_SCROLLED_WINDOW (sw), e2_option_int_get ("scrollbar-position")); gtk_widget_set_size_request (sw, -1, 95); //minimum height about enough for 3 rows + headers gtk_widget_show (sw); gtk_container_add (GTK_CONTAINER (sw), rt->treeview); return sw; } /** @brief change or set fields in @a store to reflect @a mode or @a acl wrx fields match settings in @a mode or @a acl, whole field = FALSE, sortkey field consistent with class and qualifier @param store the liststore to be updated @param basic TRUE if the permissions are to be strictly in accord with @a mode @param mode flags as returned by stat() or ~umask() @param acl pointer (maybe NULL) to data struct to replicate in @a store, if @a basic is FALSE @return */ static void _e2p_acl_reset_mode_fields (GtkListStore *store, gboolean basic, mode_t mode, acl_t acl) { gboolean rd, wr, ex; gboolean user, grp, other; gchar *key; GtkTreeModel *model; GtkTreeIter iter; if (store == NULL) //should never happen return; model = GTK_TREE_MODEL (store); if (gtk_tree_model_get_iter_first (model, &iter)) { user = grp = other = FALSE; //flags to handle duplicates if (basic || acl == NULL) { do { reloop: gtk_tree_model_get (model, &iter, SORTKEY, &key, -1); //revert to mode-compatible settings if (key[0] == '1' && key[1] == '\0') //not an extended user entry { if (user) { g_free (key); if (gtk_list_store_remove (store, &iter)) goto reloop; //no need to get next iter else break; } else { user = TRUE; rd = ((mode & S_IRUSR) > 0); wr = ((mode & S_IWUSR) > 0); ex = ((mode & S_IXUSR) > 0); } } else if (key[0] == '2' && key[1] == '\0') //group { if (grp) { g_free (key); if (gtk_list_store_remove (store, &iter)) goto reloop; //no need to get next iter else break; } else { grp = TRUE; rd = ((mode & S_IRGRP) > 0); wr = ((mode & S_IWGRP) > 0); ex = ((mode & S_IXGRP) > 0); } } else if (key[0] == '4' && key[1] == '\0') //other (should never be qualfied, but user may have made mistake) { if (other) { g_free (key); if (gtk_list_store_remove (store, &iter)) goto reloop; //no need to get next iter else break; } else { other = TRUE; rd = ((mode & S_IROTH) > 0); wr = ((mode & S_IWOTH) > 0); ex = ((mode & S_IXOTH) > 0); } } else { g_free (key); if (gtk_list_store_remove (store, &iter)) goto reloop; //no need to get next iter else break; } gtk_list_store_set (store, &iter, READ, rd, WRITE, wr, EXEC, ex, WHOLE, FALSE, -1); g_free (key); } while (gtk_tree_model_iter_next (model, &iter)); } else //use ACL values { //revert to current values gtk_list_store_clear (store); _e2p_acl_fill_store (store, acl); //confirm that user, grp, other entries are present acl_entry_t entry; acl_tag_t tag; #if defined (E2ACL_POSIX_EXTENDED) || defined (E2ACL_POSIX_FULL) # ifdef FOREACH_ACL_ENTRY FOREACH_ACL_ENTRY (entry, acl) # else gint result = acl_get_entry (acl, ACL_FIRST_ENTRY, &entry); while (result == 1) # endif { acl_get_tag_type (entry, &tag); switch (tag) { case ACL_USER_OBJ: user = TRUE; break; case ACL_GROUP_OBJ: grp = TRUE; break; case ACL_OTHER: other = TRUE; default: break; } # ifndef FOREACH_ACL_ENTRY result = acl_get_entry (acl, ACL_NEXT_ENTRY, &entry); # endif } #endif //defined () .... # ifdef E2ACL_POSIX_RESTRICTED # warning _e2p_acl_reset_mode_fields() is disfunctional # endif } //ensure no gaps if (!user) { rd = ((mode & S_IRUSR) > 0); wr = ((mode & S_IWUSR) > 0); ex = ((mode & S_IXUSR) > 0); gtk_list_store_insert_with_values (store, &iter, -1, CLASS, classinames [0], QUAL, "", READ, rd, WRITE, wr, EXEC, ex, WHOLE, TRUE, SORTKEY, "1", -1); } if (!grp) { rd = ((mode & S_IRGRP) > 0); wr = ((mode & S_IWGRP) > 0); ex = ((mode & S_IXGRP) > 0); gtk_list_store_insert_with_values (store, &iter, -1, CLASS, classinames [1], QUAL, "", READ, rd, WRITE, wr, EXEC, ex, WHOLE, TRUE, SORTKEY, "2", -1); } if (!other) { rd = ((mode & S_IROTH) > 0); wr = ((mode & S_IWOTH) > 0); ex = ((mode & S_IXOTH) > 0); gtk_list_store_insert_with_values (store, &iter, -1, CLASS, classinames [3], QUAL, "", READ, rd, WRITE, wr, EXEC, ex, WHOLE, TRUE, SORTKEY, "4", -1); } } else //store is empty { //populate it //CHECKME manage sorting during additions if (basic) { rd = ((mode & S_IRUSR) > 0); wr = ((mode & S_IWUSR) > 0); ex = ((mode & S_IXUSR) > 0); gtk_list_store_insert_with_values (store, &iter, -1, CLASS, classinames [0], QUAL, "", READ, rd, WRITE, wr, EXEC, ex, WHOLE, FALSE, SORTKEY, "1", -1); rd = ((mode & S_IRGRP) > 0); wr = ((mode & S_IWGRP) > 0); ex = ((mode & S_IXGRP) > 0); gtk_list_store_insert_with_values (store, &iter, -1, CLASS, classinames [1], QUAL, "", READ, rd, WRITE, wr, EXEC, ex, WHOLE, FALSE, SORTKEY, "2", -1); rd = ((mode & S_IROTH) > 0); wr = ((mode & S_IWOTH) > 0); ex = ((mode & S_IXOTH) > 0); gtk_list_store_insert_with_values (store, &iter, -1, CLASS, classinames [3], QUAL, "", READ, rd, WRITE, wr, EXEC, ex, WHOLE, FALSE, SORTKEY, "4", -1); } else if (acl != NULL) { _e2p_acl_fill_store (store, acl); } } } /** @brief set or clear the 'whole' field of all rows in @a store @param store pointer to liststore to be updated @param rt pointer to dialog data struct @return */ static void _e2p_acl_reset_whole_fields (GtkListStore *store, E2_ACLDlgRuntime *rt) { GtkTreeIter iter; if (store != NULL && gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter)) { gchar *key; //some whole changes are valid for set, not for add or remove gboolean valid; gboolean setaction = GTK_TOGGLE_BUTTON (rt->set_data_btn)->active || GTK_TOGGLE_BUTTON (rt->remove_all_btn)->active; gboolean remaction = GTK_TOGGLE_BUTTON (rt->remove_data_btn)->active; do { gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, SORTKEY, &key, -1); if (key != NULL) { switch (key[0]) { case '1': //user //can always add/remove qualified user entries valid = setaction || key[1] != '\0'; break; case '2': //group //can always add/remove qualified group entries valid = setaction || key[1] != '\0'; break; case '3': //mask //can remove mask as well as set valid = setaction || remaction; break; case '4': //other //can only set other valid = setaction; break; default: valid = FALSE; break; } g_free (key); gtk_list_store_set (store, &iter, WHOLE, valid, -1); } } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter)); } } /** @brief construct and store a sortkey string for the row represented by @a iter; This interprets the contents of CLASS field and uses corresponding classorder[] value to produce the expected order. Any appended qualifier from the QUAL field may be (ascii) number or (utf-8) name @param model the gtktreemodel to which @a iter applies @param iter pointer to liststoer iter of row to update @return */ static void _e2p_acl_fill_sortkey (GtkTreeModel *model, GtkTreeIter *iter) { gchar *class, *qualifier, *key; gtk_tree_model_get (model, iter, CLASS, &class, QUAL, &qualifier, -1); if (class != NULL) { guint i; for (i = 0; i < CLASSCOUNT; i++) { if (g_str_equal (classinames[i], class)) { if (qualifier == NULL || *qualifier == '\0') key = g_strdup (classorder[i]); else key = g_strconcat (classorder[i], qualifier, NULL); gtk_list_store_set (GTK_LIST_STORE (model), iter, SORTKEY, key, -1); g_free (key); break; } } g_free (class); } if (qualifier != NULL) g_free (qualifier); } /** @brief sort-order comparison function This sorts liststore items in ACL-consistent order (user>group>mask>other) by analysing SORTKEY field data in both rows. @param model the data model to be interrogated @param a pointer to model iter for the first row to be compared @param b pointer to model iter for the second row to be compared @param user_data UNUSED data specified when the sort function was assigned @return integer <0, 0 or >0 according to whether a belongs before, = or after b */ static gint _e2p_acl_view_sort (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data) { gint result; gchar *keya; gchar *keyb; gtk_tree_model_get (model, a, SORTKEY, &keya, -1); gtk_tree_model_get (model, b, SORTKEY, &keyb, -1); if (keya == NULL) result = (keyb == NULL) ? 0 : -1; else if (keyb == NULL) result = (keya == NULL) ? 0 : 1; else // result = strcmp (keya, keyb); any username appended to the field will be utf-8 result = g_utf8_collate (keya, keyb); if (keya != NULL) g_free (keya); if (keyb != NULL) g_free (keyb); return result; } /** @brief create shortform @a type ACL-string for @a localpath @param localpath path of item to process, localised string @param type enumerator dictating whether access or default ACL is wanted @return newly-allocated short-form ACL or NULL upon error */ static gchar *_e2p_acl_create_mode_string (gchar *localpath, acl_type_t type) { acl_t acl = acl_get_file (localpath, type); return (_e2p_acl_create_mode_string_for_acl (acl)); } /*******************/ /**** callbacks ****/ /*******************/ /* radio and toggle button callbacks may update widget sensitivities and/or liststore(s) content, but they do not change any action-type flags Those are all interpreted when OK or Apply-to-all is clicked */ /** @brief "toggled" signal callback for use-shown-data radio button @param widget the activated button widget @param rt pointer to dialog data struct @return */ static void _e2p_acl_set_shown_changes_cb (GtkWidget *widget, E2_ACLDlgRuntime *rt) { /* gboolean flag = (GTK_TOGGLE_BUTTON (widget)->active); if (flag) {// gtk_widget_set_sensitive (rt->?, flag); // gtk_widget_set_sensitive (rt->?, !flag);} else {// gtk_widget_set_sensitive (rt->?, flag); // gtk_widget_set_sensitive (rt->?, !flag);} */ } /** @brief "toggled" signal callback for use-system-data radio button @param widget the activated button widget @param rt pointer to dialog data struct @return */ static void _e2p_acl_set_system_changes_cb (GtkWidget *widget, E2_ACLDlgRuntime *rt) { gboolean flag = (GTK_TOGGLE_BUTTON (widget)->active); if (flag) { if (GTK_TOGGLE_BUTTON (rt->add_data_btn)->active || GTK_TOGGLE_BUTTON (rt->remove_data_btn)->active) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (rt->set_data_btn), TRUE); // gtk_widget_set_sensitive (rt->?, flag); // gtk_widget_set_sensitive (rt->?, !flag); } // else // {// gtk_widget_set_sensitive (rt->?, flag); // gtk_widget_set_sensitive (rt->?, !flag);} */ } /** @brief "toggled" signal callback for use-modified-system-data radio button @param widget the activated button widget @param rt pointer to dialog data struct @return */ static void _e2p_acl_set_systemplus_changes_cb (GtkWidget *widget, E2_ACLDlgRuntime *rt) { /* gboolean flag = (GTK_TOGGLE_BUTTON (widget)->active); if (flag) {// gtk_widget_set_sensitive (rt->?, flag); // gtk_widget_set_sensitive (rt->?, !flag);} else {// gtk_widget_set_sensitive (rt->?, flag); // gtk_widget_set_sensitive (rt->?, !flag);} */ } /** @brief "toggled" signal callback for set_data_btn and remove_all_btn This sets all permission-field states to conform to current mode @param widget the activated button widget, may be NULL at dialog start @param rt pointer to dialog data struct @return */ static void _e2p_acl_reset_mode_fields_cb (GtkWidget *widget, E2_ACLDlgRuntime *rt) { gboolean flag = (widget == NULL || GTK_TOGGLE_BUTTON (widget)->active); gboolean nuke = (widget != NULL && widget == rt->remove_all_btn); if (flag) { if (nuke) { gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (rt->system_perms_btn), TRUE); gtk_widget_set_sensitive (rt->shown_perms_btn, FALSE); gtk_widget_set_sensitive (rt->sysmod_perms_btn, FALSE); gtk_widget_set_sensitive (rt->change_whole_btn, FALSE); gtk_widget_set_sensitive (rt->add_row_btn, FALSE); gtk_widget_set_sensitive (rt->remove_row_btn, FALSE); } GtkTreeSelection *sel; GtkTreePath *tpath; #ifdef E2_VFS VPATH ddata = { rt->itempath, NULL }; //only do ACL's on local items #endif struct stat sb; E2_ERR_DECLARE if (nuke && rt->acls[AXSNOW] == NULL) gtk_list_store_clear (rt->axs_store); //CHECKME view de/re-attachment during store update #ifdef E2_VFS else if (!e2_fs_stat (&ddata, &sb E2_ERR_PTR())) #else else if (!e2_fs_stat (rt->itempath, &sb E2_ERR_PTR())) #endif { if (gtk_tree_model_iter_n_children (GTK_TREE_MODEL (rt->axs_store), NULL) == 0 && rt->acls[AXSNOW] == NULL) flag = TRUE; //force filling with a basic set of data else flag = nuke; _e2p_acl_reset_mode_fields (rt->axs_store, flag, sb.st_mode, rt->acls[AXSNOW]); if (GTK_TOGGLE_BUTTON (rt->change_whole_btn)->active) _e2p_acl_reset_whole_fields (rt->axs_store, rt); sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (rt->axs_view)); if (gtk_tree_selection_count_selected_rows (sel) == 0) { tpath = gtk_tree_path_new_first (); gtk_tree_selection_select_path (sel, tpath); gtk_tree_path_free (tpath); } } else //stat failed { gdk_threads_leave (); e2_fs_error_local (_("Cannot get current data for %s"), #ifdef E2_VFS &ddata E2_ERR_MSGL()); #else rt->itempath E2_ERR_MSGL()); #endif gdk_threads_enter (); E2_ERR_CLEAR //FIXME handle error } if (rt->def_store != NULL) { if (nuke) { gtk_list_store_clear (rt->def_store); } else { if (gtk_tree_model_iter_n_children (GTK_TREE_MODEL (rt->def_store), NULL) == 0 && rt->acls[DEFNOW] == NULL) flag = TRUE; else flag = nuke; mode_t mode = umask (0); umask (mode); _e2p_acl_reset_mode_fields (rt->def_store, flag, ~mode & ALLPERMS, rt->acls[DEFNOW]); if (GTK_TOGGLE_BUTTON (rt->change_whole_btn)->active) _e2p_acl_reset_whole_fields (rt->def_store, rt); sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (rt->def_view)); if (gtk_tree_selection_count_selected_rows (sel) == 0) { tpath = gtk_tree_path_new_first (); gtk_tree_selection_select_path (sel, tpath); gtk_tree_path_free (tpath); } } } } else //turning off if (nuke) { gtk_widget_set_sensitive (rt->shown_perms_btn, TRUE); gtk_widget_set_sensitive (rt->sysmod_perms_btn, TRUE); gtk_widget_set_sensitive (rt->change_whole_btn, TRUE); gint count = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (rt->store), NULL); if (count > 0) gtk_widget_set_sensitive (rt->remove_row_btn, TRUE); if (count < MAX_ACL_ENTRIES) gtk_widget_set_sensitive (rt->add_row_btn, TRUE); } } /** @brief "toggled" signal callback for add_perms_button and remove_perms_button This sets all permission-field states to FALSE @param widget the activated button widget, may be NULL at dialog start @param rt pointer to dialog data struct @return */ static void _e2p_acl_clear_mode_fields_cb (GtkWidget *widget, E2_ACLDlgRuntime *rt) { gboolean flag = (widget == NULL || GTK_TOGGLE_BUTTON (widget)->active); if (flag) { // gtk_widget_set_sensitive (rt->?, flag); // gtk_widget_set_sensitive (rt->?, !flag);} if (rt->system_perms_btn != NULL //not running at dialog start && GTK_TOGGLE_BUTTON (rt->system_perms_btn)->active) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (rt->sysmod_perms_btn), TRUE); GtkTreeIter iter; if (rt->axs_store != NULL && gtk_tree_model_get_iter_first (GTK_TREE_MODEL (rt->axs_store), &iter)) { do { gtk_list_store_set (rt->axs_store, &iter, READ, FALSE, WRITE, FALSE, EXEC, FALSE, WHOLE, FALSE, -1); } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (rt->axs_store), &iter)); } if (rt->def_store != NULL && gtk_tree_model_get_iter_first (GTK_TREE_MODEL (rt->def_store), &iter)) { do { gtk_list_store_set (rt->def_store, &iter, READ, FALSE, WRITE, FALSE, EXEC, FALSE, WHOLE, FALSE, -1); } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (rt->def_store), &iter)); } } // else // {// gtk_widget_set_sensitive (rt->?, flag); // gtk_widget_set_sensitive (rt->?, !flag);} } /** @brief "toggled" signal callback for recurse_btn @param widget the activated button widget @param rt pointer to dialog data struct @return */ static void _e2p_acl_toggle_recurse_btn_cb (GtkWidget *widget, E2_ACLDlgRuntime *rt) { gboolean flag = (GTK_TOGGLE_BUTTON (widget)->active); gtk_widget_set_sensitive (rt->recurse_dirs_btn, flag); gtk_widget_set_sensitive (rt->recurse_other_btn, flag); /* if (flag) {// gtk_widget_set_sensitive (rt->?, flag); // gtk_widget_set_sensitive (rt->?, !flag);} else {// gtk_widget_set_sensitive (rt->?, flag); // gtk_widget_set_sensitive (rt->?, !flag);} */ } /** @brief "toggled" signal callback for recurse_dirs and recurs_others buttons @param widget the activated button widget @param rt pointer to dialog data struct @return */ static void _e2p_acl_toggle_recurse_type_cb (GtkWidget *widget, E2_ACLDlgRuntime *rt) { /* gboolean flag = (GTK_TOGGLE_BUTTON (widget)->active); gtk_widget_set_sensitive (rt->?, flag); gtk_widget_set_sensitive (rt->?, !flag); if (flag) {// gtk_widget_set_sensitive (rt->?, flag); // gtk_widget_set_sensitive (rt->?, !flag);} else {// gtk_widget_set_sensitive (rt->?, flag); // gtk_widget_set_sensitive (rt->?, !flag);} */ if (GTK_TOGGLE_BUTTON (widget)->active) return; //don't care about choice to turn recursion on if (widget == rt->recurse_dirs_btn) { if (!(GTK_TOGGLE_BUTTON (rt->recurse_other_btn)->active)) { gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (rt->recurse_other_btn),TRUE); } } else //widget == rt->recurse_other_btn { if (!(GTK_TOGGLE_BUTTON (rt->recurse_dirs_btn)->active)) { gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (rt->recurse_dirs_btn),TRUE); } } } /** @brief "toggled" signal callback for global "whole" button @param widget the activated button widget @param rt pointer to dialog data struct @return */ static void _e2p_acl_default_whole_fields_cb (GtkWidget *widget, E2_ACLDlgRuntime *rt) { gboolean flag = (GTK_TOGGLE_BUTTON (widget)->active); if (flag) { // gtk_widget_set_sensitive (rt->?, flag); // gtk_widget_set_sensitive (rt->?, !flag); //for cosmetic purposes only, set each row's 'whole' field _e2p_acl_reset_whole_fields (rt->axs_store, rt); _e2p_acl_reset_whole_fields (rt->def_store, rt); } /* else { gtk_widget_set_sensitive (rt->?, flag); gtk_widget_set_sensitive (rt->?, !flag); } */ } /** @brief callback for notebook page-switched signal @param notebook UNUSED the book whose page has changed @param page UNUSED the new page @param page_num the index of the new page @param rt pointer to dialog data struct @return */ static void _e2p_acl_tabchange_cb (GtkNotebook *notebook, GtkNotebookPage *page, guint page_num, E2_ACLDlgRuntime *rt) { if (page_num == 0) //general page CHECKME if order dragged { rt->store = rt->axs_store; rt->treeview = rt->axs_view; } else //defaults page { rt->store = rt->def_store; rt->treeview = rt->def_view; } //adjust button sensitivies if needed if (!GTK_TOGGLE_BUTTON (rt->remove_all_btn)->active) { gint count = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (rt->store), NULL); if (count == 0) gtk_widget_set_sensitive (rt->remove_row_btn, FALSE); else if (count >= MAX_ACL_ENTRIES) gtk_widget_set_sensitive (rt->add_row_btn, FALSE); } } /** @brief dialog treeview-selection changed callback @param treeselection selection object for the clicked treeview widget @param rt pointer to data struct for the dialog @return */ static void _e2p_acl_selection_change_cb (GtkTreeSelection *treeselection, E2_ACLDlgRuntime *rt) { if (!GTK_TOGGLE_BUTTON (rt->remove_all_btn)->active) { guint count = gtk_tree_selection_count_selected_rows (treeselection); gtk_widget_set_sensitive (rt->remove_row_btn, (count > 0)); } } /** @brief change key handling when text-cell editing starts @param renderer UNUSED the renderer for the cell @param editable UNUSED the interface to @a cell @param path_string UNUSED string form of gtk tree path to the row to be amended @param rt pointer to dialog data struct @return */ static void _e2p_acl_cell_edit_start_cb (GtkCellRenderer *renderer, GtkCellEditable *editable, gchar *path_string, E2_ACLDlgRuntime *rt) { // printd (DEBUG, "start cell edit cb"); g_signal_handlers_block_by_func (G_OBJECT (rt->dialog), e2_dialog_key_neg_cb, rt->dialog); } /** @brief revert key handling when text-cell editing is finished @param renderer the renderer for the cell @param rt pointer to dialog data struct @return */ static void _e2p_acl_cell_edit_stop_cb (GtkCellRenderer *renderer, E2_ACLDlgRuntime *rt) { // printd (DEBUG, "cancel cell edit cb"); g_signal_handlers_unblock_by_func (G_OBJECT (rt->dialog), e2_dialog_key_neg_cb, rt->dialog); } /** @brief if permitted, save edited text value in the underlying liststore, with related sort key @param renderer the renderer for the cell @param path_string string form of gtk tree path to the row to be amended @param new_text replacement text string for the cell @param rt pointer to dialog data struct @return */ static void _e2p_acl_cell_edited_cb (GtkCellRendererText *cell, gchar *path_string, gchar *new_text, E2_ACLDlgRuntime *rt) { if (new_text == NULL) //this probably can't happen return; if (GTK_TOGGLE_BUTTON (rt->remove_all_btn)->active) return; //no mode changes when in nuke mode printd (DEBUG, "acl view edited cb, new text is %s", new_text); GtkTreeIter iter; if (gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (rt->store), &iter, path_string)) { //no sorting until the key is added FIXME make this work // GtkTreeSortable *sort = GTK_TREE_SORTABLE (rt->store); // gtk_tree_sortable_set_sort_column_id (sort, // GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING); gboolean update; gint col = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (cell), "column")); gchar *qualifier, *key; gtk_tree_model_get (GTK_TREE_MODEL (rt->store), &iter, QUAL, &qualifier, SORTKEY, &key, -1); if (col == CLASS) { //empty or user or group entries are ok to update update = (key == NULL || *key == '\0' || qualifier == NULL || *qualifier == '\0' || g_str_equal (new_text, classinames[0]) //user always ok || g_str_equal (new_text, classinames[1])); //ditto group } else if (col == QUAL) { //empty or user or group entries are ok to update update = (key == NULL || *key == '\0' || key[0] == '1' || key[0] == '2'); } else update = TRUE; if (qualifier != NULL) g_free (qualifier); if (key != NULL) g_free (key); if (update) { gtk_list_store_set (rt->store, &iter, col, new_text, -1); _e2p_acl_fill_sortkey (GTK_TREE_MODEL (rt->store), &iter); if (GTK_TOGGLE_BUTTON (rt->change_whole_btn)->active) _e2p_acl_reset_whole_fields (rt->store, rt); // gtk_tree_sortable_set_sort_column_id (sort, SORTKEY, GTK_SORT_ASCENDING); //ensure the resorted iter is still visible in window _e2p_acl_show_row (GTK_TREE_VIEW (rt->treeview), &iter); //revert focus to edited row gtk_widget_grab_focus (rt->treeview); } } } /** @brief if permitted, save edited boolean value in the underlying liststore @param renderer the renderer for the cell @param path_string string form of gtk tree path to the row to be amended @param new_text replacement text string for the cell @param rt pointer to dialog data struct @return */ static void _e2p_acl_toggle_cb (GtkCellRendererToggle *cell, const gchar *path_str, E2_ACLDlgRuntime *rt) { if (GTK_TOGGLE_BUTTON (rt->remove_all_btn)->active) return; //no mode changes when in nuke mode //find out where GtkTreePath *tpath = gtk_tree_path_new_from_string (path_str); GtkTreeIter iter; if (gtk_tree_model_get_iter (GTK_TREE_MODEL (rt->store), &iter, tpath)) { gint col = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (cell), "column")); gboolean update, value, setaction; gchar *key; //find out if appropriate gtk_tree_model_get (GTK_TREE_MODEL (rt->store), &iter, SORTKEY, &key, col, &value, -1); if (col == WHOLE) { // if (GTK_TOGGLE_BUTTON (rt->change_whole_btn)->active) // update = FALSE; //whole fields may be changed in 'set' mode //or sometimes in 'add' or 'remove' setaction = GTK_TOGGLE_BUTTON (rt->set_data_btn)->active; switch (key[0]) { case '1': //user //can always add/remove qualified user entries update = setaction || key[1] != '\0'; break; case '2': //group //can always add/remove qualified group entries update = setaction || key[1] != '\0'; break; case '3': //mask //can also remove mask update = setaction || GTK_TOGGLE_BUTTON (rt->remove_data_btn)->active; break; case '4': //other //can only set mask update = setaction; break; default: update = FALSE; break; } } else update = TRUE; if (update) { value ^= 1; //change model gtk_list_store_set (rt->store, &iter, col, value, -1); } g_free (key); } //clean up gtk_tree_path_free (tpath); } /** @brief dialog response callback @param dialog the acl-dialog @param response the response for the clicked button or other event @param rt pointer to dialog data struct (on stack, not heap) @return */ static void _e2p_acl_dialog_response_cb (GtkDialog *dialog, gint response, E2_ACLDlgRuntime *rt) { switch (response) { case E2_RESPONSE_USER1: //help-button press e2_utils_show_help ("access control list plugin"); //no translation unless help doc is translated gtk_widget_grab_focus (rt->dialog); break; case E2_RESPONSE_USER2: //add-button press { gint count = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (rt->store), NULL); if (count < MAX_ACL_ENTRIES) { GtkTreeIter iter; GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (rt->treeview)); if (gtk_tree_selection_get_selected (sel, NULL, &iter)) { if (iter.user_data != NULL) { gchar *curkey; GtkTreeIter iter2; gtk_tree_model_get (GTK_TREE_MODEL (rt->store), &iter, SORTKEY, &curkey, -1); gtk_list_store_insert_after (rt->store, &iter2, &iter); //set "whole" flag, and sortkey to prevent the new row from being relocated by the sort func gtk_list_store_set (rt->store, &iter2, WHOLE, TRUE, SORTKEY, curkey, -1); gtk_tree_model_iter_next (GTK_TREE_MODEL (rt->store), &iter); g_free (curkey); } } else gtk_list_store_append (rt->store, &iter); _e2p_acl_show_row (GTK_TREE_VIEW (rt->treeview), &iter); //ensure it's on-screen if (count == 0) gtk_widget_set_sensitive (rt->remove_row_btn, TRUE); else if (count == MAX_ACL_ENTRIES - 1) gtk_widget_set_sensitive (rt->add_row_btn, FALSE); gtk_tree_selection_select_iter (sel, &iter); gtk_widget_grab_focus (rt->treeview); //CHECKME consider a warning to user about adding a mask entry, when relevant //or maybe just add such an entry } } break; case E2_RESPONSE_REMOVE: //remove-button press { GtkTreeIter iter; GtkTreeModel *mdl; GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (rt->treeview)); if (gtk_tree_selection_get_selected (sel, &mdl, &iter)) { //CHECKME some validity testing here. instead of upon initiation //e.g. a warning to user about removing a mask entry, when relevant //or maybe just remove such an entry gtk_list_store_remove (rt->store, &iter); gint count = gtk_tree_model_iter_n_children (mdl, NULL); // if (count == 0) deletion always kills the selection gtk_widget_set_sensitive (rt->remove_row_btn, FALSE); // else if (count == MAX_ACL_ENTRIES - 1) gtk_widget_set_sensitive (rt->add_row_btn, TRUE); } } break; default: if (rt->permission) { //interpret all button-states into task flags rt->task = 0; //radio E2_ACL_SHOWN or E2_ACL_SYSTEM or E2_ACL_SYSMOD if (GTK_TOGGLE_BUTTON (rt->shown_perms_btn)->active) rt->task |= E2_ACL_SHOWN; else if (GTK_TOGGLE_BUTTON (rt->system_perms_btn)->active) rt->task |= E2_ACL_SYSTEM; else //if (GTK_TOGGLE_BUTTON (rt->sysmod_perms_btn)->active) rt->task |= E2_ACL_SYSMOD; //radio E2_ACL_SET or E2_ACL_ADD or E2_ACL_REMOVE or E2_ACL_NUKE if (GTK_TOGGLE_BUTTON (rt->set_data_btn)->active) rt->task |= E2_ACL_SET; else if (GTK_TOGGLE_BUTTON (rt->add_data_btn)->active) rt->task |= E2_ACL_ADD; else if (GTK_TOGGLE_BUTTON (rt->remove_data_btn)->active) rt->task |= E2_ACL_REMOVE; else //if (GTK_TOGGLE_BUTTON (rt->remove_all_btn)->active) rt->task |= E2_ACL_NUKE; if (GTK_TOGGLE_BUTTON (rt->change_whole_btn)->active) rt->task |= E2_ACL_WHOLE; //effective radio E2_ACL_NODOWN or any of E2_ACL_DIRAXS or E2_ACL_DIRDEF or E2_ACL_OTHER if (rt->thisis_dir) //means the recurse buttons have been added to the dialog { if (GTK_TOGGLE_BUTTON (rt->dir_axs_btn)->active) rt->task |= E2_ACL_DIRAXS; if (GTK_TOGGLE_BUTTON (rt->dir_def_btn)->active) rt->task |= E2_ACL_DIRDEF; if (GTK_TOGGLE_BUTTON (rt->recurse_btn)->active) { if (GTK_TOGGLE_BUTTON (rt->recurse_dirs_btn)->active) rt->task |= E2_ACL_DIR; if (GTK_TOGGLE_BUTTON (rt->recurse_other_btn)->active) rt->task |= E2_ACL_OTHER; } } if (!(rt->task & (E2_ACL_DIR | E2_ACL_OTHER))) rt->task |= E2_ACL_NODOWN; //the user actually wants to do something if (response == GTK_RESPONSE_OK || response == E2_RESPONSE_APPLYTOALL) { DialogButtons choice; if (rt->thisis_dir) { //check that something relevant has been selected if (!(rt->task & (E2_ACL_DIR | E2_ACL_OTHER | E2_ACL_DIRAXS | E2_ACL_DIRDEF))) { choice = e2_dialog_warning (_("No directory-changes have been selected")); if (choice != OK) return; } } //verify, if not using just system data and not removing if (!((rt->task & E2_ACL_NUKE) || (rt->task & (E2_ACL_SET | E2_ACL_SYSTEM)) || (rt->task & E2_ACL_REMOVE))) { gchar *prompt; gchar *template = _("The specified %s is likely to ba a problem"); if (!_e2p_acl_verify_store (rt->axs_store, rt->task)) { prompt = g_strdup_printf (template, _("General ACL")); choice = e2_dialog_warning (prompt); g_free (prompt); if (choice != OK) return; } if (rt->thisis_dir) { if (!_e2p_acl_verify_store (rt->def_store, rt->task)) { prompt = g_strdup_printf (template, _("Directory ACL")); choice = e2_dialog_warning (prompt); g_free (prompt); if (choice != OK) return; } } } //convert store data to array for later use rt->axs_changes = _e2p_acl_convert_store (rt->axs_store, rt); rt->def_changes = (rt->thisis_dir) ? _e2p_acl_convert_store (rt->def_store, rt) : NULL; } /*redundant else { rt->axs_changes = NULL; rt->def_changes = NULL; } */ saved_task = rt->task; //ready for next time } e2_dialog_response_decode_cb (dialog, response, &rt->result); gtk_widget_destroy (GTK_WIDGET (dialog)); /*redundant if (!(rt->result == OK || rt->result == YES_TO_ALL)) { if (rt->axs_changes != NULL) //probably redundant { g_ptr_array_foreach (rt->axs_changes, (GFunc)_e2p_acl_clean_changes, NULL); g_ptr_array_free (rt->axs_changes, TRUE); } if (rt->def_changes != NULL) { g_ptr_array_foreach (rt->def_changes, (GFunc)_e2p_acl_clean_changes, NULL); g_ptr_array_free (rt->def_changes, TRUE); } } */ //CHECKME other acl_t cleanups ? //other cleanups ? break; } } /** @brief create and run an ACL-change dialog @param localpath path of item to be processed @param axs_ret store for pointer to array of changes to access-ACL @param def_ret store for pointer to array of changes to default-ACL @param scope_ret store for returning whether to recurse the changes @param permission_ret store for returning whether a change is authorised @param multi TRUE if this dialog is part of a series for multiple items @return the enumerator of the clicked dialog button */ static DialogButtons _e2p_acl_dialog_run (VPATH *localpath, GPtrArray **axs_ret, GPtrArray **def_ret, E2_ACLTask *task_ret, gboolean *permission_ret, gboolean multi) { GtkWidget *dialog_vbox, *sub_vbox; GtkWidget *hbox; GtkWidget *frame; GtkWidget *table; GtkWidget *nbook = NULL; //assignment for warning prevention only GtkWidget *sw; GtkWidget *btn; gboolean freelabel; gchar *label, *name; struct stat statbuf; struct passwd *pw_buf; struct group *grp_buf; E2_ACLDlgRuntime rt; GtkTreeSelection *sel; if (e2_fs_lstat (localpath, &statbuf E2_ERR_NONE())) return CANCEL; GString *label_text = g_string_sized_new (NAME_MAX+20); memset (&rt, 0, sizeof(E2_ACLDlgRuntime)); rt.permission = e2_fs_check_write_permission (localpath E2_ERR_NONE()); rt.itempath = VPSTR (localpath); rt.thisis_dir = e2_fs_is_dir3 (localpath E2_ERR_NONE()); //CHECKME abort on error ? rt.dialog = e2_dialog_create (NULL, NULL, _("extended permissions"), _e2p_acl_dialog_response_cb, &rt); dialog_vbox = GTK_DIALOG (rt.dialog)->vbox; gtk_container_set_border_width (GTK_CONTAINER (dialog_vbox), E2_PADDING); gtk_dialog_set_has_separator (GTK_DIALOG (rt.dialog), FALSE); //all info in frames, no need label = (rt.thisis_dir) ? _("Directory name") : _("Filename") ; name = g_filename_display_basename (VPSTR(localpath)); g_string_printf (label_text, "%s: %s", label, name); g_free (name); hbox = gtk_hbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX(dialog_vbox), hbox, FALSE, FALSE, E2_PADDING); //top, bottom padding e2_widget_add_mid_label (hbox, label_text->str, 0, TRUE, E2_PADDING); //L, R padding gtk_widget_show (hbox); label = _("User"); if ((pw_buf = getpwuid (statbuf.st_uid)) != NULL) name = e2_utf8_from_locale (pw_buf->pw_name); else name = NULL; if (name != NULL) { g_string_printf (label_text, "%s: %s", label, name); g_free (name); } else g_string_printf (label_text, "%s: %d", label, (guint) statbuf.st_uid); hbox = gtk_hbox_new (FALSE, 0); e2_widget_add_mid_label (hbox, label_text->str, 0, TRUE, E2_PADDING); gtk_box_pack_start (GTK_BOX(dialog_vbox), hbox, FALSE, FALSE, 0); gtk_widget_show (hbox); label = _("Group"); if ((grp_buf = getgrgid (statbuf.st_gid)) != NULL) name = e2_utf8_from_locale (grp_buf->gr_name); else name = NULL; if (name != NULL) { g_string_printf (label_text, "%s: %s", label, name); g_free (name); } else g_string_printf (label_text, "%s: %d", label, (guint) statbuf.st_gid); hbox = gtk_hbox_new (FALSE, 0); e2_widget_add_mid_label (hbox, label_text->str, 0, TRUE, E2_PADDING); gtk_box_pack_start (GTK_BOX(dialog_vbox), hbox, FALSE, FALSE, 0); gtk_widget_show (hbox); gchar *errstr = _("none"); gchar *errstr2 = _("unable to display"); name = _("General ACL"); //CHECKME these desciption stings may have way too amy lines for this context !! rt.acls[AXSNOW] = acl_get_file (VPSTR(localpath), ACL_TYPE_ACCESS); if (rt.acls[AXSNOW] != NULL) { label = _e2p_acl_create_mode_string (VPSTR(localpath), ACL_TYPE_ACCESS); if (label != NULL) freelabel = TRUE; else { label = errstr2; freelabel = FALSE; } } else { label = errstr; freelabel = FALSE; } g_string_printf (label_text, "%s: %s", name, label); if (freelabel) g_free (label); hbox = gtk_hbox_new (FALSE, 0); e2_widget_add_mid_label (hbox, label_text->str, 0, TRUE, E2_PADDING); gtk_box_pack_start (GTK_BOX(dialog_vbox), hbox, FALSE, FALSE, 0); gtk_widget_show (hbox); if (rt.thisis_dir) { name = _("Directory ACL"); rt.acls[DEFNOW] = acl_get_file (VPSTR(localpath), ACL_TYPE_DEFAULT); if (rt.acls[DEFNOW] != NULL) { label = _e2p_acl_create_mode_string (VPSTR(localpath), ACL_TYPE_DEFAULT); if (label != NULL) freelabel = TRUE; else { label = errstr2; freelabel = FALSE; } } else { label = errstr; freelabel = FALSE; } g_string_printf (label_text, "%s: %s", name, label); if (freelabel) g_free (label); hbox = gtk_hbox_new (FALSE, 0); e2_widget_add_mid_label (hbox, label_text->str, 0, TRUE, E2_PADDING); gtk_box_pack_start (GTK_BOX(dialog_vbox), hbox, FALSE, FALSE, 0); gtk_widget_show (hbox); } frame = gtk_frame_new (_("Permissions")); gtk_box_pack_start (GTK_BOX (dialog_vbox), frame, TRUE, TRUE, E2_PADDING); gtk_widget_show (frame); sub_vbox = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (frame), sub_vbox); gtk_widget_show (sub_vbox); /* since the initial states of radio/toggle button are set at runtime to match the last session, toggling of buttons during dialog initialisation can't be allowed, until all widgets that may be affected by a "toggled" callback have been created. So all button-creations have a NULL callback, and explict callbacks are attached afterwards */ if (rt.thisis_dir) { nbook = e2_widget_add_notebook (sub_vbox, TRUE, 0, NULL, NULL); //no callback until after treeviews are created GtkNotebook *book = GTK_NOTEBOOK (nbook); #ifdef E2_TABS_DETACH //enable tab dragging to/from new window //FIXME next is crasher // gtk_drag_source_set (nbook, GDK_BUTTON1_MASK, target_table2, n_targets2, // GDK_ACTION_PRIVATE); /*FIXME re-dragging, including drag-back, does not work gtk_drag_dest_set (nbook, GTK_DEST_DEFAULT_DROP, target_table2, n_targets2, GDK_ACTION_PRIVATE); g_signal_connect (G_OBJECT (nbook), "drag-data-received", G_CALLBACK (_e2_output_tabdrag_data_received_cb), NULL); //CHECKME user_data */ // gtk_notebook_set_window_creation_hook // ((GtkNotebookWindowCreationFunc) _e2_output_tab_drop_new, // NULL, //FIXME gpointer data // NULL); //(GDestroyNotify) destroy #endif //iterate backward so that we end with the first (General) tab sw = _e2p_acl_create_view (ACL_TYPE_DEFAULT, &rt); GtkWidget *tablbl = gtk_label_new (_("Directory")); //USELESS ? gtk_widget_set_tooltip_text (tablbl, // _("Show the \"default\" ACL, which may apply to directories only")); gtk_notebook_append_page (book, sw, tablbl); #ifdef USE_GTK2_10 gtk_notebook_set_tab_reorderable (book, sw, TRUE); #ifdef E2_TABS_DETACH // gtk_notebook_set_tab_detachable (book, sw, TRUE); #endif #endif sw = _e2p_acl_create_view (ACL_TYPE_ACCESS, &rt); tablbl = gtk_label_new (_("General")); //USELESS ? gtk_widget_set_tooltip_text (tablbl, // _("Show the \"access\" ACL, which may apply to any type of item")); gtk_notebook_prepend_page (book, sw, tablbl); #ifdef USE_GTK2_10 gtk_notebook_set_tab_reorderable (book, sw, TRUE); #ifdef E2_TABS_DETACH gtk_notebook_set_tab_detachable (book, sw, TRUE); #endif #endif gtk_notebook_set_current_page (book, 0); } else //not a dir { sw = _e2p_acl_create_view (ACL_TYPE_ACCESS, &rt); //create the permissions treeview gtk_box_pack_start (GTK_BOX (sub_vbox), sw, TRUE, TRUE, 0); } rt.treeview = rt.axs_view; //dialog starts with the access ACL displayed rt.store = rt.axs_store; // guint count = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (rt.store), NULL); if (!rt.permission) //no permission = no widgets { GtkTreePath *tpath = gtk_tree_path_new_first (); if (rt.acls[AXSNOW] != NULL) { _e2p_acl_fill_store (rt.axs_store, rt.acls[AXSNOW]); sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (rt.axs_view)); gtk_tree_selection_select_path (sel, tpath); } if (rt.acls[DEFNOW] != NULL) { _e2p_acl_fill_store (rt.def_store, rt.acls[DEFNOW]); sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (rt.def_view)); gtk_tree_selection_select_path (sel, tpath); } gtk_tree_path_free (tpath); e2_ownership_dialog_warn (dialog_vbox); //show message } else //must wait until task-widgets are in place before filling store { frame = gtk_frame_new (_("Data:")); gtk_box_pack_start (GTK_BOX (dialog_vbox), frame, FALSE, FALSE, E2_PADDING); gtk_widget_show (frame); sub_vbox = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (frame), sub_vbox); gtk_widget_show (sub_vbox); table = e2_widget_add_table (sub_vbox, 1, 5, TRUE, FALSE, 0); //1 row, 5 cols, homogen, fill, no pad rt.shown_perms_btn = e2_button_add_radio_to_table (table, _("S_hown"), NULL, (saved_task & E2_ACL_SHOWN), NULL, NULL, 0,1,0,1); //gint left, gint right, gint top, gint bottom #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif rt.shown_perms_btn, _("Changes will be based only on the data shown above")); rt.sysmod_perms_btn = e2_button_add_radio_to_table (table, _("_Varied"), gtk_radio_button_get_group (GTK_RADIO_BUTTON (rt.shown_perms_btn)), (saved_task & E2_ACL_SYSMOD), NULL, NULL, 1,2,0,1); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif rt.sysmod_perms_btn, _("Changes will be based on the standard permissions of the affected" " item as modified by the data shown above")); rt.system_perms_btn = e2_button_add_radio_to_table (table, _("S_ystem"), gtk_radio_button_get_group (GTK_RADIO_BUTTON (rt.shown_perms_btn)), (saved_task & E2_ACL_SYSTEM), NULL, NULL, 2,3,0,1); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif rt.system_perms_btn, _("Changes will be based only on the standard (non-ACL) permissions" " of the affected item")); frame = gtk_frame_new (_("Action:")); gtk_box_pack_start (GTK_BOX (dialog_vbox), frame, FALSE, FALSE, E2_PADDING); gtk_widget_show (frame); sub_vbox = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (frame), sub_vbox); gtk_widget_show (sub_vbox); table = e2_widget_add_table (sub_vbox, 1, 5, TRUE, FALSE, 0); //1 row, 5 cols, homogen, fill, no pad rt.remove_all_btn = e2_button_add_radio_to_table (table, _("_Nuke"), NULL, (saved_task & E2_ACL_NUKE), NULL, NULL, 0,1,0,1); //gint left, gint right, gint top, gint bottom #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif rt.remove_all_btn, _("Clear as much of the item's ACL as possible")); rt.set_data_btn = e2_button_add_radio_to_table (table, _("_Set"), gtk_radio_button_get_group (GTK_RADIO_BUTTON (rt.remove_all_btn)), (saved_task & E2_ACL_SET), NULL, NULL, 1,2,0,1 ); rt.add_data_btn = e2_button_add_radio_to_table (table, _("_Add"), gtk_radio_button_get_group (GTK_RADIO_BUTTON (rt.remove_all_btn)), (saved_task & E2_ACL_ADD), NULL, NULL, 2,3,0,1); rt.remove_data_btn = e2_button_add_radio_to_table (table, _("_Remove"), gtk_radio_button_get_group (GTK_RADIO_BUTTON (rt.remove_all_btn)), (saved_task & E2_ACL_REMOVE), NULL, NULL, 3,4,0,1); rt.change_whole_btn = e2_button_add_toggle_to_table (table, _("_Whole"), (saved_task & E2_ACL_WHOLE), NULL, NULL, 4,5,0,1); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif rt.change_whole_btn, _("Conveniently sets all allowed 'whole' values. For those entries," " the action will apply to the whole of the entry, Otherwise," " the action affects only the permissions of that entry")); if (rt.thisis_dir) { table = e2_widget_add_table (sub_vbox, 1, 5, TRUE, FALSE, 0); //1 row, 5 cols, homogen, fill, no pad rt.recurse_btn = e2_button_add_toggle_to_table (table, _("R_ecurse:"), !(saved_task & E2_ACL_NODOWN), NULL, NULL, 0,1,0,1); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif rt.recurse_btn, _("If activated, changes will be applied to selected items and" " also their descendents, if such items match your choice of" " \"directories\" and/or \"others\" (anything not a directory)")); rt.recurse_dirs_btn = e2_button_add_toggle_to_table (table, _("d_irectories"), (saved_task & (E2_ACL_DIRAXS | E2_ACL_DIRDEF)), NULL, NULL, 1,2,0,1); gtk_widget_set_sensitive (rt.recurse_dirs_btn, !(saved_task & E2_ACL_NODOWN)); rt.recurse_other_btn = e2_button_add_toggle_to_table (table, _("o_thers"), (saved_task & E2_ACL_OTHER), NULL, NULL, 2,3,0,1); gtk_widget_set_sensitive (rt.recurse_other_btn, !(saved_task & E2_ACL_NODOWN)); rt.dir_axs_btn = e2_button_add_toggle_to_table (table, _("dirs-_general"), (saved_task & E2_ACL_DIRAXS), NULL, NULL, 3,4,0,1); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif rt.dir_axs_btn, _("if activated, specified changes to the \"general\" ACL" " will be applied to any affected directory")); rt.dir_def_btn = e2_button_add_toggle_to_table (table, _("dirs-_default"), (saved_task & E2_ACL_DIRDEF), NULL, NULL, 4,5,0,1); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif rt.dir_def_btn, _("if activated, specified changes to the \"directories-only\" ACL" " will be applied to any affected directory")); } } g_string_free (label_text, TRUE); // add buttons in the order that they will appear e2_dialog_add_undefined_button (rt.dialog, GTK_STOCK_HELP, _("_Help"), E2_RESPONSE_USER1); if (rt.permission) { rt.add_row_btn = e2_dialog_add_undefined_button (rt.dialog, GTK_STOCK_ADD, _("Add a_fter"), E2_RESPONSE_USER2); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif rt.add_row_btn, _("Insert a row in the ACL")); // if (count >= MAX_ACL_ENTRIES) // gtk_widget_set_sensitive (rt.add_row_btn, FALSE); rt.remove_row_btn = e2_dialog_add_undefined_button (rt.dialog, GTK_STOCK_REMOVE, _("De_lete"), E2_RESPONSE_REMOVE); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif rt.remove_row_btn, _("Delete the selected row from the ACL")); // if (count == 0) // gtk_widget_set_sensitive (rt.remove_row_btn, FALSE); } if (multi) { e2_dialog_set_negative_response (rt.dialog, E2_RESPONSE_NOTOALL); e2_dialog_add_defined_button (rt.dialog, &E2_BUTTON_NOTOALL); btn = e2_dialog_add_button_custom (rt.dialog, FALSE, &E2_BUTTON_APPLYTOALL, NULL, NULL, NULL); if (!rt.permission) gtk_widget_set_sensitive (btn, FALSE); } if (rt.permission || multi) { btn = e2_dialog_add_defined_button (rt.dialog, &E2_BUTTON_CANCEL); if (!rt.permission) gtk_widget_set_sensitive (btn, FALSE); } e2_dialog_add_button_custom (rt.dialog, TRUE, &E2_BUTTON_OK, NULL, NULL, NULL); //now we can apply the potential-crasher callbacks if (rt.permission) { g_signal_connect (G_OBJECT (rt.remove_all_btn), "toggled", G_CALLBACK (_e2p_acl_reset_mode_fields_cb), &rt); g_signal_connect (G_OBJECT (rt.shown_perms_btn), "toggled", G_CALLBACK (_e2p_acl_set_shown_changes_cb), &rt); g_signal_connect (G_OBJECT (rt.sysmod_perms_btn), "toggled", G_CALLBACK (_e2p_acl_set_systemplus_changes_cb), &rt); g_signal_connect (G_OBJECT (rt.system_perms_btn), "toggled", G_CALLBACK (_e2p_acl_set_system_changes_cb), &rt); g_signal_connect (G_OBJECT (rt.set_data_btn), "toggled", G_CALLBACK (_e2p_acl_reset_mode_fields_cb), &rt); g_signal_connect (G_OBJECT (rt.add_data_btn), "toggled", G_CALLBACK (_e2p_acl_clear_mode_fields_cb), &rt); g_signal_connect (G_OBJECT (rt.remove_data_btn), "toggled", G_CALLBACK (_e2p_acl_clear_mode_fields_cb), &rt); g_signal_connect (G_OBJECT (rt.change_whole_btn), "toggled", G_CALLBACK (_e2p_acl_default_whole_fields_cb), &rt); sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (rt.axs_view)); g_signal_connect (G_OBJECT (sel), "changed", G_CALLBACK (_e2p_acl_selection_change_cb), &rt); if (rt.thisis_dir) { g_signal_connect (G_OBJECT (rt.dir_axs_btn), "toggled", G_CALLBACK (_e2p_acl_toggle_recurse_type_cb), &rt); g_signal_connect (G_OBJECT (rt.dir_def_btn), "toggled", G_CALLBACK (_e2p_acl_toggle_recurse_type_cb), &rt); g_signal_connect (G_OBJECT (rt.recurse_btn), "toggled", G_CALLBACK (_e2p_acl_toggle_recurse_btn_cb), &rt); g_signal_connect (G_OBJECT (rt.recurse_dirs_btn), "toggled", G_CALLBACK (_e2p_acl_toggle_recurse_type_cb), &rt); g_signal_connect (G_OBJECT (rt.recurse_other_btn), "toggled", G_CALLBACK (_e2p_acl_toggle_recurse_type_cb), &rt); sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (rt.def_view)); g_signal_connect (G_OBJECT (sel), "changed", G_CALLBACK (_e2p_acl_selection_change_cb), &rt); g_signal_connect (G_OBJECT (nbook), "switch-page", G_CALLBACK (_e2p_acl_tabchange_cb), &rt); } //with widgets and callbacks in place, we can fill the store(s) if (saved_task & E2_ACL_NUKE) _e2p_acl_reset_mode_fields_cb (rt.remove_all_btn, &rt); else if (saved_task & E2_ACL_SET) { _e2p_acl_reset_mode_fields_cb (rt.set_data_btn, &rt); //fill as for set task _e2p_acl_set_system_changes_cb (rt.set_data_btn, &rt); //adjust some buttons } else //add or remove { btn = (saved_task & E2_ACL_ADD) ? rt.add_data_btn : rt.remove_data_btn; _e2p_acl_reset_mode_fields_cb (btn, &rt); //fill as for set task _e2p_acl_clear_mode_fields_cb (btn, &rt); //all buttons same } } guint count = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (rt.store), NULL); if (rt.permission) { if (count >= MAX_ACL_ENTRIES) gtk_widget_set_sensitive (rt.add_row_btn, FALSE); if (count == 0) gtk_widget_set_sensitive (rt.remove_row_btn, FALSE); } e2_dialog_setup (rt.dialog, app.main_window); //setup to increase window vertical size (the header counts as 1) guint shown = (count > 10) ? 11 : count + 1; gint ht; e2_widget_get_font_pixels (rt.treeview, NULL, &ht); ht = (ht + 10) * shown; //+10 fudge factor for interline-spacing gdk_threads_enter (); e2_dialog_run (rt.dialog, NULL, 0); if (ht > rt.treeview->allocation.height) { ht = ht + rt.dialog->allocation.height - rt.treeview->allocation.height; gtk_window_resize (GTK_WINDOW (rt.dialog), rt.dialog->allocation.width, ht); } gtk_main (); gdk_threads_leave (); if (rt.acls[AXSNOW] != NULL) acl_free (rt.acls[AXSNOW]); if (rt.acls[DEFNOW] != NULL) acl_free (rt.acls[DEFNOW]); *permission_ret = rt.permission; *task_ret = rt.task; if (rt.result == OK || rt.result == YES_TO_ALL) { *axs_ret = rt.axs_changes; //possibly NULL *def_ret = rt.def_changes; //possibly NULL } else { *axs_ret = NULL; *def_ret = NULL; } return rt.result; } /*****************/ /**** actions ****/ /*****************/ #ifdef ACLEXTRA_ACTIONS /** @brief transfer ACL's of selected item(s) to matching items, if any, in inactive pane For any directory, coping is always recursive @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if action completed successfully, else FALSE */ static gboolean _e2p_task_aclcopy (gpointer from, E2_ActionRuntime *art) { return (e2_task_enqueue_task (E2_TASK_CHACL, art, from, _e2p_task_aclcopyQ, e2_task_refresh_lists)); } static gboolean _e2p_task_aclcopyQ (E2_ActionTaskData *qed) { #ifdef E2_VFS if (qed->currspace != NULL) //only do ACL's for local items return FALSE; #endif GPtrArray *names = qed->names; gchar *curr_local = qed->currdir; gchar *other_local = qed->othrdir; GString *src = g_string_sized_new (PATH_MAX); GString *dest = g_string_sized_new (PATH_MAX); #ifdef E2_VFS VPATH sdata; VPATH ddata; sdata.spacedata = qed->currspace; ddata.spacedata = qed->currspace; #endif mode_t thismode; guint count; E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata; struct stat statbuf; /* out-of-loop setup = FIXME GtkWidget *dialog; dialog = e2_permissions_dialog_setup (info, &recurse); e2_dialog_add_buttons_simple (dialog, buttons ..., NULL); */ #ifdef E2_REFRESH_DEBUG printd (DEBUG, "disable refresh, acl task"); #endif e2_filelist_disable_refresh (); e2_task_advise (); for (count = 0; count < names->len; count++, iterator++) { g_string_printf (dest, "%s%s", other_local, (*iterator)->filename); #ifdef E2_VFS ddata.localpath = dest->str; if (e2_fs_lstat (&ddata, &statbuf E2_ERR_NONE())) #else if (e2_fs_lstat (dest->str, &statbuf E2_ERR_NONE())) #endif continue; thismode = statbuf.st_mode; g_string_printf (src, "%s%s", curr_local, (*iterator)->filename); //separator comes with dir #ifdef E2_VFS sdata.localpath = src->str; if (e2_fs_lstat (&sdata, &statbuf E2_ERR_NONE())) #else if (e2_fs_lstat (src->str, &statbuf E2_ERR_NONE())) #endif continue; if ((statbuf.st_mode & S_IFMT) != (thismode & S_IFMT)) continue; if (S_ISDIR (statbuf.st_mode)) { E2_CopyACLData data = { 0 }; data.task = E2_ACL_DIRDEF | E2_ACL_DIRAXS | E2_ACL_OTHER | E2_ACL_DIR | E2_ACL_SET | E2_ACL_SYSTEM; data.oldroot_len = strlen (src->str); #ifdef E2_VFS VPATH localother = { qed->othrdir, qed->othrspace }; data.otherdir = &localother; e2_fs_tw (&sdata, #else data.otherdir = qed->othrdir; e2_fs_tw (src->str, #endif _e2p_acl_twcb_copyacl, &data, -1, //flags for: no thru-links, this filesystem only, breadth-first E2TW_MOUNT | E2TW_PHYS E2_ERR_NONE()); } else { #ifdef E2_INCLIST # ifdef E2_VFS if (_e2p_acl_copyacls (&sdata, &statbuf, &ddata)) # else if (_e2p_acl_copyacls (src->str, &statbuf, dest->str)) # endif { //FIXME update line in treeview } #else # ifdef E2_FAM # ifdef E2_VFS _e2p_acl_copyacls (&sdata, &statbuf, &ddata); # else _e2p_acl_copyacls (src->str, &statbuf, dest->str); # endif # else # ifdef E2_VFS if (_e2p_acl_copyacls (&sdata, &statbuf, &ddata)) { if (ddata.spacedata == NULL) { ddata.localpath = qed->othrdir; e2_fs_touchnow (&ddata E2_ERR_NONE()); } } # else if (_e2p_acl_copyacls (src->str, &statbuf, dest->str)) //make the file-list refresher notice successful change e2_fs_touchnow (qed->othrdir E2_ERR_NONE()); # endif # endif #endif } } g_string_free (src, TRUE); g_string_free (dest, TRUE); e2_window_clear_status_message (); #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, acl task"); #endif e2_filelist_enable_refresh (); return TRUE; } #endif //def ACLEXTRA_ACTIONS /** @brief show/change extended permissions of selected item(s) in active pane If > 1 item is selected, a dialog is created for each such item in turn (or until the user chooses stop or apply-to-all) @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if action completed successfully, else FALSE */ static gboolean _e2p_task_acl (gpointer from, E2_ActionRuntime *art) { return (e2_task_enqueue_task (E2_TASK_CHACL, art, from, _e2p_task_aclQ, e2_task_refresh_lists)); } static gboolean _e2p_task_aclQ (E2_ActionTaskData *qed) { //printd (DEBUG, "task: acl"); #ifdef E2_VFS if (qed->currspace != NULL) return FALSE; //only manage ACL's for local items #endif gpointer workspace = acl_init (2); //working heapspace for retrieved access and default ACLs if (workspace == NULL) //FIXME warn user return FALSE; // printd (DEBUG, "task: acl"); GPtrArray *names = qed->names; gchar *curr_local = qed->currdir; GPtrArray *axs_changes = NULL; GPtrArray *def_changes = NULL; guint count; gint myuid = -1; gboolean multisrc = names->len > 1; gboolean all = FALSE; E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata; GString *path = g_string_sized_new (PATH_MAX+NAME_MAX); #ifdef E2_VFS VPATH ddata; ddata.spacedata = qed->currspace; #endif /* out-of-loop setup = FIXME GtkWidget *dialog; dialog = e2_permissions_dialog_setup (info, &recurse); e2_dialog_add_buttons_simple (dialog, buttons ..., NULL); */ #ifdef E2_REFRESH_DEBUG printd (DEBUG, "disable refresh, acl task"); #endif e2_filelist_disable_refresh (); e2_task_advise (); for (count=0; count < names->len; count++, iterator++) { DialogButtons choice; E2_ACLTask task = 0; //assignment for warning prevention gboolean permission; //".." entries filtered when names compiled //FIXME for single-setup: instead of the following, adjust file details in dialog, reset default button g_string_printf (path, "%s%s", curr_local, (*iterator)->filename); //separator comes with dir #ifdef E2_VFS ddata.localpath = path->str; #endif if (all) { //check if we have permission to change this item //NB this tests _all_ w permissions, chmod is not so sophisticated ... #ifdef E2_VFS permission = e2_fs_check_write_permission (&ddata E2_ERR_NONE()); #else permission = e2_fs_check_write_permission (path->str E2_ERR_NONE()); #endif if (permission) choice = OK; else { #ifdef E2_VFS ddata.localpath = (*iterator)->filename; #endif e2_fs_error_simple ( _("You do not have authority to change permission(s) of %s"), #ifdef E2_VFS &ddata); #else (*iterator)->filename); #endif choice = CANCEL; } } else { #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, acl dialog"); #endif e2_filelist_enable_refresh (); //allow updates while we wait *qed->status = E2_TASK_PAUSED; #ifdef E2_VFS choice = _e2p_acl_dialog_run (&ddata, &axs_changes, &def_changes, #else choice = _e2p_acl_dialog_run (path->str, &axs_changes, &def_changes, #endif &task, &permission, multisrc); *qed->status = E2_TASK_RUNNING; #ifdef E2_REFRESH_DEBUG printd (DEBUG, "disable refresh, permissions dialog"); #endif e2_filelist_disable_refresh (); } switch (choice) { case YES_TO_ALL: all = TRUE; myuid = getuid (); //do this once, for speed choice = OK; case OK: if (permission && (axs_changes != NULL || def_changes != NULL)) { #ifdef E2_INCLIST if (_e2p_acl_change (path->str, axs_changes, def_changes, task)) { //FIXME update line in treeview } #else # ifdef E2_FAM # ifdef E2_VFS _e2p_acl_change (&ddata, axs_changes, def_changes, task); # else _e2p_acl_change (path->str, axs_changes, def_changes, task); # endif # else # ifdef E2_VFS if (_e2p_acl_change (&ddata, axs_changes, def_changes, task)) { if (ddata.spacedata == NULL) { ddata.localpath = qed->currdir; e2_fs_touchnow (&ddata E2_ERR_NONE()); } } # else if (_e2p_acl_change (path->str, axs_changes, def_changes, task)) //make the file-list refresher notice successful change e2_fs_touchnow (qed->currdir E2_ERR_NONE()); # endif # endif #endif if (!all) { if (axs_changes != NULL) { g_ptr_array_foreach (axs_changes, (GFunc)_e2p_acl_clean_changes, NULL); g_ptr_array_free (axs_changes, TRUE); axs_changes = NULL; } if (def_changes != NULL) { g_ptr_array_foreach (def_changes, (GFunc)_e2p_acl_clean_changes, NULL); g_ptr_array_free (def_changes, TRUE); def_changes = NULL; } } } case CANCEL: break; default: choice = NO_TO_ALL; // break flag; break; } if (choice == NO_TO_ALL) break; } if (axs_changes != NULL) { g_ptr_array_foreach (axs_changes, (GFunc)_e2p_acl_clean_changes, NULL); g_ptr_array_free (axs_changes, TRUE); axs_changes = NULL; } if (def_changes != NULL) { g_ptr_array_foreach (def_changes, (GFunc)_e2p_acl_clean_changes, NULL); g_ptr_array_free (def_changes, TRUE); def_changes = NULL; } acl_free (workspace); g_string_free (path, TRUE); e2_window_clear_status_message (); #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, acl task"); #endif e2_filelist_enable_refresh (); return TRUE; } //aname must be confined to this module static gchar *aname; #ifdef ACLEXTRA_ACTIONS static gchar *aname2; #endif /** @brief plugin initialization function, called by main program @param p ptr to plugin data struct @return TRUE if the initialization succeeds, else FALSE */ gboolean init_plugin (Plugin *p) { #define ANAME "acl" aname = _("acl"); p->signature = ANAME VERSION; #ifdef ACLEXTRA_ACTIONS p->menu_name = _("_Access"); p->description = ""; #else p->menu_name = _("_Access.."); p->description = _("Change extended permissions of selected items"); #endif p->icon = "plugin_"ANAME"_"E2IP".png"; //use icon file pathname if appropriate //child UI data gchar *sig1 = "0-"ANAME; gchar *label1 = _("Change _ACLs.."); // gchar *icon1 = ""; //no icon "plugin_"ANAME"_"E2IP".png"; //use icon file pathname if appropriate gchar *tip1 = _("Change extended permissions of selected items"); gchar *sig2 = "1-"ANAME; gchar *label2 = _("_Replicate"); // gchar *icon2 = ""; gchar *tip2 = _("Recursively apply ACLs of selected items to matching items in the other pane"); if (p->action == NULL) { guint i; for (i = 0; i < CLASSCOUNT; i++) { classinames[i] = gettext (classnames[i]); } #ifdef ACLEXTRA_ACTIONS gboolean retval = TRUE; Plugin *pc = e2_plugins_create_child (p); if (pc != NULL) { //for reconciling with config treestore data, signatures must reflect //0-based index of each child's order in the list of children pc->signature = sig1; //begin with "n-", n=0,1, ... //these will generallly be discarded in favour of config treestore data //meaning that any non-constant string will leak pc->menu_name = label1; //or whatever pc->description = tip1; // pc->description = p->description; //or whatever // pc->icon = "plugin_"ANAME"_"E2IP".png"; //use icon file pathname if appropriate //no need to free this gchar *action_name = g_strconcat (_A(5),".",aname,NULL); pc->action = e2_plugins_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2p_task_acl, NULL, FALSE, 0, NULL); } else retval = FALSE; //this will not be used, but MUST be set to allow checking whether to show //the item in the context menu if (retval) p->action = pc->action; //moving an item preserves its acl(s) unless the process falls back to a copy between devices pc = e2_plugins_create_child (p); if (pc != NULL) { aname2 = _("copy_acl"); pc->signature = sig2; //for reconciling with config treestore data pc->menu_name = label2; pc->description = tip2; // pc->icon = "plugin_"?"_"E2IP".png"; //use icon file pathname if appropriate //no need to free this gchar *action_name = g_strconcat (_A(5),".",aname2,NULL); pc->action = e2_plugins_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2p_task_aclcopy, GINT_TO_POINTER (E2_FTM_SAMEACL), FALSE, 0, NULL); } else retval = FALSE; //this will not be used, but MUST be set to allow checking whether to show //the item in the context menu if (retval && (p->action == NULL)) p->action = pc->action; #else //ndef ACLEXTRA_ACTIONS //no need to free this gchar *action_name = g_strconcat (_A(5),".",aname,NULL); p->action = e2_plugins_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2p_task_acl, NULL, FALSE, 0, NULL); #endif //setup to preserve acl's during copying //but if some acl-using file-operations are in progress, defer pthread_mutex_lock (&task_mutex); while (1) { GList *member = app.taskhistory; while (member != NULL) { E2_TaskRuntime *rt = (E2_TaskRuntime *) member->data; recheck: if (rt != NULL && (rt->status == E2_TASK_RUNNING || rt->status == E2_TASK_PAUSED)) { if (rt->action) { E2_TaskType tt = rt->ex.action.tasktype; switch (tt) { case E2_TASK_COPY: case E2_TASK_COPYAS: case E2_TASK_MOVE: //may revert to a copy case E2_TASK_MOVEAS: case E2_TASK_TRASH: usleep (200000); goto recheck; break; default: member = NULL; break; } if (member == NULL) break; } } member = member->next; } if (member == NULL) { chaclfunc = _e2p_acl_copyacls; //turn on the acl callback break; } } pthread_mutex_unlock (&task_mutex); return TRUE; } else //setup children UI data for pushing into a config dialog { E2_Sextet *uidata; uidata = e2_utils_sextet_new (); p->child_list = g_list_append (p->child_list, uidata); uidata->a = label1; uidata->b = ""; //no icon uidata->c = tip1; uidata->d = sig1; uidata = e2_utils_sextet_new (); p->child_list = g_list_append (p->child_list, uidata); uidata->a = label2; uidata->b = ""; uidata->c = tip2; uidata->d = sig2; } return FALSE; } /** @brief cleanup transient things for this plugin @param p pointer to data struct for the plugin @return TRUE if all cleanups were completed */ gboolean clean_plugin (Plugin *p) { //if some acl-using file-operations are in progress, defer the unload pthread_mutex_lock (&task_mutex); while (1) { GList *member = app.taskhistory; while (member != NULL) { E2_TaskRuntime *rt = (E2_TaskRuntime *) member->data; recheck: if (rt != NULL && (rt->status == E2_TASK_RUNNING || rt->status == E2_TASK_PAUSED)) { if (rt->action) { E2_TaskType tt = rt->ex.action.tasktype; switch (tt) { case E2_TASK_COPY: case E2_TASK_COPYAS: case E2_TASK_MOVE: //may revert to a copy case E2_TASK_MOVEAS: case E2_TASK_TRASH: case E2_TASK_CHACL: usleep (200000); goto recheck; break; default: member = NULL; break; } if (member == NULL) break; } } member = member->next; } if (member == NULL) { chaclfunc = NULL; //ok to turn-off the acl callback break; } } pthread_mutex_unlock (&task_mutex); #ifdef ACLEXTRA_ACTIONS gchar *action_name = g_strconcat (_A(5),".",aname,NULL); gboolean ret1 = e2_plugins_action_unregister (action_name); g_free (action_name); /* if (ret1) { } */ action_name = g_strconcat (_A(5),".",aname2,NULL); gboolean ret2 = e2_plugins_action_unregister (action_name); g_free (action_name); /* if (ret2) { } */ return (ret1 && ret2); #else gchar *action_name = g_strconcat (_A(5),".",aname,NULL); gboolean ret = e2_plugins_action_unregister (action_name); g_free (action_name); return ret; #endif } #ifdef FAKEACLSTORE # undef FAKEACLSTORE #endif emelfm2-0.4.1/plugins/optional/plugin_thumbs_48.png0000600000175000017500000000352310607017727021230 0ustar cairocairoPNG  IHDR00WsBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<IDATh͙[lgfݵ' 1 Q.m!u. dPjGEK MJ}@^UI[ MCcAc IX$q ˮ3s^_vgkzw]hVH)Yx=Xbh38} cE܊ 8Fo~} X^6oBch's<{oDr /=^/xHMX ]C>[eF& i?LS!GwOc+0{)%=G&dھfgw_*N@"W@F+h^As\CLnRNY\;36`z6ToP YeI (n PrCn4@S9'29$+w(B2@BzZ kl? %ejwMdzVAUez,40l@%TT25E$wۋp&:h' N{6ѹNmI"0Xv$,byk.%0t1V(vY"zĦ 5$q~=W[m3"GԤm`!!?1)y-my`:X;6b+f hI4-г#l ھhIc~i0vf-}YDcP+B=?D9v\1BuM 5X*W幗Co.; phb1DWY{=w7 5F[_Yؽn9+3#* 0kcUJ`{[>4ö*jjT<75F4h OG"'h,l^DZ5wP0c; 6וo7mMqWu$Fz~5B{~$Ŝ)!4g-~#cGOX]f|=Q PhPܷeekҬ XJ.@(:;=uhL G>g xMU՟wvX*J]!SpKW;>BhBXKAj> QmR* ,i)"G@9ԵMf!\%N%!P,4˲r/.mYjBł9jIENDB`emelfm2-0.4.1/plugins/e2p_pack.c0000600000175000017500000002034210714030042015310 0ustar cairocairo/* $Id: e2p_pack.c 706 2007-11-06 09:13:06Z tpgww $ Copyright (C) 2003-2007 tooar Portions copyright (C) 1999 Michael Clark This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file plugins/e2p_pack.c @brief plugin for interfacting with several archive managers, to pack selected item(s) */ #include "emelfm2.h" #include #include "e2_plugins.h" #include "e2_dialog.h" #include "e2_command.h" #include "e2_task.h" #include "e2_filelist.h" //same enum as in unpack plugin enum { TAR_GZ, TAR_BZ2, TAR, ZIP, Z7Z, RAR, ARJ, ZOO, MAXTYPES }; typedef struct _E2_PackDlgRunTime { GtkWidget *dialog; GtkWidget *filename_entry; GtkWidget *pkgtype_combo; gchar *curr_dir; //utf8 form of qed->currdir (with trailer) } E2_PackDlgRunTime; static gint pkg_type = TAR_GZ; //in theory, we should protect this by mutex //these extension strings are in same order as enum //CHECKME do these really need to be translated ? static gchar *ext_str [MAXTYPES] = { N_(".tar.gz"), N_(".tar.bz2"), N_(".tar"), N_(".zip"), N_(".7z"), N_(".rar"), N_(".arj"), N_(".zoo") }; static gboolean _e2p_packQ (E2_ActionTaskData *qed); static void _e2p_pack_yes (E2_PackDlgRunTime *rt) { static gchar *cmd_str [MAXTYPES] = { //these command strings are in same order as enum //NOTE that %%f is converted to %f when the command is constructed with g_strdup_printf () ">tar cvf - %%f | gzip - > \"%s\"", //run in separate shell ">tar cvf - %%f | bzip2 - > \"%s\"", //ditto "tar cvf \"%s\" %%f", "zip -r \"%s\" %%f", "7za a -t7z \"%s\" %%f", "rar a -r -ol \"%s\" %%f", "arj a -a1 -r \"%s\" %%f", "zoo ahP \"%s\" %%f" }; /* compress ANSI files: 7za a -tzip archive.zip file1 file2 ... fileN compress ANSI dir: 7za a -tzip archive.zip dirnametocompress\ compress UNICODE files: 7za a -t7z archive.7z file1 file2 ... fileN compress UNICODE dir: 7za a -t7z archive.7z dirnametocompress\ decompress ANSI: 7za x archive.zip -odirname -aoa decompress UNICODE: 7za x archive.7z -odirname -aoa */ pkg_type = gtk_combo_box_get_active (GTK_COMBO_BOX (rt->pkgtype_combo)); if (pkg_type != -1) { gchar *full_name; const gchar *chosen_name = gtk_entry_get_text (GTK_ENTRY (rt->filename_entry)); gboolean flag = (*chosen_name != '\0'); if (flag) { full_name = g_strconcat (chosen_name, ext_str [pkg_type], NULL); if (e2_option_bool_get ("confirm-overwrite")) { #ifdef E2_VFSTMP //FIXME dir when not mounted local #else gchar *utf = g_strconcat (rt->curr_dir, full_name, NULL); //separator comes with dir #endif gchar *dlocal = F_FILENAME_TO_LOCALE (utf); #ifdef E2_VFS VPATH ddata = { dlocal, NULL }; //running a command is only local if (e2_fs_access2 (&ddata E2_ERR_NONE()) == 0) #else if (e2_fs_access2 (dlocal E2_ERR_NONE()) == 0) #endif { //same-named item exists already //FIXME some apps allow addition to existing archive DialogButtons result = e2_dialog_ow_check (NULL, dlocal, NONE); if (result != OK) { flag = FALSE; //signal that we will not proceed g_free (full_name); } } g_free (utf); F_FREE (dlocal); } } if (flag) { gchar *command = g_strdup_printf (cmd_str [pkg_type], full_name); // e2_filelist_disable_refresh (); //prevent changes to selected item data // gint res = #ifdef E2_COMMANDQ e2_command_run (command, E2_COMMAND_RANGE_DEFAULT, FALSE); #else e2_command_run (command, E2_COMMAND_RANGE_DEFAULT); #endif // e2_filelist_enable_refresh (); //probably nothing reported yet, to trigger a refresh // flag = (res == 0); g_free (command); g_free (full_name); } } else pkg_type = TAR_GZ; } /** @brief callback for dialog's "response" signal @param dialog the dialog where the response was triggered @param response the response for the clicked button @param rt pointer to data for dialog @return */ static void _e2p_pack_response_cb (GtkDialog *dialog, gint response, E2_PackDlgRunTime *rt) { switch (response) { case GTK_RESPONSE_OK: gtk_widget_hide (rt->dialog); _e2p_pack_yes (rt); break; default: break; } gtk_widget_destroy (rt->dialog); g_free (rt->curr_dir); DEALLOCATE (E2_PackDlgRunTime, rt); } /** @brief handle activation ( keypresses) in the query entry @param entry UNUSED the entry widget for the combo box @param rt pointer to dialog data struct @return */ static void _e2p_pack_activated_cb (GtkEntry *entry, E2_PackDlgRunTime *rt) { _e2p_pack_response_cb (GTK_DIALOG (rt->dialog), GTK_RESPONSE_OK, rt); } /** @brief create and run dialog @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if action completed successfully, else FALSE */ static gboolean _e2p_pack (gpointer from, E2_ActionRuntime *art) { return (e2_task_enqueue_task (E2_TASK_PACK, art, from, _e2p_packQ, NULL)); } static gboolean _e2p_packQ (E2_ActionTaskData *qed) { E2_PackDlgRunTime *rt = ALLOCATE (E2_PackDlgRunTime); CHECKALLOCATEDWARNT (rt, return FALSE;) rt->dialog = e2_dialog_create (NULL, _("Filename:"), _("archive creation"), _e2p_pack_response_cb, rt); GtkWidget *hbox = g_object_get_data (G_OBJECT (rt->dialog), "e2-dialog-hbox"); GPtrArray *names = qed->names; E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata; gchar *suggested_name = F_FILENAME_FROM_LOCALE ((*iterator)->filename); rt->filename_entry = e2_widget_add_entry (hbox, suggested_name, TRUE, TRUE); F_FREE (suggested_name); #ifdef E2_ASSISTED GtkWidget *label = (GtkWidget *) g_object_get_data (G_OBJECT (rt->dialog), "e2-dialog-label"); e2_widget_set_label_relations (GTK_LABEL (label), rt->filename_entry); #endif rt->pkgtype_combo = e2_combobox_add (hbox, TRUE, 0, NULL, NULL, NULL, E2_COMBOBOX_MENU_STYLE); g_signal_connect (G_OBJECT (rt->filename_entry), "activate", G_CALLBACK (_e2p_pack_activated_cb), rt); //these package name strings are in same order as enum //CHECKME do these really need to be translated ? /* gchar *type_choices [MAXTYPES] = { _(".tar.gz"), //index 0 _(".tar.bz2"), _(".tar"), _(".zip"), _(".7z"), _(".rar"), _(".arj") _(".zoo")}; //index 6 e2_combobox_append_history_counted (rt->pkgtype_combo, MAXTYPES, type_choices); */ guint i; for (i = 0; i < MAXTYPES; i++) gtk_combo_box_append_text (GTK_COMBO_BOX (rt->pkgtype_combo), gettext (ext_str[i])); gtk_combo_box_set_active (GTK_COMBO_BOX (rt->pkgtype_combo), pkg_type); rt->curr_dir = D_FILENAME_FROM_LOCALE (qed->currdir); gdk_threads_enter (); e2_dialog_show (rt->dialog, app.main_window, 0, &E2_BUTTON_CANCEL, &E2_BUTTON_OK, NULL); gdk_threads_leave (); return TRUE; } //aname must be confined to this module static gchar *aname; /** @brief plugin initialization function, called by main program @param p ptr to plugin data struct @return TRUE if the initialization succeeds, else FALSE */ gboolean init_plugin (Plugin *p) { #define ANAME "pack" aname = _("pack"); p->signature = ANAME VERSION; p->menu_name = _("_Pack.."); p->description = _("Build an archive containing the selected item(s)"); p->icon = "plugin_"ANAME"_"E2IP".png"; //use icon file pathname if appropriate if (p->action == NULL) { //no need to free this gchar *action_name = g_strconcat (_A(5),".", aname, NULL); p->action = e2_plugins_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2p_pack, NULL, FALSE, 0, NULL); return TRUE; } return FALSE; } /** @brief cleanup transient things for this plugin @param p pointer to data struct for the plugin @return TRUE if all cleanups were completed */ gboolean clean_plugin (Plugin *p) { gchar *action_name = g_strconcat (_A(5),".",aname,NULL); gboolean ret = e2_plugins_action_unregister (action_name); g_free (action_name); return ret; } emelfm2-0.4.1/plugins/e2p_cpbar.c0000600000175000017500000006750110667510534015511 0ustar cairocairo/* $Id: e2p_cpbar.c 653 2007-09-05 11:22:04Z tpgww $ ************************************** Emelcopybar - a plugin for emelFM2 by Florian Zaehringer with help from tooar and tom to port it to Gtk+2 source of source-code and inspiration: emelFM by Michael Clark ************************************** This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file plugins/e2p_cpbar.c @brief plugin for copying selected items, with a progress-bar */ #include "emelfm2.h" #include #include #include #include "e2_plugins.h" #include "e2_dialog.h" #include "e2_option.h" #include "e2_filelist.h" #include "e2_task.h" //max. no. of _bytes_, at the end of a copied item's source-path //and dest-path, shown in the progress dialog //paths longer than this will be 'ellipsized' #define MAX_CHAR 55 //size of buffer for progress dialog //make this >= (MAX_CHAR+4)*2 + space for rest of message text //#define MAX_MSG 220 //interval (usec) between progress window updates //for items < and > 10MB resepcively #define MIN_UPDATE_INTERVAL 100000 #define MAX_UPDATE_INTERVAL 200000 //include things to support pause and resume #ifndef IS_PAUSABLE #define IS_PAUSABLE #endif #ifdef IS_PAUSABLE typedef enum { E2_BARTASK_STOPPED = 1, E2_BARTASK_PAUSEREQ = 1 << 1, //pause requested E2_BARTASK_PAUSED = 1 << 2, //actually paused E2_BARTASK_COMPLETED = 1 << 3, E2_BARTASK_SUCCEEDED = 1 << 4, } E2_BarFlags; #endif typedef struct _E2_BarData { guint64 count; //count of items to be processed, in active pane guint64 totalsize; //aggregate apparent size of items to be processed } E2_BarData; typedef struct _E2_ProgressData { pthread_mutex_t mutex; //protects access to these data pthread_cond_t cond; //signals change VPATH *dlocal; //includes localised destination path (maybe temp) of item copied guint64 done_size; guint64 refresh_interval; } E2_ProgressData; typedef struct _E2_ActionData { // pthread_mutex_t mutex; //protects access to these data E2_FileTaskMode flags; //flags for features of current copy VPATH *slocal; //includes localised source path of item copied VPATH *dlocal; //includes localised destination path of item copied gboolean completed; //set TRUE when the backend task is completed (valid or not) gboolean result; //value returned by backend task function } E2_ActionData; typedef struct _E2_BarWindowData { GtkWidget *dialog; GtkWidget *label; // GtkWidget *label2; GtkWidget *progbar; #ifdef IS_PAUSABLE GtkWidget *pause_btn; GtkWidget *resume_btn; GtkWidget *stop_btn; E2_BarFlags flags; #endif } E2_BarWindowData; static gboolean _e2p_cpbarQ (E2_ActionTaskData *qed); /** @brief update items' count and total size This is a callback for a treewalk function Error message expects BGL to be open @param localpath absolute path of item reported by the walker, localised string @param statptr pointer to struct stat with data about @a localpath @param status code from the walker, indicating what type of report it is @param twdata pointer to tw data struct @return E2TW_CONTINUE always */ static E2_TwResult _e2p_cpbar_twcb (VPATH *localpath, const struct stat *statptr, E2_TwStatus status, E2_BarData *twdata) { switch (status) { case E2TW_F: //not dir or link case E2TW_SL: //symbolic link case E2TW_SLN: //symbolic link naming non-existing file twdata->totalsize += statptr->st_size; case E2TW_DL: //dir, not opened due to tree-depth limit (reported upstream) case E2TW_DM: //dir, not opened due to different file system (reported upstream) case E2TW_D: //dir (don't care about its reported size) case E2TW_DRR: //dir now readable case E2TW_DNR: //unreadable dir (reported upstream) case E2TW_NS: //un-statable item (reported upstream) twdata->count++; // case E2TW_DP: //dir, finished default: break; } return E2TW_CONTINUE; } /** @brief 'shorten' string @a string if it is longer than allowed @a dots is set to "..." if @a string is too long, or else "" @param string utf string which may need to be shortened @param dots pointer to store for utf string which will preceed @a string, when it is displayed @param string_diff pointer to store for the no. of bytes to be omitted from the start of @a string, when it is displayed @return */ /*static void _e2p_cpbar_shorten (gchar *string, gchar **dots, gint *string_diff) { if (strlen (string) > MAX_CHAR) { //nominally we will drop this many bytes off the start of string gint trunc = strlen (string) - MAX_CHAR; //but make sure we don't cut off in the middle of a char gchar *c = g_utf8_find_next_char (string+trunc-1, NULL); *string_diff = c - string; *dots = "..."; } else { *string_diff = 0; *dots = ""; } return; } */ /** @brief thread function to determine how much progress has been made on the current item This provides data for progressive updates during the course of copying an item. The thread doesn't end of its own accord, but continues until it is cancelled by the main thread BGL will be open @param data pointer to struct with data which is needed here @return NULL (never happens) */ static void * _e2p_cpbar_progress (E2_ProgressData *data) { gchar *localpath; struct stat sb; #ifdef E2_VFS VPATH ddata; #endif // printd (DEBUG, "monitor thread underway"); e2_utils_block_thread_signals (); //block all allowed signals to this thread #ifdef E2_VFS ddata.spacedata = data->dlocal->spacedata; #endif while (TRUE) { pthread_testcancel (); //has a cancel-instruction been issued ? //prevent the main thread from setting the cancel signal now if (pthread_mutex_lock (&(data->mutex))) { printd (WARN, "Lock error!"); } //get a local copy of the present item's destination path //(that will be NULL if the action thread has finished already) if (data->dlocal == NULL) localpath = NULL; else localpath = g_strdup (VPSTR (data->dlocal)); if (pthread_mutex_unlock (&(data->mutex))) { printd (WARN,"Unlock error!"); } if (localpath != NULL) { //we're still underway //determine how much of current item has been copied E2_BarData pdata; pdata.totalsize = 0; // pdata.count = 0; #ifdef E2_VFS ddata.localpath = localpath; if (!e2_fs_lstat (&ddata, &sb E2_ERR_NONE())) #else if (!e2_fs_lstat (localpath, &sb E2_ERR_NONE())) #endif { if (S_ISDIR (sb.st_mode)) { //if (! #ifdef E2_VFS e2_fs_tw (&ddata, #else e2_fs_tw (localpath, #endif _e2p_cpbar_twcb, &pdata, -1, E2TW_PHYS E2_ERR_NONE()); //) //{ //FIXME handle error //} } else // { pdata.totalsize = sb.st_size; // pdata.count = 1; // } } g_free (localpath); if (pthread_mutex_lock (&(data->mutex))) { printd (WARN, "Lock error!\n"); } //update size data for the main thread to use data->done_size = pdata.totalsize; // printd (DEBUG, "progress is %li", pdata.totalsize); if (pthread_cond_signal (&data->cond)) { printd (WARN,"Signal error!"); } if (pthread_mutex_unlock (&(data->mutex))) { printd (WARN,"Unlock error!"); } } //usleep is another cancellation point usleep (data->refresh_interval); //delay between updates } return NULL; } /** @brief perform the copy This is the thread function which performs the copy task. The thread simply exits when completed @param data pointer to struct with data which is needed here @return NULL (irrelevant pointer) */ static void * _e2p_cpbar_action (E2_ActionData *data) { // printd (DEBUG, "action thread underway for %s", data->slocal); e2_utils_block_thread_signals (); //block all allowed signals to this thread data->result = e2_task_backend_copy //no background-copy option in this context (data->slocal, data->dlocal, data->flags); data->completed = TRUE; return NULL; } /** @brief update progress window details, and copy item @a src to @a dest This is called with BGL open @param slocal path of item to be copied, localised string @param dlocal new path of copied item, localised string @param breakflag pointer to store for abort-flag @param flags bitflags indicating task parameters @param bdata pointer to bar data struct holding progress data @param tdata pointer to bar data struct holding totals data @param wdata pointer to info-window data struct @return */ static void _e2p_cpbar_exec (VPATH *slocal, VPATH *dlocal, #ifndef IS_PAUSABLE gboolean *breakflag, #endif E2_FileTaskMode flags, E2_BarData *bdata, E2_BarData *tdata, E2_BarWindowData *wdata) { gchar progresstext[64]; //utf-8 string, middle line of progress dialog //localised strings gchar *src = F_FILENAME_FROM_LOCALE (VPSTR (slocal)); // gchar *dest = F_FILENAME_FROM_LOCALE (VPSTR (dlocal)); gchar *dest_dir = g_path_get_dirname (VPSTR (dlocal)); //before copy starts, get size of item to be copied E2_BarData pdata = { 0,0 }; //if (! e2_fs_tw (slocal, _e2p_cpbar_twcb, &pdata, -1, E2TW_PHYS E2_ERR_NONE()); //) //{ //FIXME handle error //} //create action thread //work with a temp name so that the backend doesn't create its own //(which would prevent destination-monitoring) gchar *templocal = e2_utils_get_tempname (VPSTR (dlocal)); #ifdef E2_VFS VPATH tempdata = { templocal, dlocal->spacedata }; E2_ActionData a_data = { flags, slocal , &tempdata, FALSE, FALSE }; #else E2_ActionData a_data = { flags, slocal , templocal, FALSE, FALSE }; #endif pthread_t action_thread_id; gint status = pthread_create (&action_thread_id, NULL, (void *) _e2p_cpbar_action, &a_data); if (status != 0) { printd (WARN,"action-thread-create error!"); // if (errno == EAGAIN) // printd (WARN, "not enough system resources"); g_free (templocal); return; } //wait for task to initiate, at least g_usleep (50000); //create progress monitor, if task not finished already if (!a_data.completed) { //create monitor thread E2_ProgressData m_data; pthread_mutex_init (&(m_data.mutex), NULL); pthread_cond_init (&(m_data.cond), NULL); //get the temp name now in use for copying the item #ifdef E2_VFS m_data.dlocal = &tempdata; #else m_data.dlocal = templocal; #endif m_data.done_size = 0; //rough approach to setting the reporting interval (usec) // m_data.refresh_interval = pdata.totalsize * 10; // if (m_data.refresh_interval > MAX_UPDATE_INTERVAL) if (pdata.totalsize < 10000000) m_data.refresh_interval = MIN_UPDATE_INTERVAL; else m_data.refresh_interval = MAX_UPDATE_INTERVAL; //with a cap pthread_t mon_thread_id; status = pthread_create (&mon_thread_id, NULL, (gpointer) _e2p_cpbar_progress, &m_data); if (status != 0) { printd (WARN,"monitor-thread-create error!"); g_free (templocal); return; } if (!GTK_WIDGET_VISIBLE (wdata->dialog)) { gdk_threads_enter (); gtk_widget_show (wdata->dialog); gdk_threads_leave (); } gchar *shortsrc = e2_utils_str_shorten (src, MAX_CHAR, E2_DOTS_START); gchar *shortdest = e2_utils_str_shorten (dest_dir, MAX_CHAR, E2_DOTS_START); gchar *num1 = g_strdup_printf ("%"PRIu64, bdata->count); //gettext workaround gchar *num2 = g_strdup_printf ("%"PRIu64, tdata->count); gchar *labeltext = g_strdup_printf ( _("copying %s\nto %s\nthis is item %s of %s"), shortsrc, shortdest, num1, num2); gdk_threads_enter (); gtk_label_set_text (GTK_LABEL (wdata->label), labeltext); gdk_threads_leave (); g_free (shortsrc); g_free (shortdest); g_free (num1); g_free (num2); g_free (labeltext); gchar *progress_format = _("%.2f MB of %.2f MB (%.0f\%%)"); gfloat fraction; //update progress when the monitor thread reports guint64 progress; while (!a_data.completed) //loop until the action thread is completed { if (pthread_mutex_lock (&(m_data.mutex))) { printd (WARN, "Lock error!"); } while (m_data.done_size == 0) { status = pthread_cond_wait (&(m_data.cond), &(m_data.mutex)); } if (status != 0) { printd (WARN,"Wait error!"); } progress = m_data.done_size + bdata->totalsize; m_data.done_size = 0; //reset if (pthread_mutex_unlock (&(m_data.mutex))) { printd (WARN,"Unlock error!"); } #ifdef IS_PAUSABLE if (wdata->flags & E2_BARTASK_STOPPED) //user wants to abort #else if (*breakflag) //user wants to abort #endif { if (pthread_cancel (mon_thread_id)) { printd (WARN,"Thread cancel error!\n"); } //cancel the task thread if (pthread_cancel (action_thread_id)) { printd (WARN,"Thread cancel error!\n"); } //cleanup anything part-copied #ifdef E2_VFS e2_task_backend_delete (&tempdata); #else e2_task_backend_delete (templocal); #endif g_free (templocal); return; } fraction = (gdouble) progress / tdata->totalsize; //deal with rounding errors if (fraction > 1.0) fraction = 1.0; g_snprintf (progresstext, sizeof (progresstext), progress_format, progress / 1048576.0, tdata->totalsize / 1048576.0, fraction * 100.0); gdk_threads_enter (); gtk_progress_bar_set_text (GTK_PROGRESS_BAR (wdata->progbar), progresstext); gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (wdata->progbar), fraction); //CHECKME some sort of wait here ?? gdk_threads_leave (); /* USELESS, doesn't stop the copy function AND this is the main thread, we want to pause the monitor & action threads instead #ifdef IS_PAUSABLE if (wdata->flags & E2_BARTASK_PAUSEREQ) //user wants to pause { status = kill (action_thread_id, SIGSTOP); NOT ALLOWED if (status == -1) printd (DEBUG, "signal to pause the action thread returned error %d", errno); pause (); //take a break until a SIGCONT signal is received kill (action_thread_id, SIGCONT); } #endif */ } //immediate signal to monitor thread to stop processing if (pthread_mutex_lock (&(m_data.mutex))) { printd (WARN, "Lock error!"); } m_data.dlocal = NULL; if (pthread_mutex_unlock (&(m_data.mutex))) { printd (WARN,"Unlock error!"); } //show the full-time score ASAP if (a_data.result) //copy succeeded { progress = pdata.totalsize + bdata->totalsize; g_snprintf (progresstext, sizeof (progresstext), progress_format, progress / 1048576.0, tdata->totalsize / 1048576.0, 100.0); gdk_threads_enter (); gtk_progress_bar_set_text (GTK_PROGRESS_BAR (wdata->progbar), progresstext); gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (wdata->progbar), (gdouble) progress / tdata->totalsize); gdk_threads_leave (); //update progressive total bdata->totalsize = progress; } //cancel the monitor thread if (pthread_cancel (mon_thread_id)) { printd (WARN,"Thread cancel error!\n"); } //make sure things are finished before we move on pthread_join (action_thread_id, NULL); pthread_join (mon_thread_id, NULL); //CHECKME some sort of wait here ?? } else bdata->totalsize += pdata.totalsize; if (a_data.result) //copy succeeded #ifdef E2_VFS e2_task_backend_rename (&tempdata, dlocal); else e2_task_backend_delete (&tempdata); //cleanup anything part-copied #else e2_task_backend_rename (templocal, dlocal); else e2_task_backend_delete (templocal); //cleanup anything part-copied #endif g_free (templocal); #ifdef IS_PAUSABLE if (wdata->flags & E2_BARTASK_PAUSEREQ) { //if it was paused, the dialog must be still visible wdata->flags &= ~E2_BARTASK_PAUSEREQ; wdata->flags |= E2_BARTASK_PAUSED; e2_filelist_enable_refresh (); gdk_threads_enter (); gtk_main (); gdk_threads_leave (); } #endif return; } #ifdef IS_PAUSABLE /** @brief callback for stop-button press and window-close event @param widget the activated widget which triggered the callback @param flag pointer to store for flag which will abort the process when set to TRUE; @return TRUE always */ static gboolean _e2p_cpbar_break_cb (GtkWidget *widget, E2_BarWindowData *wdata) { wdata->flags |= E2_BARTASK_STOPPED; //handle paused copying if (wdata->flags & E2_BARTASK_PAUSED) { wdata->flags &= ~E2_BARTASK_PAUSED; e2_filelist_disable_refresh (); gtk_main_quit (); //FIXME = cleanups // pthread_cancel (); } return TRUE; } /** @brief callback for pause-button press @param widget the button widget which triggered the callback @param resbutton pointer to paired button widget @return TRUE always */ static gboolean _e2p_cpbar_pause_cb (GtkWidget *widget, E2_BarWindowData *wdata) { gtk_widget_set_sensitive (widget, FALSE); gtk_widget_set_sensitive (wdata->resume_btn, TRUE); gtk_widget_grab_focus (wdata->resume_btn); wdata->flags |= E2_BARTASK_PAUSEREQ; //actual pause is started near end of _e2p_cpbar_exec() return TRUE; } /** @brief callback for resume-button press @param widget the button widget which triggered the callback @param resbutton pointer to paired button widget @return TRUE unless the process is not paused */ static gboolean _e2p_cpbar_resume_cb (GtkWidget *widget, E2_BarWindowData *wdata) { if (!(wdata->flags & E2_BARTASK_PAUSED)) return FALSE; gtk_widget_set_sensitive (widget, FALSE); gtk_widget_set_sensitive (wdata->pause_btn, TRUE); gtk_widget_grab_focus (wdata->pause_btn); wdata->flags &= ~E2_BARTASK_PAUSED; e2_filelist_disable_refresh (); gtk_main_quit (); return TRUE; } #else //ndef IS_PAUSABLE /** @brief callback for stop-button press and window-close event @param widget the activated widget which triggered the callback @param flag pointer to store for flag which will abort the process when set to TRUE; @return TRUE always */ static gboolean _e2p_cpbar_break_cb (GtkWidget *widget, gboolean *flag) { *flag = TRUE; return TRUE; } #endif /** @brief cpbar action This sets up progress display window, and processes each selected item @art ->data may contain pointerised flags indicating how the copy is to be done @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if action completed successfully, else FALSE */ static gboolean _e2p_cpbar (gpointer from, E2_ActionRuntime *art) { return (e2_task_enqueue_task (E2_TASK_COPY, art, from, _e2p_cpbarQ, e2_task_refresh_lists)); } static gboolean _e2p_cpbarQ (E2_ActionTaskData *qed) { //handle case of specific data instead of selection?? if (g_str_equal (qed->currdir, qed->othrdir)) { //display some message ?? return FALSE; } E2_ERR_DECLARE //FIXME change dir permission if possible ? #ifdef E2_VFS VPATH sdata; VPATH ddata = { qed->othrdir, qed->othrspace }; if (e2_fs_access (&ddata, W_OK E2_ERR_PTR())) #else if (e2_fs_access (qed->othrdir, W_OK E2_ERR_PTR())) #endif { e2_fs_error_local (_("Cannot put anything in %s"), #ifdef E2_VFS &ddata E2_ERR_MSGL()); #else qed->othrdir E2_ERR_MSGL()); #endif E2_ERR_CLEAR return FALSE; } GPtrArray *names = qed->names; GtkWidget *vbox; //, *hbox; GString *src = g_string_sized_new (1024); GString *dest = g_string_sized_new (1024); //setup the information window E2_BarWindowData windowdata; #ifdef IS_PAUSABLE windowdata.flags = 0; #else gboolean breakflag = FALSE; //this is for aborting the process #endif windowdata.dialog = e2_dialog_create (NULL, NULL, _("copying"), NULL, NULL); e2_dialog_setup (windowdata.dialog, app.main_window); //CHECKME permit Esc-key aborting ? #ifdef IS_PAUSABLE g_signal_connect (G_OBJECT (windowdata.dialog), "delete-event", G_CALLBACK (_e2p_cpbar_break_cb), &windowdata); #else g_signal_connect (G_OBJECT (windowdata.dialog), "delete-event", G_CALLBACK (_e2p_cpbar_break_cb), &breakflag); #endif gtk_dialog_set_has_separator (GTK_DIALOG (windowdata.dialog), FALSE); vbox = GTK_DIALOG (windowdata.dialog)->vbox; windowdata.label = e2_widget_add_mid_label (vbox, "", 0.0, FALSE, 0); // hbox = e2_widget_add_box (vbox, FALSE, 0, TRUE, FALSE, 0); // windowdata.label2 = e2_widget_add_mid_label (hbox, "", 0.5, TRUE, 0); windowdata.progbar = gtk_progress_bar_new (); gtk_box_pack_start (GTK_BOX (vbox), windowdata.progbar, TRUE, TRUE, E2_PADDING_LARGE); gtk_widget_show (windowdata.progbar); #ifdef IS_PAUSABLE //buttons in reverse order windowdata.resume_btn = e2_dialog_add_undefined_button_custom (windowdata.dialog, FALSE, E2_RESPONSE_USER1,_("_Resume"), GTK_STOCK_MEDIA_PLAY, _("Resume copying after pause"), _e2p_cpbar_resume_cb, &windowdata); //this one is disabled for now gtk_widget_set_sensitive (windowdata.resume_btn, FALSE); windowdata.pause_btn = e2_dialog_add_undefined_button_custom (windowdata.dialog, FALSE, E2_RESPONSE_USER2,_("_Pause"), GTK_STOCK_MEDIA_PAUSE, _("Suspend copying, after the current item"), _e2p_cpbar_pause_cb, &windowdata); windowdata.stop_btn = e2_dialog_add_undefined_button_custom (windowdata.dialog, TRUE, E2_RESPONSE_NOTOALL,_("_Stop"), GTK_STOCK_STOP, _("Abort the copying"), _e2p_cpbar_break_cb, &windowdata); #else e2_dialog_add_undefined_button_custom (windowdata.dialog, TRUE, E2_RESPONSE_NOTOALL,_("_Stop"), GTK_STOCK_STOP, _("Abort the copying"), _e2p_cpbar_break_cb, &breakflag); #endif #ifdef E2_VFS sdata.spacedata = qed->currspace; #endif //accumulate non-recursed count and total size of src item(s) E2_BarData totaldata = { 0,0 }; guint count; E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata; for (count=0; count < names->len; count++, iterator++) { g_string_printf (src, "%s%s", qed->currdir, (*iterator)->filename); //separator comes with dir #ifdef E2_VFS sdata.localpath = src->str; #endif //if (! #ifdef E2_VFS e2_fs_tw (&sdata, _e2p_cpbar_twcb, &totaldata, -1, #else e2_fs_tw (src->str, _e2p_cpbar_twcb, &totaldata, -1, #endif E2TW_PHYS E2_ERR_NONE()); //) //{ //FIXME handle error //} } totaldata.count = names->len; //not interested in nested count gboolean check = e2_option_bool_get ("confirm-overwrite") #ifdef E2_VFS && qed->currspace == qed->othrspace #endif ; E2_BarData progressdata = { 1,0 }; OW_ButtonFlags extras = (totaldata.count > 1) ? BOTHALL : NONE; iterator = (E2_SelectedItemInfo **) names->pdata; e2_filelist_disable_refresh (); for (count=0; count < names->len; count++, iterator++) { #ifdef IS_PAUSABLE if (windowdata.flags & E2_BARTASK_STOPPED) #else if (breakflag) #endif break; //user pressed stop btn or closed info window //src_dir, dest_dir have trailing "/" g_string_printf (src, "%s%s", qed->currdir, (*iterator)->filename); //separator comes with dir g_string_printf (dest, "%s%s", qed->othrdir, (*iterator)->filename); #ifdef E2_VFS sdata.localpath = src->str; ddata.localpath = dest->str; if (check && e2_fs_access2 (&ddata E2_ERR_NONE()) == 0) #else if (check && e2_fs_access2 (dest->str E2_ERR_NONE()) == 0) #endif { e2_filelist_enable_refresh (); //allow updates while we wait gdk_threads_enter (); *qed->status = E2_TASK_PAUSED; DialogButtons result = e2_dialog_ow_check (src->str, dest->str, extras); *qed->status = E2_TASK_RUNNING; gdk_threads_leave (); e2_filelist_disable_refresh (); switch (result) { case YES_TO_ALL: check = FALSE; case OK: #ifdef E2_VFS _e2p_cpbar_exec (&sdata, &ddata, #else _e2p_cpbar_exec (src->str, dest->str, #endif #ifndef IS_PAUSABLE &breakflag, #endif GPOINTER_TO_INT (qed->action->data), &progressdata, &totaldata, &windowdata); break; case CANCEL: break; default: result = NO_TO_ALL; break; } if (result == NO_TO_ALL) { break; } } else //no overwrite, or don't care { #ifdef E2_VFS _e2p_cpbar_exec (&sdata, &ddata, #else _e2p_cpbar_exec (src->str, dest->str, #endif #ifndef IS_PAUSABLE &breakflag, #endif GPOINTER_TO_INT (qed->action->data), &progressdata, &totaldata, &windowdata); } progressdata.count++; } gdk_threads_enter (); gtk_widget_destroy (windowdata.dialog); gdk_threads_leave (); g_string_free (src, TRUE); g_string_free (dest, TRUE); e2_filelist_request_refresh (other_view->dir, TRUE); //src pane refreshed normally e2_filelist_enable_refresh (); return TRUE; } //aname must be confined to this module static gchar *aname; static gchar *aname2; /** @brief initialize this plugin @param p pointer to plugin data struct to be populated @return TRUE if the intialization succeeded */ gboolean init_plugin (Plugin *p) { #define ANAME "cpbar" aname = _("cpbar"); aname2 = _("cpbar_with_time"); //these are data for the "parent" plugin item p->signature = ANAME VERSION; p->menu_name = _("_Copy"); //no tip will work for a submenu item, but we want to avoid silly default //string in config dialog until the plugin is actually loaded //this string will be replaced by "" when plugin is loaded ?? p->description = ""; p->icon = "plugin_copy_"E2IP".png"; //use icon file pathname if appropriate //child UI data gchar *sig1 = "0-"ANAME; gchar *label1 = (gchar *)p->menu_name; // gchar *icon1 = ""; //no icon "plugin_"ANAME"_"E2IP".png"; //use icon file pathname if appropriate gchar *tip1 = _("Copy selected item(s), with displayed progress details"); gchar *sig2 = "1-"ANAME; gchar *label2 = _("Copy with _times"); // gchar *icon2 = ""; gchar *tip2 = _("Copy selected item(s), with preserved time-properties and displayed progress details"); if (p->action == NULL) { gboolean retval = TRUE; Plugin *pc = e2_plugins_create_child (p); if (pc != NULL) { //for reconciling with config treestore data, signatures must reflect //0-based index of each child's order in the list of children pc->signature = sig1; //begin with "n-", n=0,1, ... //these will generallly be discarded in favour of config treestore data //meaning that any non-constant string will leak pc->menu_name = label1; //or whatever // pc->description = _("Copy selected item(s), with displayed progress details"); pc->description = tip1; //or whatever // pc->icon = "plugin_copy_"E2IP".png"; //use icon file pathname if appropriate //no need to free this gchar *action_name = g_strconcat (_A(5),".",aname,NULL); pc->action = e2_plugins_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2p_cpbar, NULL, FALSE, 0, NULL); } else retval = FALSE; //this will not be used, but MUST be set to allow checking whether to show //the item in the context menu if (retval) p->action = pc->action; pc = e2_plugins_create_child (p); if (pc != NULL) { pc->signature = sig2; //for reconciling with config treestore data pc->menu_name = label2; pc->description = tip2; // pc->icon = "plugin_copy_"E2IP".png"; //use icon file pathname if appropriate //no need to free this gchar *action_name = g_strconcat (_A(5),".",aname2,NULL); pc->action = e2_plugins_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2p_cpbar, GINT_TO_POINTER (E2_FTM_SAMETIME), FALSE, 0, NULL); } else retval = FALSE; if (retval && (p->action == NULL)) p->action = pc->action; return retval; } else //setup children UI data for pushing into a config dialog { E2_Sextet *uidata; uidata = e2_utils_sextet_new (); p->child_list = g_list_append (p->child_list, uidata); uidata->a = label1; uidata->b = ""; //no icon uidata->c = tip1; uidata->d = sig1; uidata = e2_utils_sextet_new (); p->child_list = g_list_append (p->child_list, uidata); uidata->a = label2; uidata->b = ""; uidata->c = tip2; uidata->d = sig2; } return FALSE; } /** @brief cleanup transient things for this plugin @param p pointer to data struct for the plugin @return TRUE if all cleanups were completed */ gboolean clean_plugin (Plugin *p) { gchar *action_name = g_strconcat (_A(5),".",aname,NULL); gboolean ret1 = e2_plugins_action_unregister (action_name); g_free (action_name); action_name = g_strconcat (_A(5),".",aname2,NULL); gboolean ret2 = e2_plugins_action_unregister (action_name); g_free (action_name); return (ret1 && ret2); } #undef IS_PAUSABLE emelfm2-0.4.1/plugins/e2p_rename.h0000600000175000017500000000646211010340377015662 0ustar cairocairo/* $Id: e2p_rename.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2005-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file plugins/e2p_rename_ext.h @brief header for file-rename plugin */ #ifndef __E2P_RENAMEXT_MAIN_H__ #define __E2P_RENAMEXT_MAIN_H__ #include "emelfm2.h" #include "e2_task.h" //up to E2_CHUNK_LIMIT-1 regex group-expressions processed in any pattern #define E2_CHUNK_LIMIT 16 typedef enum { SEARCH_ALL_P = 0, SEARCH_ALLACTIVE_P, SEARCH_ALLINACTIVE_P, //for variable namespaces SEARCH_CURRENT_P, SEARCH_OTHER_P, SEARCH_THIS_P, SEARCH_SUBDIRS_P, OLD_SEL_P, OLD_WILD_P, OLD_REGEX_P, NEW_UPPER_P, NEW_LOWER_P, NEW_THIS_P, CONFIRM_P, MAX_FLAGS //no. of entries in the array } renflag_t ; typedef enum { E2PR_NORMAL = 0, E2PR_SEL = 1, E2PR_WILD = 1 << 1, E2PR_REGEX = 1 << 2, E2PR_RECURSE = 1 << 3, E2PR_LOWER = 1 << 4,//for case conversions E2PR_UPPER = 1 << 5, E2PR_PATTERN = 1 << 6,//some replacement pattern provided E2PR_NEWALL = 1 << 7,//replacememt has nothing wild in it E2PR_WHOLE = 1 << 8,//replacement with one or more "\0" E2PR_COUNTER = 1 << 9 //replacement with one or more %c[n[,m] } E2P_RenFlags; typedef struct _E2P_RenameData { E2P_RenFlags flags; const gchar *pattern; //allocated string with entered 'old' name string regex_t *compiled; //pointer to compiled regex pattern for 'old' name GPtrArray *candidates; //array of utf8 absolute path strings, each member is a match } E2P_RenameData; typedef struct _E2_RenDialogRuntime { GtkWidget *dialog; GtkWidget *directory; // entry with text for directory to search in/from GtkWidget *pattern; // entry with text for file name to search for GtkWidget *newpattern; // entry with text for file name to substitute GtkWidget *stop_button; // button widget, remembered for changing sensitivity GtkWidget *start_button; // ditto GtkWidget *help_button; // ditto GtkWidget *active_button; GtkWidget *recurse_button; GtkWidget *wild_button; GSList *groups; //list of lists of grouped toggle btns // gint find_pid; // gboolean extended_rgx; //flag for whether 'find' command can use extended regex E2P_RenFlags modeflags; gboolean parsed; //flag for whether the replacemment pattern has been converted to chunks gboolean abort; //flag for stop btn pressed when querying a rename E2_TaskStatus *status; //store for changing queued task status // flags moved to static // gboolean flags[MAX_FLAGS]; //cache for toggle values gchar *chunks[E2_CHUNK_LIMIT]; //parts of old name pattern which are needed in the new name //when replacement has nothing wild, chunks[1] has the whole replacement } E2_RenDialogRuntime; #endif //ndef __E2P_RENAMEXT_MAIN_H__ emelfm2-0.4.1/plugins/e2p_glob.c0000600000175000017500000004273410751445056015346 0ustar cairocairo/* $Id: e2p_glob.c 799 2008-02-03 23:11:42Z tpgww $ Copyright (C) 2003-2008 tooar Portions copyright (C) 1999 Michael Clark This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file plugins/e2p_glob.c @brief plugin for selecting items by various user-specified rules */ #include "emelfm2.h" #include #include #include #include "e2_plugins.h" #include "e2_dialog.h" #include "e2_filelist.h" //various dialog widgets ... typedef struct _E2_FltDlgRuntime { GtkWidget *usename_check; GtkWidget *pattern_entry; GtkWidget *case_sensitive_check; GtkWidget *invert_check; GtkWidget *usesize_check; GtkWidget *sizop_combo; GtkWidget *size_entry; GtkWidget *units_combo; GtkWidget *usedate_check; GtkWidget *datop_combo; GtkWidget *date_entry; } E2_FltDlgRuntime; //various parameters from dialog widgets ... typedef struct _E2_FltDlgData { struct { gboolean use; //TRUE when name filtering is invoked const gchar *pattern; gboolean case_sensitive; gboolean invert_mask; } name_filter; struct { gboolean use; Operator op; size_t size; } size_filter; struct { gboolean use; gint time_type; //one of MTIME, ATIME, CTIME Operator op; time_t time; //time_t may be int or long int } date_filter; } E2_FltDlgData; //the order here needs to be consistent with the timetypes enum and operator enum //in e2_fileview.h, and matched by the combo entries //enum/2 = MTIME, ATIME, CTIME and enum%2 = GT, LT enum { MODIFIED_SINCE, MODIFIED_BEFORE, ACCESSED_SINCE, ACCESSED_BEFORE, CHANGED_SINCE, CHANGED_BEFORE, MAXDATECHOICES }; static gint date_index; static gchar *previous_pattern; //these need to be in same order as the date options in the config dialog static gchar *date_format [5] = { "%d/%m/%Y", //default "%d/%m/%Y", //standard "%m/%d/%Y", //american "%Y-%m-%d", //ISO "%x" //localised }; //these functions are essentially the same as the filtering functions in e2_filelist.c /** @brief decide whether an item should be selected, on the basis of its name Item name string is localised @param info data structure for item being checked @param data data structure for view being processed @return TRUE if item 'passes' (i.e. to be selected) */ static gboolean _e2p_glob_match_name (FileInfo *info, E2_FltDlgData *data) { gchar *s, *p, *utf, *freeme; gchar save; gboolean negated, matched, positive_check = FALSE, result = FALSE; p = (gchar *) data->name_filter.pattern; utf = F_FILENAME_FROM_LOCALE (info->filename); //maybe several patterns, separated by commas while ((s = strchr (p, ',')) != NULL) //if always ascii ',', don't need g_utf8_strchr() { //check each pattern that is followed by a ',' save = *s; *s = '\0'; while (p[0] == ' ') p++; if (p[0] == '!') { negated = !data->name_filter.invert_mask; p++; } else { negated = data->name_filter.invert_mask; if (p[0] == '\\' && p[1] == '!') p++; } if (!positive_check) positive_check = !negated; if (!data->name_filter.case_sensitive) { freeme = g_utf8_strdown (utf, -1); matched = g_pattern_match_simple (p, freeme); g_free (freeme); } else matched = g_pattern_match_simple (p, utf); *s = save; if (matched && negated) { F_FREE (utf); return FALSE; } if (matched && !negated) result = TRUE; //but keep looking for any later exclude //if neither negated nor matched, we don't change result NCHR(s); //in case the matched char is bigger than 1 byte p = s; } //check the last (or only) pattern while (p[0] == ' ') p++; if (p[0] == '\0') return result; if (p[0] == '!') { negated = !data->name_filter.invert_mask; p++; } else { negated = data->name_filter.invert_mask; if (p[0] == '\\' && p[1] == '!') p++; } if (!positive_check) positive_check = !negated; if (!data->name_filter.case_sensitive) { freeme = g_utf8_strdown (utf, -1); matched = g_pattern_match_simple (p, freeme); g_free (freeme); } else matched = g_pattern_match_simple (p, utf); if (matched) result = !negated; //extra check for unmatched final check else if (negated && !positive_check) result = TRUE; F_FREE (utf); return result; } /** @brief decide whether an item should be selected, on the basis of its size Item statbuf.st_size is compared with curent filters @param info data structure for item being checked @param data data structure for view being processed @return TRUE if item 'passes' (i.e. to be selected) */ static gboolean _e2p_glob_match_size (FileInfo *info, E2_FltDlgData *data) { switch (data->size_filter.op) { case GT: return (info->statbuf.st_size > data->size_filter.size); break; case LT: return (info->statbuf.st_size < data->size_filter.size); break; case EQ: return (info->statbuf.st_size == data->size_filter.size); break; default: return TRUE; break; } } /** @brief decide whether an item should be selected, on the basis of one of its times Item statbuf.st_atime is compared with curent filters @param info data structure for item being checked @param data data structure for view being processed @return TRUE if item 'passes' (i.e. to be selected) */ static gboolean _e2p_glob_match_date (FileInfo *info, E2_FltDlgData *data) { switch (data->date_filter.time_type) { case MTIME: if (data->date_filter.op == GT) return (difftime(data->date_filter.time, info->statbuf.st_mtime) <= 0); else return (difftime(data->date_filter.time, info->statbuf.st_mtime) > 0); break; case ATIME: if (data->date_filter.op == GT) return (difftime(data->date_filter.time, info->statbuf.st_atime) <= 0); else return (difftime(data->date_filter.time, info->statbuf.st_atime) > 0); break; case CTIME: if (data->date_filter.op == GT) return (difftime(data->date_filter.time, info->statbuf.st_ctime) <= 0); else return (difftime(data->date_filter.time, info->statbuf.st_ctime) > 0); break; default: return TRUE; break; } } /** @brief callback for all glob-dialog responses Only an OK response does anything. This now works by borrowing some data slots from the *curr_view data struct and then using the same filtering process as applies to name filtering for filelists. Yeah, a hack, but that's been debugged ... @param dialog the dialog where the response was initiated, UNUSED @param response the number assigned to the widget which triggered the response @param data pointer to dialog data struct @return */ static void _e2p_glob_response_cb (GtkDialog *dialog, gint response, E2_FltDlgRuntime *data) { switch (response) { case GTK_RESPONSE_OK: { const gchar *s; //#warning ignore compiler warnings about unitialized usage of choices.* E2_FltDlgData choices; //assignment for complier-warning prevention only memset (&choices, '\0', sizeof (E2_FltDlgData)); //get all the relevant parameters from the dialog choices.name_filter.use = GTK_TOGGLE_BUTTON (data->usename_check)->active; if (choices.name_filter.use) { //filter pattern is utf s = gtk_entry_get_text (GTK_ENTRY (data->pattern_entry)); if (*s == 0) { choices.name_filter.use = FALSE; e2_output_print_error (_("Invalid filename pattern"), FALSE); } else { if (previous_pattern != NULL) g_free (previous_pattern); previous_pattern = g_strdup (s); choices.name_filter.pattern = s; choices.name_filter.invert_mask = GTK_TOGGLE_BUTTON (data->invert_check)->active; choices.name_filter.case_sensitive = GTK_TOGGLE_BUTTON (data->case_sensitive_check)->active; } } choices.size_filter.use = GTK_TOGGLE_BUTTON (data->usesize_check)->active; if (choices.size_filter.use) { s = gtk_entry_get_text (GTK_ENTRY (data->size_entry)); gdouble dsize = atof (s); gint index = gtk_combo_box_get_active (GTK_COMBO_BOX (data->units_combo)); if (index == 1 || index == 2) dsize *= pow (1024, index); choices.size_filter.size = (size_t) dsize; choices.size_filter.op = gtk_combo_box_get_active (GTK_COMBO_BOX (data->sizop_combo)); //0=1st entry index, = } choices.date_filter.use = GTK_TOGGLE_BUTTON (data->usedate_check)->active; if (choices.date_filter.use) { struct tm tm_time; gchar *strf_withtime = g_strconcat (date_format [date_index] , " %T", NULL); gchar *date_string = g_strdup_printf ("%s 00:00:00", gtk_entry_get_text (GTK_ENTRY (data->date_entry))); strptime (date_string, strf_withtime, &tm_time); g_free (strf_withtime); g_free (date_string); choices.date_filter.time = (time_t) mktime (&tm_time); gint index = gtk_combo_box_get_active (GTK_COMBO_BOX (data->datop_combo)); //these are tricks consequent on the order of combo entries choices.date_filter.time_type = index / 2; choices.date_filter.op = index % 2; } if (choices.name_filter.use || choices.size_filter.use || choices.date_filter.use) { GtkTreeIter iter; GtkTreeModel *model = curr_view->model; if (gtk_tree_model_get_iter_first (model, &iter)); { //it's not empty FileInfo *info; e2_filelist_disable_refresh (); e2_window_set_cursor (GDK_WATCH); //wait until any current recreation is finished while (TRUE) { LISTS_LOCK gboolean busy = curr_view->listcontrols.refresh_working || curr_view->listcontrols.cd_working; LISTS_UNLOCK if (!busy) break; usleep (100000); } GtkTreeSelection *sel = curr_view->selection; gtk_tree_selection_unselect_all (sel); //start with clean slate do { gtk_tree_model_get (model, &iter, FINFO, &info, -1); //note that the pattern string may be altered, but is supposed to be constant ? gboolean selectme = FALSE; if (choices.name_filter.use) selectme = selectme || _e2p_glob_match_name (info, &choices); if (choices.size_filter.use) selectme = selectme || _e2p_glob_match_size (info, &choices); if (choices.date_filter.use) selectme = selectme || _e2p_glob_match_date (info, &choices); if (selectme) gtk_tree_selection_select_iter (sel, &iter); } while (gtk_tree_model_iter_next (model, &iter)); e2_window_set_cursor (GDK_LEFT_PTR); e2_filelist_enable_refresh (); } } } break; default: break; } gtk_widget_destroy (GTK_WIDGET (dialog)); gtk_main_quit (); return; } /** @brief create and run selection-filter dialog @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2p_glob (gpointer from, E2_ActionRuntime *art) { GtkWidget *dialog; GtkWidget *vbox, *hbox, *table; E2_FltDlgRuntime data; gint index; gchar *utf; dialog = e2_dialog_create (NULL, _("Select items:"), _("selection filter"), _e2p_glob_response_cb, &data); vbox = GTK_DIALOG (dialog)->vbox; //the name-related things ... hbox = e2_widget_add_box (vbox, TRUE, 0, FALSE, FALSE, 0); data.usename_check = e2_button_add_toggle (hbox, TRUE, FALSE, NULL, NULL, FALSE, 0, NULL, &data); e2_widget_add_mid_label (hbox, _("Named like"), 0.0, FALSE, 0); data.pattern_entry = e2_widget_add_entry (hbox, "", TRUE, FALSE); //#ifdef E2_ASSISTED // e2_widget_set_label_relations (GTK_LABEL (label), data.pattern_entry); //#endif FileInfo *info = e2_fileview_get_selected_first_local (curr_view, FALSE); if (info != NULL) { gchar *text, *s; utf = F_FILENAME_FROM_LOCALE (info->filename); if ((s = strrchr (utf, '.')) != NULL && (s > utf)) //not for hidden items text = g_strconcat ("*",s, NULL); else text = utf; gtk_entry_set_text (GTK_ENTRY(data.pattern_entry), text); if (text != utf) g_free (text); F_FREE (utf); } else if (previous_pattern != NULL) gtk_entry_set_text (GTK_ENTRY(data.pattern_entry), previous_pattern); e2_widget_add_mid_label (hbox, _("example: *.c,*.h"), 0.0, FALSE, 0); table = e2_widget_add_table (vbox, 1, 3, TRUE, TRUE, 0); data.invert_check = e2_button_add_toggle_to_table (table, _("_Invert"), FALSE, NULL, NULL, 1, 2, 0, 1); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif data.invert_check, _("Select items that DO NOT match the given mask")); data.case_sensitive_check = e2_button_add_toggle_to_table (table, _("Case _sensitive"), TRUE, NULL, NULL, 2, 3, 0, 1); e2_widget_add_separator (vbox, TRUE, 1); //the size-related things ... hbox = e2_widget_add_box (vbox, TRUE, 0, FALSE, FALSE, 0); data.usesize_check = e2_button_add_toggle (hbox, TRUE, FALSE, NULL, NULL, FALSE, 0, NULL, &data); data.sizop_combo = e2_combobox_add (hbox, FALSE, 0, NULL, NULL, NULL, E2_COMBOBOX_MENU_STYLE); //don't change the order of these - the index is used as an enumerator gchar *size_choices[3] = { _("smaller than"), _("equal to"), _("bigger than") }; e2_combobox_append_history_counted (data.sizop_combo, 3, size_choices); gtk_combo_box_set_active (GTK_COMBO_BOX (data.sizop_combo), 0); //0=1st entry index gchar size_string[32]; if (info != NULL) { if (info->statbuf.st_size < (1 << 10)) { g_snprintf (size_string, sizeof (size_string), "%ld", (gulong) info->statbuf.st_size); index = 0; //0=1st combo entry index, bytes } else if (info->statbuf.st_size < (1 << 20)) { g_snprintf (size_string, sizeof (size_string), "%.2f", (gdouble)((gdouble)info->statbuf.st_size / (gdouble)(1 << 10))); index = 1; } else { g_snprintf (size_string, sizeof (size_string), "%.2f", (gdouble)((gdouble)info->statbuf.st_size / (gdouble)(1 << 20))); index = 2; } } else { size_string[0] = '\0'; index = 0; } data.size_entry = e2_widget_add_entry (hbox, size_string, TRUE, FALSE); // gtk_widget_set_size_request (data.size_entry, 100, 30); //#ifdef E2_ASSISTED // e2_widget_set_label_relations (GTK_LABEL (label), data.size_entry); //#endif data.units_combo = e2_combobox_add (hbox, FALSE, 0, NULL, NULL, NULL, E2_COMBOBOX_MENU_STYLE); gchar *size_names[3] = { _("bytes"), _("kbytes"), _("Mbytes") }; e2_combobox_append_history_counted (data.units_combo, 3, size_names); gtk_combo_box_set_active (GTK_COMBO_BOX (data.units_combo), index); e2_widget_add_separator (vbox, TRUE, 1); //the date-related things ... hbox = e2_widget_add_box (vbox, TRUE, 0, FALSE, FALSE, 0); data.usedate_check = e2_button_add_toggle (hbox, TRUE, FALSE, NULL, NULL, FALSE, 0, NULL, &data); data.datop_combo = e2_combobox_add (hbox, FALSE, 0, NULL, NULL, NULL, E2_COMBOBOX_MENU_STYLE); // don't change the order of these - index value is used in callback gchar *date_choices [MAXDATECHOICES] = { _("modified since"), _("modified before"), _("accessed since"), _("accessed before"), _("changed since"), _("changed before") }; e2_combobox_append_history_counted (data.datop_combo, MAXDATECHOICES, date_choices); gtk_combo_box_set_active (GTK_COMBO_BOX (data.datop_combo), MODIFIED_SINCE); gchar date_string[16]; if (info != NULL) { //get & set which date format to use date_index = e2_option_int_get ("date-string"); if (date_index > 4) date_index = 0; //out of range, use default format (should never happen) struct tm *tm_ptr = localtime (&(info->statbuf.st_mtime)); strftime (date_string, sizeof (date_string), date_format[date_index], tm_ptr); utf = e2_utf8_from_locale (date_string); } else utf = ""; data.date_entry = e2_widget_add_entry (hbox, utf, TRUE, FALSE); if (info != NULL) g_free (utf); // gtk_widget_set_size_request (data.date_entry, 120, 30); //#ifdef E2_ASSISTED // e2_widget_set_label_relations (GTK_LABEL (label), data.date_entry); //#endif //start at the name entry gtk_editable_select_region (GTK_EDITABLE(data.pattern_entry), 0, -1); gtk_widget_grab_focus (data.pattern_entry); // e2_dialog_resize (dialog, 1.3); //really, we only want to make it wider .. //non-modal dialog e2_dialog_show (dialog, app.main_window, 0, &E2_BUTTON_CANCEL, &E2_BUTTON_OK, NULL); gtk_main (); return TRUE; } //aname must be confined to this module static gchar *aname; /** @brief plugin initialization function, called by main program @param p ptr to plugin data struct @return TRUE if the initialization succeeds, else FALSE */ gboolean init_plugin (Plugin *p) { #define ANAME "glob" aname = _("glob"); p->signature = ANAME VERSION; p->menu_name = _("_Glob.."); p->description = _("Select items matching a specified pattern"); p->icon = "plugin_"ANAME"_"E2IP".png"; //use icon file pathname if appropriate if (p->action == NULL) { //no need to free this gchar *action_name = g_strconcat (_A(10),".",aname,NULL); p->action = e2_plugins_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2p_glob, NULL, FALSE, 0, NULL); return TRUE; } return FALSE; } /** @brief cleanup transient things for this plugin @param p pointer to data struct for the plugin @return TRUE if all cleanups were completed */ gboolean clean_plugin (Plugin *p) { gchar *action_name = g_strconcat (_A(10),".",aname,NULL); gboolean ret = e2_plugins_action_unregister (action_name); g_free (action_name); if (previous_pattern != NULL) g_free (previous_pattern); return ret; } emelfm2-0.4.1/plugins/e2p_config.c0000600000175000017500000012346310714030042015647 0ustar cairocairo/* $Id: e2p_config.c 706 2007-11-06 09:13:06Z tpgww $ Copyright (C) 2006-2007 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file plugins/e2p_config.c @brief emelfm2 config-data export/import plugin */ #include "emelfm2.h" #include #include "e2_plugins.h" #include "e2_dialog.h" #include "e2_task.h" #include "e2_filetype.h" #define ANAME "config" #ifndef UPGRADE_PNAME #define UPGRADE_PNAME "e2p_upgrade.so" #endif //NOTE this needs to be the minimum actionable version in the //upgrade plugin or else that will just install defaults #ifndef OLDEST_UPGRADE #define OLDEST_UPGRADE "0.2.0" #endif typedef struct _E2P_ConfigData { GtkWidget *dialog; GtkWidget *save_entry; GtkWidget *open_entry; GtkWidget *expander; GtkWidget *icondir_entry; GtkWidget *iconsavedir_entry; } E2P_ConfigData; typedef enum { ALL_P = 0, NONTREE_P, ALLTREE_P, CUSTOM_P, MARKS_P, FILETYPES_P, BINDINGS_P, ALIASES_P, PLUGINS_P, MENU_P, CUSTMENU_P, PANEBAR1_P, PANEBAR2_P, TASKBAR_P, CMDBAR_P, MAX_FLAGS //no. of entries in the array } flag_t; typedef enum { E2PC_ALL = 1 << ALL_P, E2PC_NONTREE = 1 << NONTREE_P, E2PC_ALLTREE = 1 << ALLTREE_P, E2PC_CUSTOM = 1 << CUSTOM_P, E2PC_MARKS = 1 << MARKS_P, E2PC_FILETYPES = 1 << FILETYPES_P, E2PC_BINDINGS = 1 << BINDINGS_P, E2PC_ALIASES = 1 << ALIASES_P, E2PC_PLUGINS = 1 << PLUGINS_P, E2PC_MENU = 1 << MENU_P, E2PC_CUSTMENU = 1 << CUSTMENU_P, E2PC_PANEBAR1 = 1 << PANEBAR1_P, E2PC_PANEBAR2 = 1 << PANEBAR2_P, E2PC_TASKBAR = 1 << TASKBAR_P, E2PC_CMDBAR = 1 << CMDBAR_P, } bitflag_t; #define E2PC_ALLTREEMASK E2PC_FILETYPES | E2PC_BINDINGS | E2PC_ALIASES \ | E2PC_PLUGINS | E2PC_MENU | E2PC_CUSTMENU | E2PC_PANEBAR1 \ | E2PC_PANEBAR2 | E2PC_TASKBAR | E2PC_CMDBAR //for determining index of some specific tree #define FIRSTTREE_P MARKS_P //these treeset "internal" names are in same order as the flag_t enumerator static gchar *set_private_names [] = { "bookmarks", "filetypes", "keybindings", "command-aliases", "plugins", "context-menu", "custom-menus", "panebar1", "panebar2", "taskbar", "commandbar", }; //cache for toggle values, static for the session static gboolean flags[MAX_FLAGS]; GPtrArray *treeset_names = NULL; //names to use when checking importable tree options gboolean rebuild_needed; E2P_ConfigData *srt; //copy of runtime ptr for some funcs that don't get rt directly /*********************/ /***** utilities *****/ /*********************/ /** @brief get mnemonic'ed label for set corresponding to @a f @param f enumerated value of option to be processed @return newly-allocated utf8 label */ static gchar *_e2pc_get_setlabel (flag_t f) { //no label-mnemonic that competes with the close button //nicer to do this lookup just once ! gunichar close_mnemonic[2] = {0}; close_mnemonic[0] = e2_utils_get_mnemonic_char (E2_BUTTON_CLOSE.label); gchar *private_name = set_private_names [f - FIRSTTREE_P]; E2_OptionSet *set = e2_option_get (private_name); //quick, flawed approach to underscoring for keyboard speedup gchar *label; if (close_mnemonic[0] != (gunichar)'\0' && g_str_has_prefix (set->desc, (gchar *) close_mnemonic)) label = g_strdup (set->desc); else label = g_strconcat ("_", set->desc, NULL); return label; } /** @brief set specified flag to T/F The relevant array value is set @param f enumerated value of flag to be set @param value new value for the flag @return */ static void _e2pc_set_flag (flag_t f, gboolean value) { if (f < MAX_FLAGS) flags[ (gint) f] = value; } /** @brief return the value of a specified flag @param f enumerated value of flag to be interrogated @param rt UNUSED pointer to dialog data struct @return flag value, T/F, or FALSE if the value is not recognised */ static gboolean _e2pc_get_flag (flag_t f) { if (f < MAX_FLAGS) return (flags[(gint) f]); else return (FALSE); } /** @brief toggle specified option flag @param widget clicked button, UNUSED @param flagnum pointerized number of the flag to be toggled @return */ static void _e2pc_toggle_cb (GtkWidget *widget, gpointer flagnum) { flag_t flg = (flag_t) flagnum; gboolean newflag = ! _e2pc_get_flag (flg); _e2pc_set_flag (flg, newflag); //handle here all the 'special cases', if any /* if (newflag) { switch (GPOINTER_TO_INT(flagnum)) { default: break; } } */ if (flg == CUSTOM_P) gtk_expander_set_expanded (GTK_EXPANDER (srt->expander), newflag); } /** @brief create and show a button in a specified container @param container the widget into which the button is to be placed @param f enumerated value of flag to be associated with the button @param state T/F initial state of the toggle @param label translated string for the button label @param callback callback function to be connected @return the button widget */ /*static GtkWidget *_e2pc_create_option_button (GtkWidget *container, flag_t f, gboolean state, gchar *label, gpointer callback_fn) { GtkWidget *btn = gtk_check_button_new_with_mnemonic (label); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (btn), state); g_signal_connect (G_OBJECT (btn), "toggled", G_CALLBACK (callback_fn), (gpointer) f); //CHECKME = correct value? gtk_container_add (GTK_CONTAINER (container), btn); gtk_widget_show (btn); return (btn); } */ /** @brief create and show a check button in a specified container @param container the widget into which the button is to be placed @param f enumerated value of flag to be associated with the button @param state T/F initial state of the toggle @param label translated string for the button label @return the button widget (UNUSED, now) */ static GtkWidget *_e2pc_create_check_button (GtkWidget *container, flag_t f, gboolean state, gchar *label) { // GtkWidget *btn = _e2pc_create_option_button // (container, f, state, label, _e2pc_toggle_cb); GtkWidget *btn = gtk_check_button_new_with_mnemonic (label); g_signal_connect (G_OBJECT (btn), "toggled", G_CALLBACK (_e2pc_toggle_cb), (gpointer) f); //CHECKME = correct value? gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (btn), state); gtk_container_add (GTK_CONTAINER (container), btn); gtk_widget_show (btn); return (btn); } /** @brief create and show a radio btn in a specified container The leader of a group is initialized to TRUE, other group members may cause that to be changed @param container the widget into which the button is to be placed @param f enumerated value of flag to be associated with the button @param label translated string for the button label @param rt @return the button widget */ static GtkWidget *_e2pc_create_radio_button (GtkWidget *container, flag_t f, gchar *label) { _e2pc_set_flag (f, TRUE); //group leader needs to be TRUE at start, may be toggled by other members GtkWidget *btn = gtk_radio_button_new_with_mnemonic (NULL, label); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (btn), TRUE); g_signal_connect (G_OBJECT (btn), "toggled", G_CALLBACK (_e2pc_toggle_cb), (gpointer) f); gtk_container_add (GTK_CONTAINER (container), btn); gtk_widget_show (btn); return (btn); } /** @brief create and show a radio btn in a specified container @param container the widget into which the button is to be placed @param group the radio button widget that 'leads' the group @param f enumerated value of flag to be associated with the button @param state T/F initial state of the toggle @param label translated string for the button label @param rt @return the button widget */ static GtkWidget *_e2pc_create_radio_grouped_button (GtkWidget *container, GtkWidget *group, flag_t f, gboolean state, gchar *label) { GSList *list = gtk_radio_button_get_group (GTK_RADIO_BUTTON (group)); GtkWidget *btn = gtk_radio_button_new_with_mnemonic (list, label); g_signal_connect (G_OBJECT (btn), "toggled", G_CALLBACK (_e2pc_toggle_cb), (gpointer) f); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (btn), state); gtk_container_add (GTK_CONTAINER (container), btn); gtk_widget_show (btn); return (btn); } /** @brief check whether a tree option is to be imported @param setname internal name of set read from config file @return TRUE if this set is one we want */ static gboolean _e2pc_match_tree (gchar *setname) { guint i; gchar **iterator; for (i = 0, iterator = (gchar **)treeset_names->pdata; i < treeset_names->len; i++, iterator++) { if (g_str_equal (*iterator, setname)) { g_ptr_array_remove_index_fast (treeset_names, i); return TRUE; } } return FALSE; } /** @brief apply requested config data No backup of existing data, so mid-read failure might be ornery! @param contents ptr to config file after loading into memory @param flags bitflags indicating which options to import @return */ static void _e2pc_filter_options (gchar *contents, bitflag_t flags) { gint i = -1; //array index gchar *line; //pointer to the current line gchar **lsplit; //everything goes into an array //as for e2_option_read_array(), contents are assumed to be parsable as ascii gchar **split = g_strsplit (contents, "\n", -1); while ((line = split[++i]) != NULL) { g_strchomp (line); //ignore empty lines and comments if (*line == '\0' || line[0] == '#') continue; lsplit = g_strsplit (line, "=", 2); if (lsplit[1] != NULL) { if (!g_str_equal (lsplit[1], "<")) //not a tree set { if (flags & (E2PC_NONTREE | E2PC_ALL)) { if (e2_option_set_from_string (lsplit[0], lsplit[1])) rebuild_needed = TRUE; else printd (WARN, "could not set option '%s'", lsplit[0]); } } else //a tree set { if ((flags & (E2PC_ALLTREE | E2PC_ALL)) || _e2pc_match_tree (lsplit[0])) { E2_OptionSet *set = e2_option_tree_get (lsplit[0]); if (set != NULL) { e2_option_tree_backup (set); gtk_tree_store_clear (GTK_TREE_STORE (set->ex.tree.model)); if (e2_option_tree_set_from_array (lsplit[0], split, &i, NULL)) { rebuild_needed = TRUE; e2_option_tree_unbackup (set, FALSE); } else { //reinstate backup e2_option_tree_unbackup (set, TRUE); set = NULL; //trigger message } } if (set == NULL) { gchar *msg = g_strdup_printf ( _("Bad configuration data for %s, not installed"), lsplit[0]); e2_output_print_error (msg, TRUE); } } else //skip the set while ((line = split[++i]) != NULL) { g_strchomp (line); if (line[0] == '>') break; } } } g_strfreev (lsplit); } g_strfreev (split); } /*********************/ /***** callbacks *****/ /*********************/ /** @brief get config data and apply them @param button UNUSED the clicked widget @param rt dialog runtime struct @return */ static void _e2pc_import_cb (GtkWidget *button, E2P_ConfigData *rt) { //convert import flags to bitflags that we can play with bitflag_t import_flags = 0; gint i; for (i = 0; i < MAX_FLAGS; i++) { if (flags[i]) import_flags |= 1 << i; } import_flags &= ~E2PC_CUSTOM; //this one is just an indicator for specific tree(s) if (!import_flags) return; //nothing chosen //don't want to separately test the "all" flags if (import_flags & E2PC_ALL) import_flags |= E2PC_NONTREE; if (import_flags & (E2PC_ALL | E2PC_ALLTREE)) import_flags |= E2PC_ALLTREEMASK; //setup tree-option private (untranslated) names, for comparison when parsing treeset_names = g_ptr_array_sized_new (MAX_FLAGS); //plenty of space, even with trailing NULL //FIXME make this dynamic if (import_flags & E2PC_MARKS) g_ptr_array_add (treeset_names, (gpointer) set_private_names[MARKS_P - FIRSTTREE_P]); if (import_flags & E2PC_FILETYPES) g_ptr_array_add (treeset_names, (gpointer) set_private_names[FILETYPES_P - FIRSTTREE_P]); if (import_flags & E2PC_BINDINGS) g_ptr_array_add (treeset_names, (gpointer) set_private_names[BINDINGS_P - FIRSTTREE_P]); if (import_flags & E2PC_ALIASES) g_ptr_array_add (treeset_names, (gpointer) set_private_names[ALIASES_P - FIRSTTREE_P]); if (import_flags & E2PC_PLUGINS) g_ptr_array_add (treeset_names, (gpointer) set_private_names[PLUGINS_P - FIRSTTREE_P]); if (import_flags & E2PC_MENU) g_ptr_array_add (treeset_names, (gpointer) set_private_names[MENU_P - FIRSTTREE_P]); if (import_flags & E2PC_CUSTMENU) g_ptr_array_add (treeset_names, (gpointer) set_private_names[CUSTMENU_P - FIRSTTREE_P]); if (import_flags & E2PC_PANEBAR1) g_ptr_array_add (treeset_names, (gpointer) set_private_names[PANEBAR1_P - FIRSTTREE_P]); if (import_flags & E2PC_PANEBAR2) g_ptr_array_add (treeset_names, (gpointer) set_private_names[PANEBAR2_P - FIRSTTREE_P]); if (import_flags & E2PC_TASKBAR) g_ptr_array_add (treeset_names, (gpointer) set_private_names[TASKBAR_P - FIRSTTREE_P]); if (import_flags & E2PC_CMDBAR) g_ptr_array_add (treeset_names, (gpointer) set_private_names[CMDBAR_P - FIRSTTREE_P]); gboolean upgrade = FALSE; //TRUE when importing an old config file that probably needs upgrade const gchar *filepath = gtk_entry_get_text (GTK_ENTRY (rt->open_entry)); gchar *realpath = (*filepath != '\0') ? (gchar *) filepath : g_build_filename (e2_cl_options.config_dir, default_config_file, NULL); gchar *localpath = F_FILENAME_TO_LOCALE (realpath); #ifdef E2_VFS VPATH ddata = { localpath, NULL }; //only allow local config data #endif gpointer contents; //get file read_file: #ifdef E2_VFS if (e2_fs_get_file_contents (&ddata, &contents, NULL, TRUE E2_ERR_NONE())) #else if (e2_fs_get_file_contents (localpath, &contents, NULL, TRUE E2_ERR_NONE())) #endif { //check for conforming version, hence format for tree options gchar *sp, *sq, *st; sp = strchr ((gchar *)contents, '\n'); if (sp != NULL) { *sp = '\0'; sq = strstr ((gchar *)contents, "(v"); if (sq != NULL) { st = g_strrstr (sq, ")"); if (st != NULL) { *st = '\0'; sq = g_strdup (sq + 2); g_strstrip (sq); upgrade = (strcmp (sq, VERSION RELEASE) < 0); if (upgrade) { gchar *command; gchar *sed = g_find_program_in_path ("sed"); if (sed != NULL) { if (strcmp (sq,"0.1.6.3") < 0 && (import_flags & (E2PC_ALLTREEMASK))) { //re-configure all tree-option name lines //this format change is no longer supported in the upgrade plugin command = g_strconcat ( "cp -f ", realpath, " ", realpath, ".save;", sed, " -e 's/^<\\(.*\\)/\\1=", realpath, NULL); system (command); g_free (command); g_free (sed); g_free (sq); if (realpath != filepath) g_free (realpath); g_free (contents); //need free() if file buffer allocated by malloc() goto read_file; } } else { gchar *msg = g_strdup_printf (_("Incompatible format - %s"), realpath); e2_output_print_error (msg, TRUE); g_free (sq); if (realpath != filepath) g_free (realpath); g_free (contents); //need free() if file buffer allocated by malloc() return; } } *st = ')'; } } *sp = '\n'; } else sq = NULL; rebuild_needed = FALSE; //process the file _e2pc_filter_options ((gchar *)contents, import_flags); g_free (contents); //need free() if file buffer allocated by malloc() if (rebuild_needed) { if (upgrade) { //backup current config file in case the upgrade is not so good gchar *path1 = g_build_filename (e2_cl_options.config_dir, default_config_file, NULL); gchar *local1 = F_FILENAME_TO_LOCALE (path1); gchar *path2 = g_strconcat (default_config_file, "-before-import", NULL); gchar *path3 = g_build_filename (e2_cl_options.config_dir, path2, NULL); gchar *local2 = F_FILENAME_TO_LOCALE (path3); gchar *savepath = e2_utils_get_tempname (local2); gdk_threads_leave (); //downstream errors invoke local mutex locking #ifdef E2_VFS VPATH ddata = { local1, NULL }; //local config files only VPATH tdata = { savepath, NULL }; //local config files only e2_task_backend_rename (&ddata, &tdata); #else e2_task_backend_rename (local1, savepath); #endif gdk_threads_enter (); g_free (path1); g_free (path2); g_free (path3); g_free (savepath); F_FREE (local1); F_FREE (local2); //save updated config file e2_option_file_write (NULL); //do any upgrades needed Plugin *p = e2_plugins_open1 (PLUGINS_DIR G_DIR_SEPARATOR_S UPGRADE_PNAME); //localised path if (p != NULL) { //fake the config version for the plugin //NOTE this needs to be the minimum actionable version in the //upgrade plugin or else that will just install defaults gchar *sv; if (sq != NULL) sv = (strcmp (sq, OLDEST_UPGRADE) < 0) ? OLDEST_UPGRADE: sq; else sv = OLDEST_UPGRADE; g_strlcpy (app.cfgfile_version, sv, sizeof (app.cfgfile_version)); //app.cfgfile_version is reverted in plugin if (!p->plugin_init (p)) //do any upgrades { printd (ERROR, "Can't initialize upgrade plugin: "UPGRADE_PNAME); g_strlcpy (app.cfgfile_version, VERSION RELEASE, sizeof (app.cfgfile_version)); } e2_plugins_unload1 (p, FALSE); } else printd (ERROR, "Can't find upgrade plugin "UPGRADE_PNAME", so can't upgrade the imported config data"); if (sq != NULL) g_free (sq); } //recreate window and runtime data structs as appropriate //this is like e2_option_refresh (FALSE, TRUE) but omits some fatal //parts of that app.rebuild_enabled = FALSE; //block recursion if (import_flags & E2PC_BINDINGS) e2_keybinding_clean (); if (import_flags & E2PC_FILETYPES) g_hash_table_destroy (app.filetypes); if (import_flags & E2PC_PLUGINS) { //this is like e2_plugins_unload_all() but must not unload this plugin if (app.plugins != NULL) { GList *member; for (member = app.plugins; member != NULL; member = member->next) { if (member->data != NULL) { if (! g_str_equal(((Plugin *)member->data)->signature, ANAME VERSION)) //FIXME handle plugins that are not allowed to be unloaded { e2_plugins_unload1 ((Plugin *)member->data, TRUE); member->data = NULL; } } } app.plugins = g_list_remove_all (app.plugins, NULL); } } //clear relevant current information // e2_option_clear_data (); //now re-create things, in the same order as at session-start // e2_option_default_register (); // if (reload) // { // printd (INFO, "reloading config due to external change"); // e2_option_file_read (); // } // e2_option_tree_install_defaults (); if (import_flags & E2PC_PLUGINS) e2_plugins_load_all (); // load plugins (if any) CHECKME what if this plugin is reloaded ? //re-initialise things that are not done in normal 'recreate' functions e2_pane_create_option_data (&app.pane1); e2_pane_create_option_data (&app.pane2); e2_toolbar_initialise (E2_BAR_PANE1); e2_toolbar_initialise (E2_BAR_PANE2); e2_toolbar_initialise (E2_BAR_TASK); e2_toolbar_initialise (E2_BAR_COMMAND); // if (recreate) e2_window_recreate (&app.window); //this also recreates key bindings // else // e2_keybinding_register_all (); if (import_flags & E2PC_FILETYPES) e2_filetype_add_all (); app.rebuild_enabled = TRUE; //unblock } } else { gchar *msg = g_strdup_printf (_("Error reading file %s"), realpath); e2_output_print_error (msg, TRUE); } if (*filepath == '\0') g_free (realpath); F_FREE (localpath); g_ptr_array_free (treeset_names, TRUE); } /** @brief get name of config file to open, via a gtkfilechooser @param button UNUSED the clicked widget @param rt dialog runtime struct @return */ static void _e2pc_select_config_cb (GtkWidget *button, E2P_ConfigData *rt) { GtkWidget *dialog = gtk_file_chooser_dialog_new (NULL, GTK_WINDOW (rt->dialog), GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); e2_dialog_setup_chooser (dialog, _("select configuration data file"), gtk_entry_get_text (GTK_ENTRY (rt->open_entry)), GTK_FILE_CHOOSER_ACTION_OPEN, TRUE, //show hidden FALSE, //single-selection GTK_RESPONSE_OK); //default response gint response; while ((response = gtk_dialog_run (GTK_DIALOG (dialog))) == E2_RESPONSE_USER1) {} if (response == GTK_RESPONSE_OK) { gchar *local = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); gchar *openpath = F_FILENAME_FROM_LOCALE (local); gtk_entry_set_text (GTK_ENTRY (rt->open_entry), openpath); g_free (local); F_FREE (openpath); } gtk_widget_destroy (dialog); } /** @brief save config file with specified name @param button UNUSED the clicked widget @param rt dialog runtime struct @return */ static void _e2pc_save_cb (GtkWidget *button, E2P_ConfigData *rt) { const gchar *savepath = gtk_entry_get_text (GTK_ENTRY (rt->save_entry)); if (*savepath != '\0') { if (e2_option_bool_get ("confirm-overwrite")) { gchar *dlocal = D_FILENAME_TO_LOCALE (savepath); g_strstrip (dlocal); #ifdef E2_VFS VPATH ddata = { dlocal, NULL }; if (e2_fs_access2 (&ddata E2_ERR_NONE()) == 0) #else if (e2_fs_access2 (dlocal E2_ERR_NONE()) == 0) #endif { DialogButtons choice = e2_dialog_ow_check (NULL, dlocal, NONE); if (choice != OK) { g_free (dlocal); return; } } g_free (dlocal); } e2_option_file_write (savepath); } } /** @brief save config file with new name @param button UNUSED the clicked widget @param rt dialog runtime struct @return */ static void _e2pc_saveas_cb (GtkWidget *button, E2P_ConfigData *rt) { GtkWidget *dialog = gtk_file_chooser_dialog_new (NULL, GTK_WINDOW (rt->dialog), GTK_FILE_CHOOSER_ACTION_SAVE, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); e2_dialog_setup_chooser (dialog, _("save configuration data file"), gtk_entry_get_text (GTK_ENTRY (rt->save_entry)), GTK_FILE_CHOOSER_ACTION_SAVE, FALSE, //hide hidden FALSE, //single selection GTK_RESPONSE_OK); //default response gint response; while ((response = gtk_dialog_run (GTK_DIALOG (dialog))) == E2_RESPONSE_USER1) {} if (response == GTK_RESPONSE_OK) { gchar *local = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); #ifndef USE_GTK2_8 //check for O/W if not done by the dialog itself #ifdef E2_VFS VPATH ddata = { local, NULL }; #endif if (e2_option_bool_get ("confirm-overwrite") #ifdef E2_VFS && e2_fs_access2 (&ddata E2_ERR_NONE()) == 0) #else && e2_fs_access2 (local E2_ERR_NONE()) == 0) #endif { DialogButtons choice = e2_dialog_ow_check (NULL, local, NONE); if (choice != OK) { gtk_widget_destroy (dialog); g_free (local); return; } } #endif gchar *savepath = F_FILENAME_FROM_LOCALE (local); gtk_entry_set_text (GTK_ENTRY (rt->save_entry), savepath); g_free (local); F_FREE (savepath); } gtk_widget_destroy (dialog); } /** @brief select directory containing custom icons to be used @param widget clicked button, UNUSED @param rt dialog runtime struct @return */ static void _e2pc_select_icondir_cb (GtkWidget *widget, E2P_ConfigData *rt) { GtkWidget *dialog = gtk_file_chooser_dialog_new (NULL, GTK_WINDOW (rt->dialog), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_OK, NULL); e2_dialog_setup_chooser (dialog, _("select icons directory"), gtk_entry_get_text (GTK_ENTRY (rt->icondir_entry)), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, TRUE, //show hidden FALSE, //single-selection GTK_RESPONSE_OK); //default response gint response; while ((response = gtk_dialog_run (GTK_DIALOG (dialog))) == E2_RESPONSE_USER1) {} if (response == GTK_RESPONSE_OK) { gchar *local = gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (dialog)); // if (strlen (local) > 0) // { gchar *opendir = F_FILENAME_FROM_LOCALE (local); gtk_entry_set_text (GTK_ENTRY (rt->icondir_entry), opendir); F_FREE (opendir); // } g_free (local); } gtk_widget_destroy (dialog); } /** @brief apply icon directory @param widget clicked button, UNUSED @param rt dialog runtime struct @return */ static void _e2pc_apply_icondir_cb (GtkWidget *widget, E2P_ConfigData *rt) { gchar *utfpath = g_strdup (gtk_entry_get_text (GTK_ENTRY (rt->icondir_entry))); if (g_str_has_suffix (utfpath, G_DIR_SEPARATOR_S)) *(utfpath + strlen (utfpath) - sizeof(gchar)) = '\0'; gchar *localpath = F_FILENAME_TO_LOCALE (utfpath); if (g_str_equal (localpath, ICON_DIR)) e2_option_bool_set ("use-icon-dir", FALSE); else { e2_option_bool_set ("use-icon-dir", TRUE); E2_OptionSet *set = e2_option_get ("icon-dir"); e2_option_str_set_direct (set, utfpath); e2_toolbar_recreate_all (); } F_FREE (localpath); g_free (utfpath); } /** @brief select directory in which to save custom icons now in use @param widget clicked button, UNUSED @param rt dialog runtime struct @return */ static void _e2pc_select_iconsavedir_cb (GtkWidget *widget, E2P_ConfigData *rt) { GtkWidget *dialog = gtk_file_chooser_dialog_new (NULL, GTK_WINDOW (rt->dialog), GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); e2_dialog_setup_chooser (dialog, _("select icons directory"), gtk_entry_get_text (GTK_ENTRY (rt->iconsavedir_entry)), GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER, FALSE, //hide-hidden FALSE, //single-selection GTK_RESPONSE_OK); //default response gint response; while ((response = gtk_dialog_run (GTK_DIALOG (dialog))) == E2_RESPONSE_USER1) {} if (response == GTK_RESPONSE_OK) { gchar *local = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); #ifndef USE_GTK2_8 //check for O/W if not done by the dialog itself #ifdef E2_VFS VPATH ddata = { local, NULL }; #endif if (e2_option_bool_get ("confirm-overwrite") #ifdef E2_VFS && e2_fs_access2 (&ddata E2_ERR_NONE()) == 0) #else && e2_fs_access2 (local E2_ERR_NONE()) == 0) #endif { DialogButtons choice = e2_dialog_ow_check (NULL, local, NONE); if (choice != OK) { gtk_widget_destroy (dialog); g_free (local); return; } } #endif gchar *openpath = F_FILENAME_FROM_LOCALE (local); gtk_entry_set_text (GTK_ENTRY (rt->iconsavedir_entry), openpath); g_free (local); F_FREE (openpath); } gtk_widget_destroy (dialog); } /** @brief copy icons to specified directory If the dir is not absolute, curr_view->dir is prepended @param widget clicked button, UNUSED @param rt dialog runtime struct @return */ static void _e2pc_apply_iconsavedir_cb (GtkWidget *widget, E2P_ConfigData *rt) { const gchar *path; gchar *slocal, *dest, *dlocal; //can't have trailing / for copy func slocal = e2_utils_get_icons_path (FALSE); path = gtk_entry_get_text (GTK_ENTRY (rt->iconsavedir_entry)); if (!g_path_is_absolute (path)) //E2_VFSTMPOK dest = e2_utils_dircat (curr_view, path, FALSE); else dest = g_strdup (path); if (g_str_has_suffix (dest, G_DIR_SEPARATOR_S)) *(dest + strlen (dest) - sizeof (gchar)) = '\0'; dlocal = F_FILENAME_TO_LOCALE (dest); DialogButtons result; #ifdef E2_VFS VPATH ddata = { dlocal, NULL }; #endif if (e2_option_bool_get ("confirm-overwrite") #ifdef E2_VFS && e2_fs_access2 (&ddata E2_ERR_NONE()) == 0) #else && e2_fs_access2 (dlocal E2_ERR_NONE()) == 0) #endif result = e2_dialog_ow_check (slocal, dlocal, NONE); else result = OK; if (result == OK) { #ifdef E2_VFS VPATH sdata = { slocal, NULL }; #endif gdk_threads_leave (); //downstream errors invoke local mutex locking #ifdef E2_VFS e2_task_backend_copy (&sdata, &ddata, E2_FTM_NORMAL); #else e2_task_backend_copy (slocal, dlocal, E2_FTM_NORMAL); #endif gdk_threads_enter (); } g_free (slocal); g_free (dest); F_FREE (dlocal); } /** @brief cleanup after cancel button is pressed @param widget clicked button, UNUSED @param rt dialog runtime struct @return */ static void _e2pc_quit_cb (GtkWidget *widget, E2P_ConfigData *rt) { printd (DEBUG, "Config plugin dialog quit cb"); gtk_widget_destroy (rt->dialog); DEALLOCATE (E2P_ConfigData, rt); // gtk_widget_grab_focus (curr_view->treeview); } /** @brief create and show notbook page with config export widgets @param dialog the dialog where the response was triggered @param response the number assigned to the widget that triggered the callback @param rt dialog runtime struct @return */ static void _e2pc_response_cb (GtkDialog *dialog, gint response, E2P_ConfigData *rt) { printd (DEBUG, "Config plugin dialog response cb"); switch (response) { case GTK_RESPONSE_CLOSE: _e2pc_quit_cb (NULL, rt); break; default: break; } } /***************************/ /***** widget creation *****/ /***************************/ /** @brief create and show notebook page with config export widgets @param notebook the notebook widget @param rt ptr to dialog data struct @return */ static void _e2pc_make_export_tab (GtkWidget *notebook, E2P_ConfigData *rt) { GtkWidget *vbox = gtk_vbox_new (FALSE, 0); gtk_widget_show (vbox); #ifdef E2_ASSISTED GtkWidget *label = #endif e2_widget_add_label (vbox, _("Save configuration data in"), 0.5, 0.5, FALSE, E2_PADDING); const gchar *savepath; gchar *local = F_FILENAME_TO_LOCALE (e2_cl_options.config_dir); //default export to config dir if it's usable #ifdef E2_VFS VPATH ddata = { local, NULL }; if (e2_fs_is_dir3 (&ddata E2_ERR_NONE()) && !e2_fs_access (&ddata, R_OK | W_OK | X_OK E2_ERR_NONE())) #else if (e2_fs_is_dir3 (local E2_ERR_NONE()) && !e2_fs_access (local, R_OK | W_OK | X_OK E2_ERR_NONE())) #endif savepath = e2_cl_options.config_dir; else savepath = g_get_home_dir (); F_FREE (local); gchar *savename = g_build_filename (savepath, default_config_file, NULL); local = F_FILENAME_TO_LOCALE (savename); gchar *temppath, *tempext, *local2; guint i = 0; while (TRUE) { tempext = g_strdup_printf (".%s~%d", _("backup"), i); local2 = F_FILENAME_TO_LOCALE (tempext); temppath = e2_utils_strcat (local, local2); g_free (tempext); F_FREE (local2); E2_ERR_DECLARE #ifdef E2_VFS ddata.localpath = temppath; if (e2_fs_access2 (&ddata E2_ERR_PTR()) //checkme need for vfs ? #else if (e2_fs_access2 (temppath E2_ERR_PTR()) //checkme need for vfs ? #endif && E2_ERR_IS (ENOENT)) { E2_ERR_CLEAR break; } E2_ERR_CLEAR g_free (temppath); i++; } F_FREE (local); g_free (savename); savepath = F_FILENAME_FROM_LOCALE (temppath); if (savepath != temppath) g_free (temppath); rt->save_entry = e2_widget_add_entry (vbox, (gchar *) savepath, TRUE, TRUE); #ifdef E2_ASSISTED e2_widget_set_label_relations (GTK_LABEL (label), rt->save_entry); #endif gtk_widget_set_size_request (rt->save_entry, 400, -1); GtkWidget *hbox = gtk_hbutton_box_new (); gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, E2_PADDING); gtk_widget_show (hbox); gtk_box_set_spacing (GTK_BOX (hbox), E2_PADDING_LARGE); gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_END); GtkWidget *btn = e2_button_get (_("Se_lect"), GTK_STOCK_SAVE_AS, _("Select the file in which to store the config data"), _e2pc_saveas_cb, rt); gtk_container_add (GTK_CONTAINER (hbox), btn); btn = e2_button_get (_("_Save"), GTK_STOCK_SAVE, _("Save current config data in the specified file"), _e2pc_save_cb, rt); gtk_container_add (GTK_CONTAINER (hbox), btn); #ifndef E2_ASSISTED GtkWidget * #endif label = gtk_label_new (_("export")); gtk_widget_show (label); gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, label); } /** @brief create and show notebook page with config import widgets @param notebook the notebook widget @param rt ptr to dialog data struct @return */ static void _e2pc_make_import_tab (GtkWidget *notebook, E2P_ConfigData *rt) { GtkWidget *vbox = gtk_vbox_new (FALSE, 0); gtk_widget_show (vbox); #ifdef E2_ASSISTED GtkWidget *label = #endif e2_widget_add_label (vbox, _("Get configuration data from"), 0.5, 0.5, FALSE, E2_PADDING); const gchar *openpath; gchar *local = F_FILENAME_TO_LOCALE (e2_cl_options.config_dir); //default export to config dir if it's usable #ifdef E2_VFS VPATH ddata = { local, NULL }; if (e2_fs_is_dir3 (&ddata E2_ERR_NONE()) && !e2_fs_access (&ddata, R_OK | X_OK E2_ERR_NONE())) #else if (e2_fs_is_dir3 (local E2_ERR_NONE()) && !e2_fs_access (local, R_OK | X_OK E2_ERR_NONE())) #endif openpath = e2_cl_options.config_dir; else openpath = g_get_home_dir (); F_FREE (local); gchar *openname = g_build_filename (openpath, default_config_file, NULL); rt->open_entry = e2_widget_add_entry (vbox, (gchar *) openname, TRUE, TRUE); #ifdef E2_ASSISTED e2_widget_set_label_relations (GTK_LABEL (label), rt->open_entry); #endif gtk_widget_set_size_request (rt->open_entry, 400, -1); GtkWidget *hbox = gtk_hbutton_box_new (); gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, E2_PADDING); gtk_widget_show (hbox); gtk_box_set_spacing (GTK_BOX (hbox), E2_PADDING_LARGE); gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_END); GtkWidget *btn = e2_button_get (_("Se_lect"), GTK_STOCK_OPEN, _("Select the config file from which to get the data"), _e2pc_select_config_cb, rt); gtk_box_pack_start (GTK_BOX (hbox), btn, FALSE, FALSE, 0); btn = e2_button_get (_("_Apply"), GTK_STOCK_APPLY, _("Import config data in accord with choices below"), _e2pc_import_cb, rt); gtk_box_pack_start (GTK_BOX (hbox), btn, FALSE, FALSE, 0); e2_widget_add_separator (vbox, FALSE, E2_PADDING_SMALL); //now the import options hbox = e2_widget_add_box (vbox, TRUE, E2_PADDING_SMALL, FALSE, TRUE, E2_PADDING); GtkWidget *leader = _e2pc_create_radio_button (hbox, ALL_P, _("_all options")); _e2pc_create_radio_grouped_button (hbox, leader, NONTREE_P, FALSE, _("all '_non-group' options")); hbox = e2_widget_add_box (vbox, TRUE, E2_PADDING_SMALL, FALSE, TRUE, E2_PADDING); _e2pc_create_radio_grouped_button (hbox, leader, ALLTREE_P, FALSE, _("all 'g_roup' options")); _e2pc_create_radio_grouped_button (hbox, leader, CUSTOM_P, FALSE, _("_specific group option(s)")); rt->expander = gtk_expander_new_with_mnemonic (_("_groups")); gtk_box_pack_start (GTK_BOX (vbox), rt->expander, FALSE, FALSE, 0); gtk_widget_show (rt->expander); GtkWidget *expvbox = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (rt->expander), expvbox); gtk_widget_show (expvbox); hbox = e2_widget_add_box (expvbox, TRUE, E2_PADDING_SMALL, FALSE, TRUE, E2_PADDING); gchar *labeltxt = _e2pc_get_setlabel (PANEBAR1_P); _e2pc_create_check_button (hbox, PANEBAR1_P, FALSE, labeltxt); g_free (labeltxt); labeltxt = _e2pc_get_setlabel (PANEBAR2_P); _e2pc_create_check_button (hbox, PANEBAR2_P, FALSE, labeltxt); g_free (labeltxt); hbox = e2_widget_add_box (expvbox, TRUE, E2_PADDING_SMALL, FALSE, TRUE, E2_PADDING); labeltxt = _e2pc_get_setlabel (TASKBAR_P); _e2pc_create_check_button (hbox, TASKBAR_P, FALSE, labeltxt); g_free (labeltxt); labeltxt = _e2pc_get_setlabel (CMDBAR_P); _e2pc_create_check_button (hbox, CMDBAR_P, FALSE, labeltxt); g_free (labeltxt); hbox = e2_widget_add_box (expvbox, TRUE, E2_PADDING_SMALL, FALSE, TRUE, E2_PADDING); labeltxt = _e2pc_get_setlabel (MARKS_P); _e2pc_create_check_button (hbox, MARKS_P, FALSE, labeltxt); g_free (labeltxt); labeltxt = _e2pc_get_setlabel (FILETYPES_P); _e2pc_create_check_button (hbox, FILETYPES_P, FALSE, labeltxt); g_free (labeltxt); hbox = e2_widget_add_box (expvbox, TRUE, E2_PADDING_SMALL, FALSE, TRUE, E2_PADDING); labeltxt = _e2pc_get_setlabel (BINDINGS_P); _e2pc_create_check_button (hbox, BINDINGS_P, FALSE, labeltxt); g_free (labeltxt); labeltxt = _e2pc_get_setlabel (ALIASES_P); _e2pc_create_check_button (hbox, ALIASES_P, FALSE, labeltxt); g_free (labeltxt); hbox = e2_widget_add_box (expvbox, TRUE, E2_PADDING_SMALL, FALSE, TRUE, E2_PADDING); labeltxt = _e2pc_get_setlabel (MENU_P); _e2pc_create_check_button (hbox, MENU_P, FALSE, labeltxt); g_free (labeltxt); labeltxt = _e2pc_get_setlabel (PLUGINS_P); _e2pc_create_check_button (hbox, PLUGINS_P, FALSE, labeltxt); g_free (labeltxt); hbox = e2_widget_add_box (expvbox, TRUE, E2_PADDING_SMALL, FALSE, TRUE, E2_PADDING); labeltxt = _e2pc_get_setlabel (CUSTMENU_P); _e2pc_create_check_button (hbox, CUSTMENU_P, FALSE, labeltxt); g_free (labeltxt); #ifndef E2_ASSISTED GtkWidget * #endif label = gtk_label_new (_("import")); gtk_widget_show (label); gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, label); } /** @brief create and show notebook page with config import widgets @param notebook the notebook widget @param rt ptr to dialog data struct @return */ static void _e2pc_make_icons_tab (GtkWidget *notebook, E2P_ConfigData *rt) { GtkWidget *vbox = gtk_vbox_new (FALSE, 0); gtk_widget_show (vbox); #ifdef E2_ASSISTED GtkWidget *label = #endif e2_widget_add_label (vbox, _("Use icons in"), 0.5, 0.5, FALSE, E2_PADDING); gchar *openpath; gchar *localpath = e2_utils_get_icons_path (FALSE); //default icons in config dir if that's usable #ifdef E2_VFS VPATH ddata = { localpath, NULL }; if (!e2_fs_is_dir3 (&ddata E2_ERR_NONE()) || e2_fs_access (&ddata, R_OK | X_OK E2_ERR_NONE())) #else if (!e2_fs_is_dir3 (localpath E2_ERR_NONE()) || e2_fs_access (localpath, R_OK | X_OK E2_ERR_NONE())) #endif openpath = g_build_filename (e2_cl_options.config_dir, _("icons"), NULL); else openpath = D_FILENAME_FROM_LOCALE (localpath); g_free (localpath); rt->icondir_entry = e2_widget_add_entry (vbox, openpath, TRUE, TRUE); #ifdef E2_ASSISTED e2_widget_set_label_relations (GTK_LABEL (label), rt->icondir_entry); #endif g_free (openpath); gtk_widget_set_size_request (rt->icondir_entry, 400, -1); GtkWidget *hbox = gtk_hbutton_box_new (); gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, E2_PADDING); gtk_widget_show (hbox); gtk_box_set_spacing (GTK_BOX (hbox), E2_PADDING_LARGE); gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_END); GtkWidget *btn = e2_button_get (_("Se_lect"), GTK_STOCK_OPEN, _("Select the directory where the icons are"), _e2pc_select_icondir_cb, rt); gtk_box_pack_start (GTK_BOX (hbox), btn, FALSE, FALSE, 0); btn = e2_button_get (_("_Apply"), GTK_STOCK_APPLY, _("Apply the chosen icon directory"), _e2pc_apply_icondir_cb, rt); gtk_box_pack_start (GTK_BOX (hbox), btn, FALSE, FALSE, 0); e2_widget_add_separator (vbox, FALSE, E2_PADDING_SMALL); //now the exporting #ifdef E2_ASSISTED label = #endif e2_widget_add_label (vbox, _("Copy current icons to"), 0.5, 0.5, FALSE, E2_PADDING); openpath = g_strconcat (e2_cl_options.config_dir,G_DIR_SEPARATOR_S BINNAME"-", _("icons"), NULL); rt->iconsavedir_entry = e2_widget_add_entry (vbox, openpath, TRUE, TRUE); #ifdef E2_ASSISTED e2_widget_set_label_relations (GTK_LABEL (label), rt->iconsavedir_entry); #endif g_free (openpath); gtk_widget_set_size_request (rt->iconsavedir_entry, 400, -1); hbox = gtk_hbutton_box_new (); gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, E2_PADDING); gtk_widget_show (hbox); gtk_box_set_spacing (GTK_BOX (hbox), E2_PADDING_LARGE); gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_END); btn = e2_button_get (_("Se_lect"), GTK_STOCK_OPEN, _("Select the directory where the icons will be saved"), _e2pc_select_iconsavedir_cb, rt); gtk_box_pack_start (GTK_BOX (hbox), btn, FALSE, FALSE, 0); btn = e2_button_get (_("C_opy"), GTK_STOCK_COPY, _("Copy the icons to the chosen directory"), _e2pc_apply_iconsavedir_cb, rt); gtk_box_pack_start (GTK_BOX (hbox), btn, FALSE, FALSE, 0); GtkWidget *pagelabel = gtk_label_new (_("icons")); gtk_widget_show (pagelabel); gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, pagelabel); } /** @brief establish and show the dialog @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2p_config_dialog_create (gpointer from, E2_ActionRuntime *art) { E2P_ConfigData *rt = ALLOCATE (E2P_ConfigData); CHECKALLOCATEDWARN (rt, return FALSE;) srt = rt; //copy for some funcs that don't get rt as an argument //create dialog rt->dialog = e2_dialog_create (NULL, NULL, _("manage configuration data"), _e2pc_response_cb, rt); //populate it with widgets GtkWidget *vbox = GTK_DIALOG (rt->dialog)->vbox; GtkWidget *notebook = e2_widget_add_notebook (vbox, TRUE, 0, NULL, NULL); gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_TOP); _e2pc_make_export_tab (notebook, rt); //page 0 _e2pc_make_import_tab (notebook, rt); //page 1 _e2pc_make_icons_tab (notebook, rt); //page 2 gtk_widget_show (notebook); //add action buttons in the order that they will appear e2_dialog_add_defined_button (rt->dialog, &E2_BUTTON_CLOSE); e2_dialog_set_negative_response (rt->dialog, GTK_RESPONSE_CLOSE); e2_dialog_setup (rt->dialog, app.main_window); gtk_widget_show (rt->dialog); gtk_main (); return TRUE; } //aname must be confined to this module static gchar *aname; /** @brief plugin initialization function, called by main program @param p ptr to plugin data struct @return TRUE if the initialization succeeds, else FALSE */ gboolean init_plugin (Plugin *p) { aname = _("manage"); p->signature = ANAME VERSION; p->menu_name = _("_Configure.."); p->description = _("Export or import configuration data"); p->icon = "plugin_config_"E2IP".png"; if (p->action == NULL) { //no need to free this gchar *action_name = g_strconcat (_A(2),".",aname,NULL); p->action = e2_plugins_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2p_config_dialog_create, NULL, FALSE, 0, NULL); return TRUE; } return FALSE; } /** @brief cleanup transient things for this plugin @param p pointer to data struct for the plugin @return TRUE if all cleanups were completed */ gboolean clean_plugin (Plugin *p) { gchar *action_name = g_strconcat (_A(2),".",aname,NULL); gboolean ret = e2_plugins_action_unregister (action_name); g_free (action_name); return ret; } emelfm2-0.4.1/plugins/e2p_sort_by_ext.c0000600000175000017500000000715310644417464016763 0ustar cairocairo/* $Id: e2p_sort_by_ext.c 475 2007-07-09 11:42:44Z tpgww $ Copyright (C) 2003-2007 tooar Portions copyright (C) 1999 Michael Clark This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file plugins/e2p_sort_by_ext.c @brief plugin to enable sorting of filelists by extension, instead of by name This is effectively redundant, pressing before a sort-click achieves the same effect */ #include "emelfm2.h" #include #include "e2_plugins.h" #include "e2_filelist.h" extern gint stored_col_order[2][MAX_COLUMNS]; extern gint displayed_col_order[2][MAX_COLUMNS]; /** @brief sort active filepane by item-extension @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2p_sort_by_ext (gpointer from, E2_ActionRuntime *art) { //replace the sort function for filename column of liststore //sortable uses base model, even with filter-model parent GtkTreeSortable *sortable = GTK_TREE_SORTABLE (curr_view->store); gint sortcolnow; GtkSortType sort_order = curr_view->sort_order; if (curr_view->extsort) //already extension-sorted, toggle direction curr_view->sort_order = (sort_order == GTK_SORT_ASCENDING) ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING; else curr_view->extsort = TRUE; gtk_tree_sortable_get_sort_column_id (sortable, &sortcolnow, &sort_order); gtk_widget_hide (curr_view->sort_arrows[sortcolnow]); gtk_arrow_set (GTK_ARROW (curr_view->sort_arrows[FILENAME]), (curr_view->sort_order == GTK_SORT_ASCENDING) ? GTK_ARROW_RIGHT : GTK_ARROW_LEFT, GTK_SHADOW_IN); gtk_widget_show (curr_view->sort_arrows[FILENAME]); gtk_tree_sortable_set_sort_func (sortable, FILENAME, (GtkTreeIterCompareFunc) e2_fileview_ext_sort, &sort_order, NULL); //do the sort gtk_tree_sortable_set_sort_column_id (sortable, FILENAME, curr_view->sort_order); return TRUE; } //aname must be confined to this module static gchar *aname; /** @brief plugin initialization function, called by main program @param p ptr to plugin data struct @return TRUE if the initialization succeeds, else FALSE */ gboolean init_plugin (Plugin *p) { #define ANAME "sort_by_ext" aname = _("sort_by_ext"); p->signature = ANAME VERSION; p->menu_name = _("Extension _sort"); p->description = _("Sort the active file pane by filename extension"); p->icon = "plugin_extsort_"E2IP".png"; //use icon file pathname if appropriate if (p->action == NULL) { //no need to free this gchar *action_name = g_strconcat (_A(10),".",aname,NULL); p->action = e2_plugins_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2p_sort_by_ext, NULL, FALSE, 0, NULL); return TRUE; } return FALSE; } /** @brief cleanup transient things for this plugin @param p pointer to data struct for the plugin @return TRUE if all cleanups were completed */ gboolean clean_plugin (Plugin *p) { gchar *action_name = g_strconcat (_A(10),".",aname,NULL); gboolean ret = e2_plugins_action_unregister (action_name); g_free (action_name); return ret; } emelfm2-0.4.1/plugins/e2p_dircmp.c0000600000175000017500000006772610722641152015702 0ustar cairocairo/* $Id: e2p_dircmp.c 755 2007-11-26 22:02:18Z tpgww $ Portions copyright (C) 2006-2007 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file plugins/e2p_dircmp.c @brief plugin for showing differences between the contents of active and inactive directories */ #include "emelfm2.h" #include "e2p_dircmp.h" #include "e2_plugins.h" #include "e2_fileview.h" #include "e2_filelist.h" #include "e2_task.h" typedef struct _E2_CmpData { #ifdef E2_VFS GError **operr; PlaceInfo *otherspace; #endif gint oldroot_len; gchar *newroot; } E2_CmpData; /********* start of borrowed code *********/ /* This code implements the MD5 message-digest algorithm, by Ron Rivest. This code was written by Colin Plumb in 1993, our understanding is that no copyright is claimed and that this code is in the public domain. Equivalent code is available from RSA Data Security, Inc. This code has been tested against that, and is functionally equivalent. To compute the message digest of a chunk of bytes, declare an MD5Context structure, pass it to MD5Init, call MD5Update as needed on buffers full of bytes, and then call MD5Final, which will fill a supplied 16-byte array with the digest. */ #ifndef HIGHFIRST #define byteReverse(buf, len) /* Nothing */ #else void byteReverse(unsigned char *buf, unsigned longs); #ifndef ASM_MD5 /* Note: this code is harmless on little-endian machines. */ void byteReverse(unsigned char *buf, unsigned longs) { u_int32_t t; do { t = (u_int32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 | ((unsigned) buf[1] << 8 | buf[0]); *(u_int32_t *) buf = t; buf += 4; } while (--longs); } #endif #endif /* Start MD5 accumulation. Set bit count to 0 and buffer to mysterious initialization constants. */ void MD5Init(struct MD5Context *ctx) { ctx->buf[0] = 0x67452301; ctx->buf[1] = 0xefcdab89; ctx->buf[2] = 0x98badcfe; ctx->buf[3] = 0x10325476; ctx->bits[0] = 0; ctx->bits[1] = 0; } /* Update context to reflect the concatenation of another buffer full of bytes. */ void MD5Update(struct MD5Context *ctx, unsigned char const *buf, unsigned len) { u_int32_t t; /* Update bitcount */ t = ctx->bits[0]; if ((ctx->bits[0] = t + ((u_int32_t) len << 3)) < t) ctx->bits[1]++; /* Carry from low to high */ ctx->bits[1] += len >> 29; t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ /* Handle any leading odd-sized chunks */ if (t) { unsigned char *p = (unsigned char *) ctx->in + t; t = 64 - t; if (len < t) { memcpy(p, buf, len); return; } memcpy(p, buf, t); byteReverse(ctx->in, 16); MD5Transform(ctx->buf, (u_int32_t *) ctx->in); buf += t; len -= t; } /* Process data in 64-byte chunks */ while (len >= 64) { memcpy(ctx->in, buf, 64); byteReverse(ctx->in, 16); MD5Transform(ctx->buf, (u_int32_t *) ctx->in); buf += 64; len -= 64; } /* Handle any remaining bytes of data. */ memcpy(ctx->in, buf, len); } /* Final wrapup - pad to 64-byte boundary with the bit pattern 1 0* (64-bit count of bits processed, MSB-first) */ void MD5Final(unsigned char digest[16], struct MD5Context *ctx) { unsigned count; unsigned char *p; /* Compute number of bytes mod 64 */ count = (ctx->bits[0] >> 3) & 0x3F; /* Set the first char of padding to 0x80. This is safe since there is always at least one byte free */ p = ctx->in + count; *p++ = 0x80; /* Bytes of padding needed to make 64 bytes */ count = 64 - 1 - count; /* Pad out to 56 mod 64 */ if (count < 8) { /* Two lots of padding: Pad the first block to 64 bytes */ memset(p, 0, count); byteReverse(ctx->in, 16); MD5Transform(ctx->buf, (u_int32_t *) ctx->in); /* Now fill the next block with 56 bytes */ memset(ctx->in, 0, 56); } else { /* Pad block to 56 bytes */ memset(p, 0, count - 8); } byteReverse(ctx->in, 14); /* Append length in bits and transform */ ((u_int32_t *) ctx->in)[14] = ctx->bits[0]; ((u_int32_t *) ctx->in)[15] = ctx->bits[1]; MD5Transform(ctx->buf, (u_int32_t *) ctx->in); byteReverse((unsigned char *) ctx->buf, 4); memcpy(digest, ctx->buf, 16); memset(ctx, 0, sizeof(* ctx)); /* In case it's sensitive */ /* The original version of this code omitted the asterisk. In effect, only the first part of ctx was wiped with zeros, not the whole thing. Bug found by Derek Jones. Original line: */ // memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */ } #ifndef ASM_MD5 /* The four core functions - F1 is optimized somewhat */ /* #define F1(x, y, z) (x & y | ~x & z) */ #define F1(x, y, z) (z ^ (x & (y ^ z))) #define F2(x, y, z) F1(z, x, y) #define F3(x, y, z) (x ^ y ^ z) #define F4(x, y, z) (y ^ (x | ~z)) /* This is the central step in the MD5 algorithm. */ #ifdef __PUREC__ #define MD5STEP(f, w, x, y, z, data, s) \ ( w += f /*(x, y, z)*/ + data, w = w<>(32-s), w += x ) #else #define MD5STEP(f, w, x, y, z, data, s) \ ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) #endif /* The core of the MD5 algorithm, this alters an existing MD5 hash to reflect the addition of 16 longwords of new data. MD5Update blocks the data and converts bytes into longwords for this routine. */ void MD5Transform(u_int32_t buf[4], u_int32_t const in[16]) { register u_int32_t a, b, c, d; a = buf[0]; b = buf[1]; c = buf[2]; d = buf[3]; #ifdef __PUREC__ /* PureC Weirdness... (GG) */ MD5STEP(F1(b,c,d), a, b, c, d, in[0] + 0xd76aa478L, 7); MD5STEP(F1(a,b,c), d, a, b, c, in[1] + 0xe8c7b756L, 12); MD5STEP(F1(d,a,b), c, d, a, b, in[2] + 0x242070dbL, 17); MD5STEP(F1(c,d,a), b, c, d, a, in[3] + 0xc1bdceeeL, 22); MD5STEP(F1(b,c,d), a, b, c, d, in[4] + 0xf57c0fafL, 7); MD5STEP(F1(a,b,c), d, a, b, c, in[5] + 0x4787c62aL, 12); MD5STEP(F1(d,a,b), c, d, a, b, in[6] + 0xa8304613L, 17); MD5STEP(F1(c,d,a), b, c, d, a, in[7] + 0xfd469501L, 22); MD5STEP(F1(b,c,d), a, b, c, d, in[8] + 0x698098d8L, 7); MD5STEP(F1(a,b,c), d, a, b, c, in[9] + 0x8b44f7afL, 12); MD5STEP(F1(d,a,b), c, d, a, b, in[10] + 0xffff5bb1L, 17); MD5STEP(F1(c,d,a), b, c, d, a, in[11] + 0x895cd7beL, 22); MD5STEP(F1(b,c,d), a, b, c, d, in[12] + 0x6b901122L, 7); MD5STEP(F1(a,b,c), d, a, b, c, in[13] + 0xfd987193L, 12); MD5STEP(F1(d,a,b), c, d, a, b, in[14] + 0xa679438eL, 17); MD5STEP(F1(c,d,a), b, c, d, a, in[15] + 0x49b40821L, 22); MD5STEP(F2(b,c,d), a, b, c, d, in[1] + 0xf61e2562L, 5); MD5STEP(F2(a,b,c), d, a, b, c, in[6] + 0xc040b340L, 9); MD5STEP(F2(d,a,b), c, d, a, b, in[11] + 0x265e5a51L, 14); MD5STEP(F2(c,d,a), b, c, d, a, in[0] + 0xe9b6c7aaL, 20); MD5STEP(F2(b,c,d), a, b, c, d, in[5] + 0xd62f105dL, 5); MD5STEP(F2(a,b,c), d, a, b, c, in[10] + 0x02441453L, 9); MD5STEP(F2(d,a,b), c, d, a, b, in[15] + 0xd8a1e681L, 14); MD5STEP(F2(c,d,a), b, c, d, a, in[4] + 0xe7d3fbc8L, 20); MD5STEP(F2(b,c,d), a, b, c, d, in[9] + 0x21e1cde6L, 5); MD5STEP(F2(a,b,c), d, a, b, c, in[14] + 0xc33707d6L, 9); MD5STEP(F2(d,a,b), c, d, a, b, in[3] + 0xf4d50d87L, 14); MD5STEP(F2(c,d,a), b, c, d, a, in[8] + 0x455a14edL, 20); MD5STEP(F2(b,c,d), a, b, c, d, in[13] + 0xa9e3e905L, 5); MD5STEP(F2(a,b,c), d, a, b, c, in[2] + 0xfcefa3f8L, 9); MD5STEP(F2(d,a,b), c, d, a, b, in[7] + 0x676f02d9L, 14); MD5STEP(F2(c,d,a), b, c, d, a, in[12] + 0x8d2a4c8aL, 20); MD5STEP(F3(b,c,d), a, b, c, d, in[5] + 0xfffa3942L, 4); MD5STEP(F3(a,b,c), d, a, b, c, in[8] + 0x8771f681L, 11); MD5STEP(F3(d,a,b), c, d, a, b, in[11] + 0x6d9d6122L, 16); MD5STEP(F3(c,d,a), b, c, d, a, in[14] + 0xfde5380cL, 23); MD5STEP(F3(b,c,d), a, b, c, d, in[1] + 0xa4beea44L, 4); MD5STEP(F3(a,b,c), d, a, b, c, in[4] + 0x4bdecfa9L, 11); MD5STEP(F3(d,a,b), c, d, a, b, in[7] + 0xf6bb4b60L, 16); MD5STEP(F3(c,d,a), b, c, d, a, in[10] + 0xbebfbc70L, 23); MD5STEP(F3(b,c,d), a, b, c, d, in[13] + 0x289b7ec6L, 4); MD5STEP(F3(a,b,c), d, a, b, c, in[0] + 0xeaa127faL, 11); MD5STEP(F3(d,a,b), c, d, a, b, in[3] + 0xd4ef3085L, 16); MD5STEP(F3(c,d,a), b, c, d, a, in[6] + 0x04881d05L, 23); MD5STEP(F3(b,c,d), a, b, c, d, in[9] + 0xd9d4d039L, 4); MD5STEP(F3(a,b,c), d, a, b, c, in[12] + 0xe6db99e5L, 11); MD5STEP(F3(d,a,b), c, d, a, b, in[15] + 0x1fa27cf8L, 16); MD5STEP(F3(c,d,a), b, c, d, a, in[2] + 0xc4ac5665L, 23); MD5STEP(F4(b,c,d), a, b, c, d, in[0] + 0xf4292244L, 6); MD5STEP(F4(a,b,c), d, a, b, c, in[7] + 0x432aff97L, 10); MD5STEP(F4(d,a,b), c, d, a, b, in[14] + 0xab9423a7L, 15); MD5STEP(F4(c,d,a), b, c, d, a, in[5] + 0xfc93a039L, 21); MD5STEP(F4(b,c,d), a, b, c, d, in[12] + 0x655b59c3L, 6); MD5STEP(F4(a,b,c), d, a, b, c, in[3] + 0x8f0ccc92L, 10); MD5STEP(F4(d,a,b), c, d, a, b, in[10] + 0xffeff47dL, 15); MD5STEP(F4(c,d,a), b, c, d, a, in[1] + 0x85845dd1L, 21); MD5STEP(F4(b,c,d), a, b, c, d, in[8] + 0x6fa87e4fL, 6); MD5STEP(F4(a,b,c), d, a, b, c, in[15] + 0xfe2ce6e0L, 10); MD5STEP(F4(d,a,b), c, d, a, b, in[6] + 0xa3014314L, 15); MD5STEP(F4(c,d,a), b, c, d, a, in[13] + 0x4e0811a1L, 21); MD5STEP(F4(b,c,d), a, b, c, d, in[4] + 0xf7537e82L, 6); MD5STEP(F4(a,b,c), d, a, b, c, in[11] + 0xbd3af235L, 10); MD5STEP(F4(d,a,b), c, d, a, b, in[2] + 0x2ad7d2bbL, 15); MD5STEP(F4(c,d,a), b, c, d, a, in[9] + 0xeb86d391L, 21); #else MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); #endif buf[0] += a; buf[1] += b; buf[2] += c; buf[3] += d; } #endif /*************** end of borrowed code ***************/ /** @brief blockwise read and hash the regular file @a filepath Uses file descriptors, which means files must be real, local Any error message here expects BGL to be open @param filepath localised string, absolute path of item to read @return pointer to newly-allocated string containing the computed hash, or NULL on error */ static guchar *_e2p_diff_dohash (VPATH *filepath) { HASH_CONTEXT md; guchar buffer [BUFSIZ]; guchar sum [HASH_LENGTH+1]; //computed hash + trailing \0 gint fdesc; E2_ERR_DECLARE #if defined(__USE_GNU) && defined(O_NOATIME) gint flags = O_RDONLY; if (!e2_fs_access (filepath, W_OK E2_ERR_PTR())) flags |= O_NOATIME; else { //try to proceed without atime preservation E2_ERR_CLEAR } fdesc = e2_fs_safeopen (VPSTR (filepath), flags, 0); #else //be strict about open-flags fdesc = e2_fs_safeopen (filepath, O_RDONLY, 0); #endif if (fdesc < 0) { #ifdef E2_VFS e2_fs_set_error_from_errno (&E2_ERR_NAME); #endif e2_fs_error_local (_("Cannot open '%s' for reading"), filepath E2_ERR_MSGL()); E2_ERR_CLEAR return NULL; } guint64 bytes_read = 0; //used for error recovery HASH_Init (&md); while (TRUE) { // clear buffer in case we need to pad the hash memset (buffer, 0, BUFSIZ); ssize_t n_read = e2_fs_read (fdesc, buffer, BUFSIZ E2_ERR_PTR()); if (n_read == BUFSIZ) { HASH_Update (&md, buffer, BUFSIZ); bytes_read += n_read; } else if (n_read == 0) break; else if (n_read < 0) { // if (1) //FIXME fatal error handling //these are the fatal errors used in md5deep if ( E2_ERR_IS (EACCES) //permission denied (changed since opened ?) || E2_ERR_IS (ENODEV) //operation not supported (can't happen?) || E2_ERR_IS (EBADF) //bad file descriptor (can't happen?) || E2_ERR_IS (EFBIG) //file too big || E2_ERR_IS (ETXTBSY) //text file busy ) { e2_fs_error_local (_("Error reading file %s"), filepath E2_ERR_MSGL()); E2_ERR_CLEAR return NULL; } //non-fatal, we can continue E2_ERR_CLEAR HASH_Update(&md, buffer, BUFSIZ); bytes_read += BUFSIZ; /* but the file pointer's now undefined. So manually advance it to the next read position */ lseek (fdesc, bytes_read, SEEK_SET); } else if (n_read < BUFSIZ) { //end of the file, but not a full buffer //update the hash accordingly HASH_Update (&md, buffer, n_read); break; } } HASH_Final (sum, &md); /* if we want to return result in human-readable form static gchar hex [] = "0123456789abcdef"; // static gchar result [2 * HASH_LENGTH + 1]; gint i; for (i = 0; i < HASH_LENGTH; ++i) { result[2 * i] = hex[(sum[i] >> 4) & 0xf]; result[2 * i + 1] = hex[sum[i] & 0xf]; } result [2 * HASH_LENGTH] = '\0'; hash = strdup (result); else */ sum [HASH_LENGTH] = '\0'; //casting silliness to prevent warnings ... guchar *hash = (guchar *) g_strdup ((gchar *) sum); /* overkill if (hash == NULL) { gchar *utf = F_DISPLAYNAME_FROM_LOCALE (filename); gchar *msg = g_strdup_printf ( _("Md5 calculation failed for %s - out of memory"), utf); gdk_threads_enter (); e2_output_print_error (msg, TRUE); gdk_threads_leave (); F_FREE (utf); } */ TEMP_FAILURE_RETRY (close (fdesc)); //FIXME vfs return hash; } /** @brief determine equality of non-dir items described by data in @a data BGL is expected to be open/off @param localpath absolute path of non-directory item to be compared, localised string @param statptr pointer to info from lstat() for @a localpath @param localotherpath absolute path of item to be compared, localised string @return TRUE if the items are equal, (also FALSE after memory shortage) */ static gboolean _e2p_diff1 (VPATH *localpath, const struct stat *statptr, VPATH *localotherpath) { gboolean retval; struct stat othersb; if (e2_fs_lstat (localotherpath, &othersb E2_ERR_NONE())) retval = FALSE; //lstat() failed, assume this means we can't find a match else if ((statptr->st_mode & S_IFMT) != (othersb.st_mode & S_IFMT)) retval = FALSE; //unequal type = quick check else if (statptr->st_size != othersb.st_size) retval = FALSE; //unequal size = quick check else if (S_ISREG (othersb.st_mode) && othersb.st_size > 0) { //determine equality of items' md5 sums guchar *hash1 = _e2p_diff_dohash (localpath); if (hash1 == NULL) retval = FALSE; else { guchar *hash2 = _e2p_diff_dohash (localotherpath); if (hash2 == NULL) { g_free (hash1); retval = FALSE; } else { retval = ! strcmp ((gchar *) hash1, (gchar *) hash2); g_free (hash1); g_free (hash2); } } } else if (S_ISLNK (othersb.st_mode)) { //first, check for equality of link targets gchar *target1, *target2; #ifdef USE_GLIB2_10 target1 = g_slice_alloc (PATH_MAX); #else target1 = g_try_malloc (PATH_MAX); #endif CHECKALLOCATEDWARNT (target1, ); if (target1 != NULL) { gint len = e2_fs_readlink (localpath, target1, PATH_MAX E2_ERR_NONE()); //FIXME vfs if (len < 0) len = 0; //hack to deal with error *(target1 + len) = '\0'; #ifdef USE_GLIB2_10 target2 = g_slice_alloc (PATH_MAX); #else target2 = g_try_malloc (PATH_MAX); #endif CHECKALLOCATEDWARNT (target2, ); if (target2 != NULL) { len = e2_fs_readlink (localotherpath, target2, PATH_MAX E2_ERR_NONE()); //FIXME vfs if (len < 0) len = 0; *(target2 + len) = '\0'; retval = !strcmp (target1, target2); /* //the targets actually match ? if (!strcmp (target1, target2)) { E2_ERR_DECLARE gchar *target1 = g_try_malloc (PATH_MAX); CHECKALLOCATEDWARNT (target1, return FALSE;) if (target1 != NULL) { g_strlcpy (target1, localpath, PATH_MAX); if (!e2_fs_walk_link (&target1 E2_ERR_ARG())) { //FIXME report error E2_ERR_CLEAR return FALSE; } gchar *target2 = g_try_malloc (PATH_MAX); CHECKALLOCATEDWARNT (target2, FIXMEg_free (target1); return FALSE;) if (target2 != NULL) { g_strlcpy (target2, localotherpath, PATH_MAX); if (!e2_fs_walk_link (&target2 E2_ERR_ARG())) { //FIXME report error E2_ERR_CLEAR g_free (target1); g_free (target2); return FALSE; } if (e2_fs_lstat (target1, &othersb E2_ERR_NONE())) retval = FALSE; else { CHECK FOR DIR retval = _e2p_diff1 (target1, &othersb, target2); } g_free (target2); } g_free (target1); } } else retval = FALSE; */ #ifdef USE_GLIB2_10 g_slice_free1 (PATH_MAX, target2); #else g_free (target2); #endif } else retval = FALSE; #ifdef USE_GLIB2_10 g_slice_free1 (PATH_MAX, target1); #else g_free (target1); #endif } else retval = FALSE; } else //at this point, empty regular files, or other types (various types of special file) //must be equal retval = TRUE; return retval; } /** @brief helper function for checking directories item-count This is a callback for a treewalk function. Each dir should bump @a count by 2 more than the no. of items (due to the DP or DRR and DP reports). @param local_name path of item reported by the walker, localised string @param statbuf pointer to struct stat with data about @a local_name @param status code from the walker, indicating what type of item is being reported @param count pointer to store for items count @return E2TW_CONTINUE always */ static E2_TwResult _e2p_diff_count_twcb (VPATH *local_name, const struct stat *statbuf, E2_TwStatus status, guint *count) { (*count)++; return E2TW_CONTINUE; } /** @brief determine equality of dir items described by data in @a data @param data ptr to data struct for items to be compared This is a callback for a treewalk function Error message expects BGL to be open/off @param localpath absolute path of item reported by the walker, localised string @param statbuf pointer to struct stat with data about @a localpath @param status code from the walker, indicating what type of report it is @param twdata pointer to tw data struct @return E2TW_CONTINUE when items match, E2TW_STOP when they don't or can't decide */ static E2_TwResult _e2p_diff_twcb (VPATH *localpath, const struct stat *statptr, E2_TwStatus status, E2_CmpData *twdata) { gchar *localotherpath; E2_TwResult retval = E2TW_STOP; #ifdef E2_VFS VPATH ddata; #endif switch (status) { case E2TW_F: //not directory or link case E2TW_SL: //symbolic link case E2TW_SLN: //symbolic link naming non-existing file localotherpath = e2_utils_strcat (twdata->newroot, (gchar *)localpath + twdata->oldroot_len); #ifdef E2_VFS ddata.localpath = localotherpath; ddata.spacedata = twdata->otherspace; if (_e2p_diff1 (localpath, statptr, &ddata)) #else if (_e2p_diff1 (localpath, statptr, localotherpath)) #endif retval = E2TW_CONTINUE; g_free (localotherpath); break; case E2TW_D: //directory case E2TW_DRR: //directory now readable { struct stat othrstatbuf; localotherpath = e2_utils_strcat (twdata->newroot, (gchar *)localpath + twdata->oldroot_len); #ifdef E2_VFS ddata.localpath = localotherpath; ddata.spacedata = twdata->otherspace; if (e2_fs_stat (&ddata, &othrstatbuf E2_ERR_NONE()) #else if (e2_fs_stat (localotherpath, &othrstatbuf E2_ERR_NONE()) #endif || !S_ISDIR (othrstatbuf.st_mode) || S_ISLNK (othrstatbuf.st_mode)) break; //maybe there are more items in the other dir, or if this dir is empty //there will be no other check that the other even exists guint count = 0; e2_fs_tw (localpath, _e2p_diff_count_twcb, &count, 1, E2TW_XQT | E2TW_PHYS E2_ERR_NONE()); guint count2 = 0; localotherpath = e2_utils_strcat (twdata->newroot, (gchar *)localpath + twdata->oldroot_len); #ifdef E2_VFS e2_fs_tw (&ddata, _e2p_diff_count_twcb, &count2, 1, #else e2_fs_tw (localotherpath, _e2p_diff_count_twcb, &count2, 1, #endif E2TW_XQT | E2TW_PHYS E2_ERR_NONE()); g_free (localotherpath); if (count != count2) break; } case E2TW_DP: //directory, finished retval = E2TW_CONTINUE; //effectively ignore these break; // case E2TW_DL: //directory, not opened due to tree-depth limit // case E2TW_DM: //directory, not opened due to different file system // case E2TW_DNR: //unreadable directory (for which, error is reported upstream) // case E2TW_NS: //un-statable item (for which, error is reported upstream) default: break; } return retval; } /** @brief thread function to iterate over active pane file list to check for matches BGL will be open @param thread_data UNUSED NULL pointer specified when thread was established @return NULL */ static gpointer _e2p_diff_all (gpointer thread_data) { GtkTreeIter iter; GtkTreeModel *model = curr_view->model; if (gtk_tree_model_get_iter_first (model, &iter)); { //it's not empty e2_filelist_disable_refresh (); gdk_threads_enter (); //prevent thread-related hang! e2_window_set_cursor (GDK_WATCH); gdk_threads_leave (); while (TRUE) { LISTS_LOCK //CHECKME abort if cd detected ? gboolean busy = curr_view->listcontrols.refresh_working || curr_view->listcontrols.cd_working; LISTS_UNLOCK if (!busy) break; usleep (100000); } gchar *curr_local, *other_local, *currpath; FileInfo *info; struct stat othersb; E2_CmpData data; #ifdef E2_VFS data.operr = NULL; //or something ... data.otherspace = other_view->spacedata; VPATH sdata; VPATH ddata; sdata.spacedata = curr_view->spacedata; ddata.spacedata = other_view->spacedata; #endif curr_local = F_FILENAME_TO_LOCALE (curr_view->dir); other_local = F_FILENAME_TO_LOCALE (other_view->dir); gboolean matched; GtkTreeSelection *sel = curr_view->selection; gdk_threads_enter (); gtk_tree_selection_unselect_all (sel); //start with clean slate gdk_threads_leave (); do { #ifdef E2_VFSTMP //CLEAR any error data fom last loop #endif gtk_tree_model_get (model, &iter, FINFO, &info, -1); data.newroot = e2_utils_strcat (other_local, info->filename); #ifdef E2_VFS ddata.localpath = data.newroot; if (e2_fs_lstat (&ddata, &othersb E2_ERR_NONE())) #else if (e2_fs_lstat (data.newroot, &othersb E2_ERR_NONE())) #endif matched = FALSE; //lstat() failed, crudely assume can't find anything to match else { currpath = e2_utils_strcat (curr_local, info->filename); if (S_ISDIR (info->statbuf.st_mode) && S_ISDIR (othersb.st_mode)) { data.oldroot_len = strlen (currpath); #ifdef E2_VFS sdata.localpath = currpath; matched = e2_fs_tw (&sdata, _e2p_diff_twcb, &data, -1, #else matched = e2_fs_tw (currpath, _e2p_diff_twcb, &data, -1, #endif E2TW_PHYS E2_ERR_NONE()); } else if (!S_ISDIR (info->statbuf.st_mode) && !S_ISDIR (othersb.st_mode)) #ifdef E2_VFS { sdata.localpath = currpath; matched = _e2p_diff1 (&sdata, &info->statbuf, &ddata); } #else matched = _e2p_diff1 (currpath, &info->statbuf, data.newroot); #endif else matched = FALSE; //mixture of dir and non-dir g_free (currpath); } if (matched) { gdk_threads_enter (); gtk_tree_selection_select_iter (sel, &iter); gdk_threads_leave (); } g_free (data.newroot); } while (gtk_tree_model_iter_next (model, &iter)); F_FREE (curr_local); F_FREE (other_local); #ifdef E2_VFS //cleanup data.operr //or something ... #endif gdk_threads_enter (); e2_window_set_cursor (GDK_LEFT_PTR); gdk_threads_leave (); e2_filelist_enable_refresh (); } return NULL; } /** @brief directory-comparison plugin action This creates a thread to iterate over active pane file list to check for matches @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2p_diff (gpointer from, E2_ActionRuntime *art) { g_thread_create (_e2p_diff_all, NULL, FALSE, NULL); return TRUE; } //aname must be confined to this module static gchar *aname; /** @brief plugin initialization function, called by main program @param p ptr to plugin data struct @return TRUE if the initialization succeeds, else FALSE */ gboolean init_plugin (Plugin *p) { #define ANAME "dircmp" aname = _("compare"); p->signature = ANAME VERSION; p->menu_name = _("C_ompare"); p->description = _("Select active-pane items which are duplicated in the other pane"); p->icon = "plugin_"ANAME"_"E2IP".png"; //prepend path if appropriate if (p->action == NULL) { //no need to free this gchar *action_name = g_strconcat (_A(13),".",aname,NULL); p->action = e2_plugins_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2p_diff, NULL, FALSE, 0, NULL); return TRUE; } return FALSE; } /** @brief cleanup transient things for this plugin @param p pointer to data struct for the plugin @return TRUE if all cleanups were completed */ gboolean clean_plugin (Plugin *p) { gchar *action_name = g_strconcat (_A(13),".",aname,NULL); gboolean ret = e2_plugins_action_unregister (action_name); g_free (action_name); return ret; } emelfm2-0.4.1/plugins/e2p_names_clip.c0000600000175000017500000000724110657471775016543 0ustar cairocairo/* $Id: e2p_names_clip.c 596 2007-08-12 02:44:13Z tpgww $ Copyright (C) 2003-2007 tooar Portions copyright (C) 1999 Michael Clark This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file plugins/e2p_names_clip.c @brief plugin to copy name and perhaps path of each selected item to the clipboard */ #include "emelfm2.h" #include #include "e2_plugins.h" /** @brief copy name and perhaps path of each selected item to the clipboard @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if action completed successfully, else FALSE */ static gboolean _e2p_copy_to_clipboard (gpointer from, E2_ActionRuntime *art) { GString *text; gchar *names; GdkModifierType state; if (gtk_get_current_event_state (&state) && (state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) { //get quoted list of selected-item-names, to facilitate their separation names = e2_utils_expand_macros ("%f", NULL); if (names == NULL) return FALSE; gboolean full = (state & GDK_SHIFT_MASK); gchar sep = (state & GDK_CONTROL_MASK) ? '\n' : ' '; gchar **split = g_strsplit (names, "\"", -1); gchar **tmp = split; text = g_string_new (""); while (*tmp != NULL) { if (**tmp == '\0') //empty string before 1st " and after last " { tmp++; continue; } else if (**tmp == ' ') text = g_string_append_c (text, sep); else { if (full) //E2_VFSTMPOK text = g_string_append (text, curr_view->dir); text = g_string_append (text, *tmp); } tmp++; } g_free (names); g_strfreev (split); } else { //get un-quoted list of selected item names names = e2_utils_expand_macros ("%%f", NULL); if (names == NULL) return FALSE; text = g_string_new (names); } GtkClipboard *clip = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); gtk_clipboard_set_text (clip, text->str, text->len); g_string_free (text, TRUE); return TRUE; } //aname must be confined to this module static gchar *aname; /** @brief plugin initialization function, called by main program @param p ptr to plugin data struct @return TRUE if the initialization succeeds, else FALSE */ gboolean init_plugin (Plugin *p) { #define ANAME "names_clip" aname = _("copy_name"); p->signature = ANAME VERSION; p->menu_name = _("Copy _names"); p->description = _("Copy path or name of each selected item to the clipboard"); p->icon = "plugin_clip_"E2IP".png"; //use icon file pathname if appropriate if (p->action == NULL) { //no need to free this gchar *action_name = g_strconcat (_A(5),".",aname,NULL); p->action = e2_plugins_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2p_copy_to_clipboard, NULL, FALSE, 0, NULL); return TRUE; } return FALSE; } /** @brief cleanup transient things for this plugin @param p pointer to data struct for the plugin @return TRUE if all cleanups were completed */ gboolean clean_plugin (Plugin *p) { gchar *action_name = g_strconcat (_A(5),".",aname,NULL); gboolean ret = e2_plugins_action_unregister (action_name); g_free (action_name); return ret; } emelfm2-0.4.1/plugins/e2p_du.c0000600000175000017500000001756210656541622015034 0ustar cairocairo/* $Id: e2p_du.c 585 2007-08-09 07:34:42Z tpgww $ Copyright (C) 2003-2007 tooar Portions copyright (C) 1999 Michael Clark This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file plugins/e2p_du.c @brief plugin for determining the disk usage of selected items */ #include "emelfm2.h" //#include #include #include #include #include "e2_plugins.h" // binary factor #define BFAC 1024.0 #define BFAC2 1048576.0 #define BFAC3 1073741824.0 typedef struct _E2_Du { guint64 total; guint64 files; guint64 dirs; gboolean hidden; } E2_Du; /** @brief update du counters This is a callback for the treewalk function Error message expects BGL to be open/off @param localpath absolute path of item reported by the walker, localised string @param statptr pointer to struct stat with data about @a localpath @param status code from the walker, indicating what type of report it is @param user_data pointer to data struct with total, files, dirs counters @return E2TW_CONTINUE always */ static E2_TwResult _e2p_du_twcb (VPATH *localpath, const struct stat *statptr, E2_TwStatus status, E2_Du *user_data) { guint64 thissize; const gchar *ptr; ptr = strrchr (VPSTR (localpath), G_DIR_SEPARATOR); if (ptr == NULL) ptr = VPCSTR(localpath); else ptr++; //skip the / if (ITEM_ISHIDDEN (ptr)) user_data->hidden = TRUE; switch (status) { case E2TW_F: //not directory or link case E2TW_SL: //symbolic link case E2TW_SLN: //symbolic link naming non-existing file user_data->files++; if (statptr->st_dev > 0) //CHECKME { thissize = statptr->st_blocks * statptr->st_blksize; if (thissize > statptr->st_size) thissize = statptr->st_size; user_data->total += thissize; } break; case E2TW_DM: //directory, not opened due to different file system (reported upstream) case E2TW_DL: //directory, not opened due to tree-depth limit (reported upstream) case E2TW_DNR: //unreadable directory (for which, error is reported upstream) case E2TW_DRR: //directory now readable case E2TW_D: //directory user_data->dirs++; if (statptr->st_dev > 0) //CHECKME { thissize = statptr->st_blocks * statptr->st_blksize; if (thissize > statptr->st_size) thissize = statptr->st_size; user_data->total += thissize; } break; // case E2TW_DP: //dir finished // case E2TW_NS: //un-stattable item (for which, error is reported upstream) default: break; } return E2TW_CONTINUE; } /** @brief thread function to iterate over active pane file list to get sizes @param starttab pointer to tab which was current when the thread was initiated @return NULL */ static gpointer _e2p_du_all (E2_OutputTabRuntime *starttab) { //FIXME with E2_ASYNC, curr_view etc is not necessarily relevant TABLOG (starttab) FileInfo *info; guint64 total, files, dirs; gboolean hashidden; GString *text; GList *base, *tmp; gchar *curr_local, *local_path; #ifdef E2_VFS VPATH ddata; #endif //all counters initialized to 0 E2_Du *cbdata = ALLOCATE0 (E2_Du); CHECKALLOCATEDWARN (cbdata, return NULL;) #ifdef E2_VFS ddata.spacedata = curr_view->spacedata; #endif curr_local = F_FILENAME_TO_LOCALE (curr_view->dir); base = e2_fileview_get_selected_local (curr_view); for (tmp = base; tmp != NULL; tmp = tmp->next) { info = tmp->data; // printd (DEBUG, "fn: %s", info->filename); local_path = e2_utils_strcat (curr_local, info->filename); //if (! #ifdef E2_VFS ddata.localpath = local_path; e2_fs_tw (&ddata, _e2p_du_twcb, cbdata, -1, E2TW_PHYS E2_ERR_NONE()); #else e2_fs_tw (local_path, _e2p_du_twcb, cbdata, -1, E2TW_PHYS E2_ERR_NONE()); #endif //) //{ //FIXME handle error //} g_free (local_path); } F_FREE (curr_local); total = cbdata->total; files = cbdata->files; dirs = cbdata->dirs; hashidden = cbdata->hidden; DEALLOCATE (E2_Du, cbdata); g_list_free (base); static gchar big[3] = { '1', ',', '\0' }; gchar *comma = nl_langinfo (THOUSEP); if (comma != NULL && *comma != '\0') big[1] = *comma; text = g_string_new(_("total size: ")); gint fwidth; if (total < BFAC) { gchar *b = _("bytes"); if (total < 1000) g_string_append_printf (text, "%"PRIu64" %s", total, b); else { total -= 1000; g_string_append_printf (text, "%s%03"PRIu64" %s", big, total, b); } } else if (total < BFAC2) { gchar *kb = _("kilobytes"); fwidth = (total < 10 * BFAC) ? 3 : 2; if ((total/BFAC) < 1000) g_string_append_printf(text, "%.*f %s", fwidth, (total / BFAC), kb); else { total -= (1000 * BFAC); g_string_append_printf (text, "%s%04.1f %s", big, (total / BFAC), kb); } } else if (total < BFAC3) { gchar *mb = _("Megabytes"); fwidth = (total < 10 * BFAC2) ? 3 : 1; if ((total/BFAC2) < 1000) g_string_append_printf(text, "%.*f %s", fwidth, (total / BFAC2), mb); else { total -= (1000 * BFAC2); g_string_append_printf (text, "%s%04.1f %s", big, (total / BFAC2), mb); } } else { gchar *gb = _("gigabytes"); fwidth = (total < 10 * BFAC3) ? 3 : 1; if ((total/BFAC3) < 1000) g_string_append_printf(text, "%.*f %s", fwidth, (total / BFAC3), gb); else { total -= (1000 * BFAC3); g_string_append_printf (text, "%s%04.1f %s", big, (total / BFAC3), gb); } } gchar *filetext = (files == 1) ? _("file") : _("files"); gchar *dirtext = (dirs == 1) ? _("directory") : _("directories"); g_string_append_printf (text, "\n%s %"PRIu64" %s %s %"PRIu64" %s", _("in"), files, filetext, _("and"), dirs, dirtext); if (hashidden && (files > 0 || dirs > 0)) g_string_append_printf (text, " %s\n", _("(one or more are hidden)")); else text = g_string_append_c (text, '\n'); gdk_threads_enter (); e2_output_print_same (text->str); gdk_threads_leave (); E2_OutputTabRuntime *tab = (starttab == curr_tab) ? &app.tab : starttab; gdk_threads_enter (); e2_output_print_end (tab, FALSE); gdk_threads_leave (); g_string_free (text, TRUE); return NULL; } /** @brief du plugin action This creates a thread to iterate over active pane selected items to check size @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2p_du (gpointer from, E2_ActionRuntime *art) { g_thread_create ((GThreadFunc)_e2p_du_all, curr_tab, FALSE, NULL); return TRUE; } //aname must be confined to this module static gchar *aname; /** @brief plugin initialization function, called by main program @param p ptr to plugin data struct @return TRUE if the initialization succeeds, else FALSE */ gboolean init_plugin (Plugin *p) { #define ANAME "du" aname = _("du"); p->signature = ANAME VERSION; p->menu_name = _("_Disk usage"); p->description = _("Calculate the disk usage of selected item(s)"); p->icon = "plugin_"ANAME"_"E2IP".png"; //icon file pathname if (p->action == NULL) { gchar *action_name = g_strconcat (_A(5),".",aname,NULL); p->action = e2_plugins_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2p_du, NULL, FALSE, 0, NULL); return TRUE; } return FALSE; } /** @brief cleanup transient things for this plugin @param p pointer to data struct for the plugin @return TRUE if all cleanups were completed */ gboolean clean_plugin (Plugin *p) { gchar *action_name = g_strconcat (_A(5),".",aname,NULL); gboolean ret = e2_plugins_action_unregister (action_name); g_free (action_name); return ret; } emelfm2-0.4.1/plugins/e2p_times.c0000600000175000017500000010054210713555213015527 0ustar cairocairo/* $Id: e2p_times.c 700 2007-11-05 08:55:39Z tpgww $ Copyright (C) 2006-2007 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file plugins/e2p_times.c @brief plugin for setting item times, and related functions This is NOT thread-safe */ #include "emelfm2.h" #include #include #include #include "e2_plugins.h" #include "e2_filelist.h" #include "e2_dialog.h" #include "e2_ownership_dialog.h" #include "e2_task.h" typedef struct _E2_TouchData { gboolean continued_after_problem; time_t mtime; time_t atime; time_t ctime; GList *dirdata; } E2_TouchData; typedef struct _E2_TimesDlgRuntime { GtkWidget *mcurrent; GtkWidget *acurrent; GtkWidget *ccurrent; GtkWidget *mdate_combo; GtkWidget *adate_combo; GtkWidget *cdate_combo; GtkWidget *mtime_combo; GtkWidget *atime_combo; GtkWidget *ctime_combo; GtkWidget *set_mtime_button; GtkWidget *set_atime_button; GtkWidget *set_ctime_button; GtkWidget *recurse_button; gboolean *recurse; gboolean permission; DialogButtons real_choice; //store for over-ride dialog choice E2_TouchData *data; } E2_TimesDlgRuntime; static GList *mdate_history = NULL; static GList *mtime_history = NULL; static GList *adate_history = NULL; static GList *atime_history = NULL; static GList *cdate_history = NULL; static GList *ctime_history = NULL; static gboolean _e2p_task_timesQ (E2_ActionTaskData *qed); /** @brief change times of @a path @param path localised string, absolute path of item to change @param sb pointer to struct stat for with info about the current item @param data pointer to replacement time data @return TRUE if the change was completed */ static gboolean _e2pt_touch1 (VPATH *path, const struct stat *statptr, E2_TouchData *data) { struct utimbuf tb; struct timeval systime1, systime2, strt, nd; struct timezone tz; //old or new mod time tb.modtime = (data->mtime == (time_t ) -1) ? statptr->st_mtime : data->mtime; //old or new access time tb.actime = (data->atime == (time_t ) -1) ? statptr->st_atime : data->atime; gboolean retval = TRUE; struct tm *tmptr; gboolean fixc = (data->ctime != (time_t) -1); if (fixc) { //store current sys time gettimeofday (&systime1, &tz); //set sys time to required ctime approx. time_t _now = time (NULL); strt.tv_sec = data->ctime; strt.tv_usec = 0; tmptr = localtime (&_now); if (tmptr->tm_isdst > 0) { //daylight savings is in effect strt.tv_sec -= 3600; //FIXME correct offset } else printd (DEBUG, "DST hack NOT done"); settimeofday (&strt, NULL); } if (fixc || tb.modtime != statptr->st_mtime || tb.actime != statptr->st_atime) { E2_ERR_DECLARE if (e2_fs_utime (path, &tb E2_ERR_PTR())) { #ifdef E2_VFSTMP //FIXME handle error #endif E2_ERR_CLEAR retval = FALSE; } } if (fixc) { //find elapsed time since sys time was reset gettimeofday (&nd, NULL); gint nsec; /* if (nd.tv_usec < strt.tv_usec) { nsec = (strt.tv_usec - nd.tv_usec) / 1000000 + 1; strt.tv_usec -= 1000000 * nsec; strt.tv_sec += nsec; } if (nd.tv_usec - strt.tv_usec > 1000000) { nsec = (strt.tv_usec - nd.tv_usec) / 1000000; strt.tv_usec += 1000000 * nsec; strt.tv_sec -= nsec; } */ systime2.tv_sec = systime1.tv_sec + nd.tv_sec - strt.tv_sec; systime2.tv_usec = systime1.tv_usec + nd.tv_usec - strt.tv_usec; if (systime2.tv_usec > 1000000) { nsec = systime2.tv_usec / 1000000 + 1; systime2.tv_usec -= 1000000 * nsec; systime2.tv_sec += nsec; } //set sys time = old sys time + elapsed time //no DST correction needed settimeofday (&systime2, &tz); } return retval; } /** @brief callback function for recursive directory touches Tree is being walked breadth-first, not physical. Dirs are made accessible and writable if not already so and it's permitted, dirs are added to a list to be processed after all the tree has been traversed, Other items are changed as requested (if possible) Error messages expect BGL off @param localpath absolute path of item to change, localised string @param statptr pointer to struct stat with info about @a localpath @param status code from the walker, indicating what type of report it is @param user_data pointer to user-specified data @return E2TW_CONTINUE on success, others as appropriate */ static E2_TwResult _e2_task_twcb_touch (VPATH *localpath, const struct stat *statptr, E2_TwStatus status, E2_TouchData *user_data) { E2_TwResult retval = E2TW_CONTINUE; //default error code = none E2_ERR_DECLARE switch (status) { mode_t mode, newmode; E2P_DirEnt *dirfix; GList *member; #ifdef E2_VFS VPATH ddata; #endif case E2TW_DP: //dir completion, touch, revert mode if needed, cleanup #ifdef E2_VFS ddata.spacedata = localpath->spacedata; #endif for (member = user_data->dirdata; member != NULL; member = member->next) { dirfix = member->data; if (dirfix != NULL) { if (g_str_equal (dirfix->path, localpath)) { #ifdef E2_VFS ddata.localpath = dirfix->path; if (!_e2pt_touch1 (&ddata, statptr, user_data)) #else if (!_e2pt_touch1 (dirfix->path, statptr, user_data)) #endif retval = E2TW_FIXME; #ifdef E2_VFS if (e2_fs_chmod (&ddata, dirfix->mode E2_ERR_PTR()) #else if (e2_fs_chmod (dirfix->path, dirfix->mode E2_ERR_PTR()) #endif && E2_ERR_ISNOT (ENOENT)) //CHECKME ctime effect { e2_fs_error_local (_("Cannot change times of %s"), localpath E2_ERR_MSGL()); retval = E2TW_FIXME; } g_free (dirfix->path); DEALLOCATE (E2P_DirEnt, dirfix); user_data->dirdata = g_list_delete_link (user_data->dirdata, member); break; } } // else //should never happen CHECKME ok when walking list ? // user_data->dirdata = g_list_delete_link (user_data->dirdata, member); } break; case E2TW_DRR: //directory now readable //CHECKME retval |= E2TW_DRKEEP; //no permission reversion in walker, as that stuffs ctime? case E2TW_D: //ensure dir is writable, if we can if (e2_fs_tw_adjust_dirmode (localpath, statptr, (W_OK | X_OK)) == 0) { //take a shot at doing the change, anyhow, probably fails _e2pt_touch1 (localpath, statptr, user_data); //FIXME warn user about failure retval |= E2TW_SKIPSUB; //don't try to do any descendant } else //dir can be processed { //add this dir to list of items to touch afterwards dirfix = ALLOCATE (E2P_DirEnt); CHECKALLOCATEDWARNT (dirfix, retval=E2TW_STOP;break;) dirfix->path = g_strdup (VPSTR (localpath)); dirfix->mode = statptr->st_mode & ALLPERMS; //want to restore the original value user_data->dirdata = g_list_prepend (user_data->dirdata, dirfix); } break; case E2TW_DM: //dir not opened (reported upstream) case E2TW_DL: //ditto case E2TW_DNR: //unreadable directory (for which, error is reported upstream) //touch for this will probably fail, but try anyhow mode = statptr->st_mode; //ensure dir is writable, if we can, don't need X permission newmode = e2_fs_tw_adjust_dirmode (localpath, statptr, W_OK); if (newmode == 0) { //take a shot at doing the change, anyhow, probably fails _e2pt_touch1 (localpath, statptr, user_data); //FIXME warn user about failure retval = E2TW_FIXME; } else //dir can be processed { if (!_e2pt_touch1 (localpath, statptr, user_data)) retval = E2TW_FIXME; if (newmode != mode) e2_fs_chmod (localpath, mode E2_ERR_NONE()); //CHECKME ctime effect } break; case E2TW_SL: case E2TW_SLN: case E2TW_F: if (!_e2pt_touch1 (localpath, statptr, user_data)) retval = E2TW_FIXME; break; // case E2TW_NS: //un-statable item (for which, error is reported upstream) default: retval = E2TW_STOP; break; } if (retval & E2TW_SKIPSUB) user_data->continued_after_problem = TRUE; if (retval & E2TW_FIXME) { user_data->continued_after_problem = TRUE; retval &= ~E2TW_FIXME; //continue after bad item } return retval; } /** @brief change time(s) of item @a path to values stored in @a statbuf If invoked on a non-dir, or a dir without recursion, it is processed here. If recursion is invoked on a dir, a ntfw funtion is invoked. By that, all nested dir access permissons will be set to include x, non-dir items will be have their new times set when they are 'reported'. Finally, here, dirs will be set to their original permissions (bottom-up order) at the end of the process Recursive touch works on the host filesystem only. Links are not affected, their target is processed. (OK ??) Error messages expect BGL open @param path localised string, absolute path of item to process @param statbuf ptr to data struct with times to set @param recurse TRUE to do recursive change (ignored if @a path is not a dir) @return TRUE if operation succeeds */ static gboolean _e2p_touch (VPATH *localpath, E2_TouchData *data, gboolean recurse) { struct stat statbuf; E2_ERR_DECLARE if (recurse) { //confirm whether path is a dir or not if (e2_fs_stat (localpath, &statbuf E2_ERR_PTR())) //look _through_ links { //abort if we can't find the item e2_fs_error_local (_("Cannot get current data for %s"), localpath E2_ERR_MSGL()); E2_ERR_CLEAR return FALSE; } if (S_ISDIR (statbuf.st_mode)) { //recursive touch //no processed dirs yet data->dirdata = NULL; gboolean retval = e2_fs_tw (localpath, _e2_task_twcb_touch, data, -1, //flags for fix-DNR, thru-links, this filesystem only, breadth-first E2TW_FIXDIR | E2TW_MOUNT E2_ERR_PTR()); if (data->dirdata != NULL) { //touch and revert dir permissions, LIFO (=bottom-up) order #ifdef E2_VFS VPATH ddata; ddata.spacedata = localpath->spacedata; #endif GList *member; for (member = data->dirdata; member != NULL; member = member->next) { E2P_DirEnt *dirfix = member->data; #ifdef E2_VFS ddata.localpath = dirfix->path; if (e2_fs_lstat (&ddata, &statbuf E2_ERR_NONE()) //no link pass-thru || !_e2pt_touch1 (&ddata, &statbuf, data)) #else if (e2_fs_lstat (dirfix->path, &statbuf E2_ERR_NONE()) //no link pass-thru || !_e2pt_touch1 (dirfix->path, &statbuf, data)) #endif retval = FALSE; #ifdef E2_VFS if (e2_fs_chmod (&ddata, dirfix->mode E2_ERR_PTR()) #else if (e2_fs_chmod (dirfix->path, dirfix->mode E2_ERR_PTR()) #endif && E2_ERR_ISNOT (ENOENT)) e2_fs_error_local (_("Cannot change permissions of %s"), localpath E2_ERR_MSGL()); E2_ERR_CLEAR g_free (dirfix->path); DEALLOCATE (E2P_DirEnt, dirfix); } g_list_free (data->dirdata); } // g_free (data); stacked, not heaped if (!retval) { //FIXME handle error E2_ERR_CLEAR } return retval; } else //not dir, handle without recurse recurse = FALSE; } if (!recurse) { //get current times if (e2_fs_lstat (localpath, &statbuf E2_ERR_PTR())) //no link pass-thru { e2_fs_error_local (_("Cannot get current times of %s"), localpath E2_ERR_MSGL()); E2_ERR_CLEAR return FALSE; } return (_e2pt_touch1 (localpath, &statbuf, data)); } return FALSE; //warning prevention only } /** @brief convert strings in @a date_combo and/or @a time_combo to corresponding time variable This is called only from within callback, BGL closed @param current label containing string form of item's current date-time @param date_combo widget with string for new date @param time_combo widget with string for new time @param newdt store for new timevalue (set to -1 if problem occurs) @return TRUE if new time_t is determined successfully */ static gboolean _e2p_times_parse_time (GtkWidget *current, GtkWidget *date_combo, GtkWidget *time_combo, time_t *newdt) { struct tm tm; //clear result structure memset (&tm, '\0', sizeof (tm)); const gchar *date = gtk_entry_get_text (GTK_ENTRY (GTK_BIN (date_combo)->child)); const gchar *time = gtk_entry_get_text (GTK_ENTRY (GTK_BIN (time_combo)->child)); const gchar *currdate = gtk_label_get_text (GTK_LABEL (current)); const gchar *currtime = strchr (currdate, ' ') + 1; //ascii scanning for space char const gchar *cp; gchar *newvalue; if (*date != '\0') { if (*time != '\0') newvalue = g_strdup_printf ("%s %s", date, time); else newvalue = g_strdup_printf ("%s %s", date, currtime); } else if (*time != '\0') { gchar *freeme = g_strndup (currdate, currtime - currdate - 1); newvalue = g_strdup_printf ("%s %s", freeme, time); g_free (freeme); } else { newvalue = g_strdup (currdate); } cp = strptime (newvalue, "%x %X", &tm); if (cp != NULL && *cp == '\0') //all of the string was processed { tm.tm_isdst = -1; //don't correct for daylight saving *newdt = mktime (&tm); //may be set to -1 } else *newdt = (time_t) -1; if (*newdt == (time_t) -1) { gchar *msg = g_strdup_printf (_("Cannot interpret date-time %s"), newvalue); e2_output_print_error (msg, TRUE); } g_free (newvalue); return (*newdt != (time_t) -1); } /** @brief handle ok or apply-to-all button press Converts all entered date-time strings to calendar values If there's a problem, it may set a flag to produce a cancel response for the dialog as a whole @param widget UNUSED the clicked button widget @param rt pointer to dialog data struct @return */ static void _e2p_times_ok_cb (GtkWidget *widget, E2_TimesDlgRuntime *rt) { if (rt->permission) { gboolean success = TRUE; //default condition-flags rt->real_choice = OK; // GtkWidget *entry; const gchar *entrytext; //store any valid new dates/times if (GTK_TOGGLE_BUTTON (rt->set_mtime_button)->active) { success = _e2p_times_parse_time (rt->mcurrent, rt->mdate_combo, rt->mtime_combo, &rt->data->mtime); if (success) { /*as dialog is destroyed each iteration, no point in deferring backup until later //update combo history entry = GTK_BIN (rt->mdate_combo)->child; entrytext = gtk_entry_get_text (GTK_ENTRY (entry)); if (*entrytext != '\0') e2_combobox_activated_cb (entry, NULL); entry = GTK_BIN (rt->mtime_combo)->child; entrytext = gtk_entry_get_text (GTK_ENTRY (entry)); if (*entrytext != '\0') e2_combobox_activated_cb (entry, NULL); */ entrytext = gtk_entry_get_text (GTK_ENTRY (GTK_BIN (rt->mdate_combo)->child)); if (*entrytext != '\0') e2_list_update_history (entrytext, &mdate_history, NULL, 0, FALSE); entrytext = gtk_entry_get_text (GTK_ENTRY (GTK_BIN (rt->mtime_combo)->child)); if (*entrytext != '\0') e2_list_update_history (entrytext, &mtime_history, NULL, 0, FALSE); } else rt->real_choice = NO; //signal parse-failure } else rt->data->mtime = (time_t) -1; if (success && GTK_TOGGLE_BUTTON (rt->set_atime_button)->active) { success = _e2p_times_parse_time (rt->acurrent, rt->adate_combo, rt->atime_combo, &rt->data->atime); if (success) { /* //update combo history entry = GTK_BIN (rt->adate_combo)->child; entrytext = gtk_entry_get_text (GTK_ENTRY (entry)); if (*entrytext != '\0') e2_combobox_activated_cb (entry, NULL); entry = GTK_BIN (rt->atime_combo)->child; entrytext = gtk_entry_get_text (GTK_ENTRY (entry)); if (*entrytext != '\0') e2_combobox_activated_cb (entry, NULL); */ entrytext = gtk_entry_get_text (GTK_ENTRY (GTK_BIN (rt->adate_combo)->child)); if (*entrytext != '\0') e2_list_update_history (entrytext, &adate_history, NULL, 0, FALSE); entrytext = gtk_entry_get_text (GTK_ENTRY (GTK_BIN (rt->atime_combo)->child)); if (*entrytext != '\0') e2_list_update_history (entrytext, &atime_history, NULL, 0, FALSE); } else rt->real_choice = NO; //signal parse-failure } else rt->data->atime = (time_t) -1; if (success && GTK_TOGGLE_BUTTON (rt->set_ctime_button)->active) { success = _e2p_times_parse_time (rt->ccurrent, rt->cdate_combo, rt->ctime_combo, &rt->data->ctime); if (success) { //warn about system clock changes gchar *prompt = _("Changing 'ctime' requires temporary changes to the system clock." " That is normally unwise, as typically, other things rely on" " system time. Click 'ok' to proceed."); DialogButtons choice = e2_dialog_warning (prompt); if (choice != OK) { success = FALSE; rt->data->ctime = (time_t) -1; } } else rt->real_choice = NO; //signal parse-failure if (success) { /* //update combo history entry = GTK_BIN (rt->cdate_combo)->child; entrytext = gtk_entry_get_text (GTK_ENTRY (entry)); if (*entrytext != '\0') e2_combobox_activated_cb (entry, NULL); entry = GTK_BIN (rt->ctime_combo)->child; entrytext = gtk_entry_get_text (GTK_ENTRY (entry)); if (*entrytext != '\0') e2_combobox_activated_cb (entry, NULL); */ entrytext = gtk_entry_get_text (GTK_ENTRY (GTK_BIN (rt->cdate_combo)->child)); if (*entrytext != '\0') e2_list_update_history (entrytext, &cdate_history, NULL, 0, FALSE); entrytext = gtk_entry_get_text (GTK_ENTRY (GTK_BIN (rt->ctime_combo)->child)); if (*entrytext != '\0') e2_list_update_history (entrytext, &ctime_history, NULL, 0, FALSE); } } else rt->data->ctime = (time_t) -1; if (success) { //anything valid to change ? if (rt->data->mtime == (time_t) -1 && rt->data->atime == (time_t) -1 && rt->data->ctime == (time_t) -1 ) rt->real_choice = CANCEL; else { rt->real_choice = OK; //the entered choice stands confirmed if (rt->recurse_button != NULL) *rt->recurse = GTK_TOGGLE_BUTTON (rt->recurse_button)->active; } // return; } else if (rt->real_choice != NO) //error message already printed by the date interpeter rt->real_choice = CANCEL; } else { //no permisson, the ok click was pointless ... rt->real_choice = CANCEL; } } /** @brief dialog response callback - make response available to main loop In general, the response of the clicked button is made available. However if the ok/yesall cb generates an error, the return code set there will be used instead @param dialog the dialog from which the response was generated @param response the response returned by the dialog @param dialog_result pointer to store for @a response or error cancel @return */ /*static void _e2p_times_response_cb (GtkDialog *dialog, gint response, DialogButtons *dialog_result) { // printd (DEBUG, "times dialog response cb, response is %d", response); e2_dialog_response_decode_cb (dialog, response, dialog_result); } */ /** @brief create comboboxentry widget for the dialog @param history history list for the widget (static for the session) @return the widget */ static GtkWidget *_e2p_times_create_combo (GList *history) { GtkWidget *combo = e2_combobox_get (NULL, NULL, &history, E2_COMBOBOX_HAS_ENTRY | E2_COMBOBOX_NO_AUTO_HISTORY | E2_COMBOBOX_FOCUS_ON_CHANGE | E2_COMBOBOX_CYCLE_HISTORY); //show the last-used value, if any if (e2_combobox_has_history (GTK_COMBO_BOX (combo))) e2_combobox_set_active (combo, 0); // gtk_entry_set_activates_default (GTK_ENTRY (GTK_BIN (combo)->child), TRUE); // gtk_widget_set_size_request (combo, 40, -1); return combo; } /** @brief create and run time-change dialog @param local_dir path of dir containing item to process, localised string with trailer @param spacedata pointer to space data for active pane @param info pointer to selection data struct for the item being processed @param recurse_ret pointer to store for user's choice for a recursive change @param statbuf pointer to filesystem data struct for the item being processed @param multi TRUE if the change is part of a multi-item selection @return enumerator corresponing to the clicked dialog button */ static DialogButtons _e2p_times_dialog_run (gchar *local_dir, #ifdef E2_VFS PlaceInfo *spacedata, #endif E2_SelectedItemInfo *info, gboolean *recurse_ret, E2_TouchData *data, gboolean multi) { DialogButtons choice; GtkWidget *times_dialog; GtkWidget *dialog_vbox, *sub_vbox; GtkWidget *hbox; GtkWidget *table; struct stat statbuf; struct tm *tm_ptr; gchar date_string[32]; E2_TimesDlgRuntime rt; gchar *local = e2_utils_strcat (local_dir, info->filename); #ifdef E2_VFS VPATH tdata = { local, spacedata }; if (e2_fs_lstat (&tdata, &statbuf E2_ERR_NONE())) #else if (e2_fs_lstat (local, &statbuf E2_ERR_NONE())) #endif { g_free (local); return CANCEL; } E2_ERR_DECLARE rt.permission = #ifdef E2_VFS e2_fs_check_write_permission (&tdata E2_ERR_PTR()); #else e2_fs_check_write_permission (local E2_ERR_PTR()); #endif //FIXME handle error gboolean thisis_dir = #ifdef E2_VFS e2_fs_is_dir3 (&tdata E2_ERR_PTR()); #else e2_fs_is_dir3 (local E2_ERR_PTR()); #endif //FIXME handle error g_free (local); //copy pointers rt.data = data; rt.recurse = recurse_ret; times_dialog = e2_dialog_create (NULL, NULL, _("times"), // _e2p_times_response_cb, &choice); e2_dialog_response_decode_cb, &choice); dialog_vbox = GTK_DIALOG (times_dialog)->vbox; gtk_container_set_border_width (GTK_CONTAINER (dialog_vbox), E2_PADDING); hbox = gtk_hbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (dialog_vbox), hbox, TRUE, TRUE, E2_PADDING); //top, bottom padding gchar *labeltype = (thisis_dir) ? _("Directory name") : _("Filename") ; gchar *utf = F_DISPLAYNAME_FROM_LOCALE (info->filename); gchar *label_text = g_strdup_printf ("%s: %s", labeltype, utf); e2_widget_add_mid_label (hbox, label_text, 0, TRUE, E2_PADDING); //L, R padding gtk_widget_show (hbox); F_FREE (utf); g_free (label_text); sub_vbox = gtk_vbox_new (FALSE, E2_PADDING); gtk_box_pack_start (GTK_BOX (dialog_vbox), sub_vbox, TRUE, TRUE, E2_PADDING_LARGE); gtk_widget_show (sub_vbox); gint rows = (thisis_dir) ? 5 : 4; table = e2_widget_add_table (sub_vbox, rows, 5, FALSE, TRUE, E2_PADDING); //4 rows, 5 cols, !homogen, expand gtk_table_set_col_spacings (GTK_TABLE (table), E2_PADDING); e2_widget_add_mid_label_to_table (table, _("Current values"), 0.5,1,2,0,1); e2_widget_add_mid_label_to_table (table, _("New date"), 0.5, 2,3,0,1); e2_widget_add_mid_label_to_table (table, _("New time"), 0.5, 3,4,0,1); e2_widget_add_mid_label_to_table (table, _("Accessed"), 0, 0,1,1,2); e2_widget_add_mid_label_to_table (table, _("Content Modified"), 0,0,1,2,3); e2_widget_add_mid_label_to_table (table, _("Inode Changed"), 0, 0,1,3,4); tm_ptr = localtime (&statbuf.st_atime); strftime (date_string, sizeof (date_string), "%x %X", tm_ptr); utf = e2_utf8_from_locale (date_string); rt.acurrent = e2_widget_add_mid_label_to_table (table, utf, 0,1,2,1,2); g_free (utf); tm_ptr = localtime (&statbuf.st_mtime); strftime (date_string, sizeof (date_string), "%x %X", tm_ptr); utf = e2_utf8_from_locale (date_string); rt.mcurrent = e2_widget_add_mid_label_to_table (table, utf, 0,1,2,2,3); g_free (utf); tm_ptr = localtime (&statbuf.st_ctime); strftime (date_string, sizeof (date_string), "%x %X", tm_ptr); utf = e2_utf8_from_locale (date_string); rt.ccurrent = e2_widget_add_mid_label_to_table (table, utf, 0,1,2,3,4); g_free (utf); rt.adate_combo = _e2p_times_create_combo (adate_history); gtk_table_attach_defaults (GTK_TABLE (table), rt.adate_combo, 2,3,1,2); rt.mdate_combo = _e2p_times_create_combo (mdate_history); gtk_table_attach_defaults (GTK_TABLE (table), rt.mdate_combo, 2,3,2,3); rt.cdate_combo = _e2p_times_create_combo (cdate_history); gtk_table_attach_defaults (GTK_TABLE (table), rt.cdate_combo, 2,3,3,4); rt.atime_combo = _e2p_times_create_combo (atime_history); gtk_table_attach_defaults (GTK_TABLE (table), rt.atime_combo, 3,4,1,2); rt.mtime_combo = _e2p_times_create_combo (mtime_history); gtk_table_attach_defaults (GTK_TABLE (table), rt.mtime_combo, 3,4,2,3); rt.ctime_combo = _e2p_times_create_combo (ctime_history); gtk_table_attach_defaults (GTK_TABLE (table), rt.ctime_combo, 3,4,3,4); rt.set_atime_button = e2_button_add_toggle_to_table (table, _("_Set"), FALSE, NULL, NULL, 4,5,1,2);//gint left, gint right, gint top, gint bottom rt.set_mtime_button = e2_button_add_toggle_to_table (table, _("_Set"), FALSE, NULL, NULL, 4,5,2,3 ); rt.set_ctime_button = e2_button_add_toggle_to_table (table, _("_Set"), FALSE, NULL, NULL, 4,5,3,4); if (thisis_dir) //no longer do this for 'multi' rt.recurse_button = e2_button_add_toggle_to_table (table, _("R_ecurse:"), FALSE, NULL, NULL, 4,5,4,5 ); else rt.recurse_button = NULL; if (!rt.permission) //no right to change item times { gtk_widget_set_sensitive (rt.mdate_combo, FALSE); gtk_widget_set_sensitive (rt.adate_combo, FALSE); gtk_widget_set_sensitive (rt.cdate_combo, FALSE); gtk_widget_set_sensitive (rt.mtime_combo, FALSE); gtk_widget_set_sensitive (rt.atime_combo, FALSE); gtk_widget_set_sensitive (rt.ctime_combo, FALSE); gtk_widget_set_sensitive (rt.set_mtime_button, FALSE); gtk_widget_set_sensitive (rt.set_atime_button, FALSE); gtk_widget_set_sensitive (rt.set_ctime_button, FALSE); gtk_widget_set_sensitive (rt.recurse_button, FALSE); e2_ownership_dialog_warn (dialog_vbox); } else if (getuid () != 0) { //only root can alter system time, which is needed to change ctime gtk_widget_set_sensitive (rt.ctime_combo, FALSE); gtk_widget_set_sensitive (rt.cdate_combo, FALSE); gtk_widget_set_sensitive (rt.set_ctime_button, FALSE); } // add buttons in the order that they will appear GtkWidget *btn; if (multi) { e2_dialog_set_negative_response (times_dialog, E2_RESPONSE_NOTOALL); e2_dialog_add_defined_button (times_dialog, &E2_BUTTON_NOTOALL); btn = e2_dialog_add_button_custom (times_dialog, FALSE, &E2_BUTTON_APPLYTOALL, NULL, _e2p_times_ok_cb, &rt); if (!rt.permission) gtk_widget_set_sensitive (btn, FALSE); } if (rt.permission || multi) { btn = e2_dialog_add_defined_button (times_dialog, &E2_BUTTON_CANCEL); if (!rt.permission) gtk_widget_set_sensitive (btn, FALSE); } e2_dialog_add_button_custom (times_dialog, TRUE, &E2_BUTTON_OK, NULL, _e2p_times_ok_cb, &rt); e2_dialog_setup (times_dialog, app.main_window); gdk_threads_enter (); do { e2_dialog_run (times_dialog, NULL, 0); gtk_main (); } while (rt.real_choice == NO); //after date/time parse error, keep trying /*as dialog is destroyed each iteration, no gain in deferring this from the response callback //backup combo histories before dialog destruction e2_combobox_save_history (rt.mdate_combo, &mdate_history); e2_combobox_save_history (rt.mtime_combo, &mtime_history); e2_combobox_save_history (rt.adate_combo, &adate_history); e2_combobox_save_history (rt.atime_combo, &atime_history); e2_combobox_save_history (rt.cdate_combo, &cdate_history); e2_combobox_save_history (rt.ctime_combo, &ctime_history); */ gtk_widget_destroy (times_dialog); gdk_threads_leave (); if (rt.real_choice == CANCEL) choice = CANCEL; return choice; } /** @brief show/change time(s) of selected items in active pane The actual operation is performed by a separate back-end function @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if action completed successfully, else FALSE */ static gboolean _e2p_task_times (gpointer from, E2_ActionRuntime *art) { return (e2_task_enqueue_task (E2_TASK_TIMESET, art, from, _e2p_task_timesQ, e2_task_refresh_lists)); } static gboolean _e2p_task_timesQ (E2_ActionTaskData *qed) { //printd (DEBUG, "task: times"); GPtrArray *names = qed->names; gchar *curr_local = qed->currdir; GString *path = g_string_sized_new (PATH_MAX+NAME_MAX); guint count; E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata; gboolean multisrc = names->len > 1; gboolean all = FALSE; E2_TouchData data; #ifdef E2_VFS VPATH sdata; sdata.spacedata = qed->currspace; #endif /* out-of-loop setup = FIXME GtkWidget *dialog; dialog = e2_permissions_dialog_setup (info, &recurse); e2_dialog_add_buttons_simple (dialog, buttons ..., NULL); */ // prepare_dialog(); #ifdef E2_REFRESH_DEBUG printd (DEBUG, "disable refresh, times task"); #endif e2_filelist_disable_refresh (); e2_task_advise (); for (count=0; count < names->len; count++, iterator++) { DialogButtons choice; gboolean permission; gboolean recurse; //".." entries filtered when names compiled g_string_printf (path, "%s%s", curr_local, (*iterator)->filename); //separator comes with dir #ifdef E2_VFS sdata.localpath = path->str; #endif if (all) { //check if we have permission to change this item //NB this tests _all_ w permissions, touch not so sophisticated ... #ifdef E2_VFS permission = e2_fs_check_write_permission (&sdata E2_ERR_NONE()); #else permission = e2_fs_check_write_permission (path->str E2_ERR_NONE()); #endif if (!permission) { #ifdef E2_VFS sdata.localpath = (*iterator)->filename; #endif e2_fs_error_simple ( _("You do not have authority to change time(s) for %s"), #ifdef E2_VFS &sdata); #else (*iterator)->filename); #endif } choice = (permission) ? OK : CANCEL; } else { recurse = FALSE; data.continued_after_problem = FALSE; data.mtime = (time_t) -1; data.atime = (time_t) -1; data.ctime = (time_t) -1; data.dirdata = NULL; #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, times dialog"); #endif e2_filelist_enable_refresh (); //allow updates while we wait *qed->status = E2_TASK_PAUSED; choice = _e2p_times_dialog_run (curr_local, #ifdef E2_VFS qed->currspace, #endif *iterator, &recurse, &data, multisrc); *qed->status = E2_TASK_RUNNING; #ifdef E2_REFRESH_DEBUG printd (DEBUG, "disable refresh, permissions dialog"); #endif e2_filelist_disable_refresh (); } switch (choice) { case YES_TO_ALL: all = TRUE; choice = OK; case OK: #ifdef E2_INCLIST # ifdef E2_VFS if (_e2p_touch (&sdata, &data, recurse)) # else if (_e2p_touch (path->str, &data, recurse)) # endif { //FIXME update line in treeview } #else # ifdef E2_FAM # ifdef E2_VFS _e2p_touch (&sdata, &data, recurse); # else _e2p_touch (path->str, &data, recurse); # endif # else # ifdef E2_VFS if (_e2p_touch (&sdata, &data, recurse)) { sdata.localpath = curr_local; //make the file-list refresher notice successful change e2_fs_utime (&sdata, NULL E2_ERR_NONE()); } # else if (_e2p_touch (path->str, &data, recurse)) //make the file-list refresher notice successful change e2_fs_utime (curr_local, NULL E2_ERR_NONE()); # endif # endif #endif case CANCEL: break; default: choice = NO_TO_ALL; // break flag; break; } if (choice == NO_TO_ALL) break; } g_string_free (path, TRUE); F_FREE (curr_local); //FIXME E2_INCLIST - just do other view if it's same e2_window_clear_status_message (); #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, permissions task"); #endif e2_filelist_enable_refresh (); return TRUE; } //aname must be confined to this module static gchar *aname; /** @brief plugin initialization function, called by main program @param p ptr to plugin data struct @return TRUE if the initialization succeeds, else FALSE */ gboolean init_plugin (Plugin *p) { #define ANAME "times" aname = _("timeset"); p->signature = ANAME VERSION; p->menu_name = _("Change _times.."); p->description = _("Change any of the time properties of selected items"); p->icon = "plugin_"ANAME"_"E2IP".png"; //use icon file pathname if appropriate if (p->action == NULL) { //no need to free this gchar *action_name = g_strconcat (_A(5),".",aname,NULL); p->action = e2_plugins_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2p_task_times, NULL, FALSE, 0, NULL); return TRUE; } return FALSE; } /** @brief cleanup transient things for this plugin @param p pointer to data struct for the plugin @return TRUE if all cleanups were completed */ gboolean clean_plugin (Plugin *p) { gchar *action_name = g_strconcat (_A(5),".",aname,NULL); gboolean ret = e2_plugins_action_unregister (action_name); g_free (action_name); if (ret) { if (mdate_history != NULL) e2_list_free_with_data (&mdate_history); if (mtime_history != NULL) e2_list_free_with_data (&mtime_history); if (adate_history != NULL) e2_list_free_with_data (&adate_history); if (atime_history != NULL) e2_list_free_with_data (&atime_history); if (cdate_history != NULL) e2_list_free_with_data (&cdate_history); if (ctime_history != NULL) e2_list_free_with_data (&ctime_history); } return ret; } emelfm2-0.4.1/README0000777000175000017500000000000011015120161014470 2docs/READMEustar cairocairoemelfm2-0.4.1/icons/0000700000175000017500000000000011015120161013104 5ustar cairocairoemelfm2-0.4.1/icons/plugin_clone_48.png0000600000175000017500000000433210521644703016625 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org<lIDATh[lTߜ9{@ט^c 5!UR*UF*T%Q)$Mj46PGt㯘o/!jA=zd93+N^gi+=C8*3yH!j<J+ ~2oG\S1H|FxWפ吹|ݣm΀> xSQ焺Uza|1cٵ 2`]ൃГx10;w 2 6`i4tۨ]YF4Eh(8v tT2m1BнP#zg 6<>K~F`` )^e̴5K;}$# -0T1}`[[L'h Z1bRIkMZ2UFkFKoO{J1PǞ uz5\(i;ׂf,DDӲ~k}rgp,kÊ)Vp2fah؎e)؎+H]rMGIB̏UhM@X LS`J? Ũ;,Xr`;vl[!3 (Q#׽UUߎcƁh wW UD0@x)͸xUZït]u ל`hnb@Jס.S!qx^"%e"} =P w3}D{EX-L;) $!)az"-g_83nUZD3ѣ!+b+6KVHI%G'< WZ^Fqg$C.C;~뙝Zjx6V0a 1bZ-tc~L0oahӠ ڝm`xC{;[[],L(k5K΀տN"4h$GDD3OYĀ`@%Jam*zj{55C& { ;l4"VEbI~qdǟiO$wAwA%°7i'ǪG"iRޣD4Z5%g@" vBb˶hDz|&gcw[o;Y' 5N|A־W#?LkNrПI8d-)ÿM\f~N^n޼'v1?We~\ 0|W$d>5@􂨬<"zQ%$J6*@'H?К74HoUUouz~(+?@xb%#觛=m9@uuvJ9CN18d"??gNgZis(k?x6 aw]I%kiдN,V %E9`sO}e*{ 1AxC/O"T2l)ˀg) ^9}wĒ ]Mt"pi'wvܼqJ$ I H0ܢ / Ò/l~X{\+&\H(F׵#{I;˓ ṉ+~UGh܁5fr,XUGe5v}-I)8ֽI\|>VK} +E8)gsx aX:U'1c  _ylu4d2#-A񭂄\sc?)WVx(@$n2u[k:72H dw-qB̸HQp AU`/YH(ah[hX&Y4%0D] ܲ[T߾t{X%0b/\s`Gc,@t'ٮe-vM GYHQ4yFAҡND$H2=d\"@)6% dž8$Gbmmò, 4at]i4}Q</ M,0:3gu@[:kI|"]M^ /y~rwջzZKhjsDo~455'{" 칡Ngٴ$(2)v[{o52o‖e0aEPk Ooٵ &Tz5II㱟i{jbR"Zưѵ>oQSڎ/=^0 Jl4BD3#'>&R-s EeFFADR)l7gM2@Ep%\Klm*Cރ3!`Jow.ز'̬P)"*J FF: SPq^9 co[&dZG@@`Dݔ"L0s>I|D?!/gw GO悩ҠL\ 61=Tvz[+eܚ&k?T;;" \Æ+[F. pN\U:&*P̕` دS>[RLلIENDB`emelfm2-0.4.1/icons/emelfm2_24.png0000600000175000017500000000330410521644703015466 0ustar cairocairoPNG  IHDRw=sBIT|dtEXtSoftwarewww.inkscape.org<VIDATHml[gII\qj;a&iJ eiRhM !Z Ċƴ-hHEۀ&Uޚim7q;i&H""|uXh:N,xT] :+]e[vT9*Wgn[8q,ځGXfzƖG)a.xlUQxD 'o }gkc.زln-³5MU "%X68Zx%-˳ Nj \ׅY(Ouki;O=u(ր`I01qbIGmC-m64Q(\v4؀gN ׁߜSTUƭh)Lj!ԲC!*N@(yb#h0Zx\6wGbp.\tOf7x߳\ylqhX^X`fZd-e%L@c'᮫EQRK˕~sSdH3>_Ϊ*f>Z$ʢQr55оz_=6X([}07AEy7^EDŗDΝ{[xc:ncj,e_0t ${:5MyXxQ)R7ᩯ%ܶ 7I$M*JSx]QC-F-͙ɴ77{Fg- YLS[,2wXIH,+ܺ#P GzJaw43fXxo g~שTSDLAKW͉҅h~S}ҙ@uzG3 RgX""z(d5D 5IENDB`emelfm2-0.4.1/icons/bookmark_48.png0000600000175000017500000000677510521644703015771 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org< IDAThypwǿ}uvm' $ ͒e'ـ1$I&SS3ٝJ2lmjHaT,L& }eƲlٖ,66UuUwuMpmhU(:TU/g3 vYIJY VT+*Ljoݠ$JU&9-.ձXMﺻZ7e,&2!I*HLg#(> z=zWXGTEYsjԹw 兰3H*lT 5CQ{+K<ޘ.#$sʒDoS%kTpem`BNE8"AIPԛ(9 k |5n| qܣ(ηeXK15su%0"W "Q CD * @EQ  Yŏ~UFBAOx{Iyqђy\UM,͇AC"<) m>[;`#YF/*&gvh.iz_y]F Q`Wc(G[mmGm4 A::c,E>_X{[4MIJ "]j^ G9QVbXl>z#x+|ww @n>:=/ CeCSKf G֜rGb01( Ep9(BN$IBq!vPS7%M-^oHMYF\3AOqܕ133(+܁0,;gG''BAOpmia6Fˆa2TZ܎z(.2ﯳ{x{i(IOh;srGnԸ2ae? ۷圦itrܷ^iv>Wu%cC¤dY<}SU5"(sZ \$O틟Lه+^x} 1.x5++mxp($7ԛ= 8f4D&hMoSZ0 oͰY@rDŽH_{/[? f&r6 sYcર!ԛ@w(1A;zm$I8 PZDIY9Y7 bCODu(0i<aHTUfJIuNv~:8+(tQ^VvR0sҶww M:nK,Z(gqt)1]! +,]Oq hbR|ء#ޞPQw(YQgn_UpÑow׎gG/ٱKPTEd/Ĵ$)%W7p6y'owhCH$rh]GD5J-&Q"̔#"DYvE@L%ziDEr-CA,rh]@(蹦'H%{ F 2SmQx^vks7n=]o CGuEc3d @L&8^`0ЕepL17YU6pf3Κ tɒe]1EQ]qN IVV3 {^6ƲLa>P~іOOگ: \n`"rQYY,,8V;=݈P(N4q/u1)>urW%7FL*hEÌW&dd`aa!I<~_՝P/6Ew.MX8MSu͓ZXg7h:23Ea)6+̢ Nx)rB]\CwtYtxB1IF~(Lzi1pˮ9w{xŪڴ'M5L\m"*ĠJ  CuGD9MSĂWQNǂXP4fpDԳj` K(ϝ:RY5[?`NV_@Ì .' h5`BSQ^;φīODAff@Q,HE1 3. quH&  zx62 3F?(27 KfK9l4p 4i]DM EU 6&#CvzG h$łT#<,//|իR!ƕyՙРi@Ci0-p@Ec+i |p9xc!(f0RLEgήvf-5 䓯|4.\hEQ-oK)"̀Q h1(gC êGAUQ)q_?M{ȎZ :4ncSU+A i$E#P lmA0FImTx!ow8pďU78 vvkͪVsoޏ?6˰S8 O>w۲PУgLOBc# zʾc޾8#8}mf[7/Vtb=ȄFMᴑ N~%?GA^j]mHF<%VQǬ_ȹU.'CAOTuI(*zz tz3u ̼>u~QıkX*`-h9FA3x=+JQG޴Pг_?`45S tc·` f 0!?')e)BC!E ୌԠ#i?(t#K &3tzCZP4T/<)8dYbj4Kz = I hI*}C2T ʽx#id!@İ2R~8'ƃi6  iڸA![`(en}+>{IENDB`emelfm2-0.4.1/icons/edit_48.png0000600000175000017500000000535510521644703015102 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org< IDAThՙ{pT?{汛nț!DZp B5 RרvDDZ>f`| C%# ;$$lHvG!0m3ٻ{~;sKH).\h޲elO ŲlݺuhWu0vحmmm/U伡b]]݁0*ӦMKQUŋ{._G˗/W r!z bdD3?DI`RʏԞl|1RAM2>&!5=؎!*`(H){$] zӅr'Rb  vRYD dp!!;@d#Qw$ت{IT`Ȯvu/=ʗ%1C&R B3e`M>h*+X"(B9;!󚝽b ;rǥ~;>UwOʋgdSKlv7fw,Y&gΜ)[z|y畾Vlmkm&-sΕ{`@ A뺔RJ-*O)Ј\Ă{9wbOggaAnً!K)S & `Ġ/jD P= 1q<3ytdO=>fs#כ.dHt;0d8tn3e;Ǝ;z1H%`Ɏ\ȜFM3b,YK.i.͛ǤIl=w{[:@p>0[ma5[q:qkN@J;]Ȼ!fH@Vuo tN bRjIP9Mn/s-Vhi#tLwwȀN A" ,6$v!l}8^C%d$uPe `F`cc0hMCUQ:1FCddy:eZ ;,7^OeQf@Հ*4"b"HHu!E@ ] ]8_W7tCW m*¸1Ig QteBؐB&d x|'Esk5G'!d^.:P]H ӅB0hÇtIH 2L9"B ҪDx/tA5y@tmHJK讇-jFSLZzgcQ!w搒}" IH3zwۨc)"x]sϽ]ׯ;wĚc$&PT&S<={6%"P8E ߮.Us{;a6| x0UwSN Z Vk%+Mk|ᄡGc uIPq=:s5$i40[F[[ɘ{OII SLAQ_bLKH 6)Ddӓq 1z۶?fֳ35g~}lO,xc~$M0lذw!)D7 СCY,ysZ k@T҉z KؾLGW3jwYj+rٲezI !MJk,{y/&fJOJ\\YYM(ǚW/?i>ނn&K%a^-Y[_<ڵ۷ӔHҏ#{ )7 .j=IϠp@C8<'ezgst)O,]f5&N8墼R*.W`o#*$1ĪNW[|:Ls{*?guߑcNZ I)%qї7 @bb"#GRGBrB& tx1bҸgl^qSxEJg ƿ}3; Aee%W^{nmE Ds,%g64\W2hd'O2} ?Ðpw폓o䒒HJJ$2uTعs'J7r o YIfr:iFOҞ=4TTUP6$]nIee%=&L6HOQksQx&c77rׯHU|# mov;GppYQ,:l|_Ki1d{e_7s&1 8C@9axzhE0Y44wٌ,۷Ns'5cB7p(oޅrmTr&<[uVq"XS]r |#lzN؏|_|eF[;)(~3EqTā$־'t=~HG3lnE_JYOln˷4^B^[[H߉łgF!քwsHU_k/;gC2-cDڞIENDB`emelfm2-0.4.1/icons/delete_48.png0000600000175000017500000000375110521644703015415 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org<{IDAThZkl\朳/@*@J*%U}FDJJmU))?*jWrbň"H •8 8{ٻw׻L{w^/)#>og3Lf*@t\nDd=e& gyDXh 8q-3px8|63W%e@׋9w?/1lRG8vDৈ.d]`&k'Dya"z{y(58!YWz.0jé߮|Y6Z[]`β! ,7inWpxWA`֗X B:pxx0&"BR\)UUU,m?`̯=|,B:v=kZkض N'.}XwÕ-r[ypW'B!tZmۜN8sgJ"篈m`bsf.g-+#q|̜8z,/\vol>f@%8${|9r X'ʀ=PSdž~u+WWcX2Z_.۾L:@h1P@R e\4`YVf"УZZ@MM׮9r n`tZ O%D 88ys\5ۋ4:j&%'N8Zȍp$Ԃ^yۗBRm[GqR薲d SSwT5)0$RTxH!%Ka9KI*U RfiȽ{.oH ATw7ԓ/G"P]uShhpx\H'~Wyum!$M 1#{7[ 7`n -3@tr^ 锦C>w3`ӦMg֮+K0ČRx̶Hׄ@0pǪUeqg}Zw)"R[?'IB g[Z3{B ,R!p0_ fggY!"*$B"I]FΟ #/уϹVW_O>xyT)/Jl.f^{ O^೻0M9ǟz|AyxL*\HNı2` (doQQ)Q)~|/o@d/rt'0 skx!M4M~3&L!a őC(B[[`zz  ߘkoD5ê2|I2G$7unNafXeh"OL)3Nq)Nj!:++( 8)PV+K.[ͧIJ 2ˆ ؉r9!AH]*+vF˺PDf:D^@w䤲[?  Sh tnŮ;wL4} tƄȤKƇ<ֱc5nÖ}H66” \:El[zk#TD{4ǣEﵒ8(pӖ6xuqrgE"/sѣT0DXI55eij)Q1Q}zX ,T(Ɔ)MJh6,$B8VG"J._*vዶM”w~Y5Qg$zĔr)e)K7t/D ~>_A/kãGt`*֭؏L^P+L__\+J"IENDB`emelfm2-0.4.1/icons/plugin_find_48.png0000600000175000017500000000514110521644703016444 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org< IDATh՚{pTǿ>d„ F>R&֎hk52N֎ &3ΨS&$JZ[D!f_ٻI00ΜI9Ϟ=&BO,@i -b"0,!!M>P`s]{﹌. ]qX(LIجL'4ON?qR`*[hQgUYׯ L0YX}vymm\+.j cK9VcRlms]4s).uEn)$} H~uVf߀ceU5/@EER]!`jWnŒ&}Ś-RJ"cW׾4Ylm7~~Ɋ; zKC2fzUS"@״qmQP}q+k9)Xr~YӪaPTg+?=o*3lIQT`cI8=}bB(${chu>=%c#e{+Nu0|)h:Y(pandeɇO7 ³6KO^Ȼ8e\<2s^ ` sa ^- h֝9,on<|iگ[pAź~ aqWH*HŲٗ2Grߠ/bfAa۫< Y pƊ.<';;Ȗ8q3bZ?[h'Ѧ0'+A &K##g6 [pP2[.!7;0[kN LIyGNk8H t"?40svIW1 Ae 6x8hM;{T djbMqrI\hC&bOav $է 5ܟ_?M [+Dp9Np Ȱ6x|C?uRↅÛ#)=~$x}WYz@I\C&2B ۸QGH!sѥ&^t26_JF,696;z!>kqжa ` ',1d1a3%ܓ6ޫS3Rg۶gy,,O-ߺ)g6M\ hꄒ0JRϓjUgo2NÈݳy~ʏVU͉]n嶴CVe& d0ȣ$ q)fb`+g+EE1f H&C`F;?}ZSw D/^3Zi7,,b*)aOm[iD1< V;ڞ:sFDn;asH(C򆦦;!~U7][l6HeR9FƄ΀F3-v?}=*PFd-yNFFBa~ʟ6ov sq*)AIr_QzU8 BL7sD5<ddɮ[|զI">Rihћ[m{ZlۋcЈp@ -&vmn m +x>)Y6${d{Gzi:~MyN%v[뜎Pn.I>;ݱ/򆜙 (WP<{C "M녢v.^*\Mki5!1Y d:~f,&Ӕ~c 4?櫙% 7OF_YT'^*~CW7S.0*LryW:t#14|Jx 1H&YF$lj.ԁ ʌaI; ÌG"IW^LU, Ncihd,݇eQ]DP'HMUuIfp˗W؋%R|ekoIzFwK%8=|L'&6}?d=a ϙmrR 7ugŶ9!+j>_EEг XȲ:8r$EƚSQY9cIv)1#fIQjc[7b}/B3WbYl=10zF_\p)2A 1x@d7_$\i3k}`E/gE|EdKć h@BDHjW^f/Tfg{ZsCtNw"AMO?I$J!3-xgI_+6Tğm9Q:SI jqaߟ '>qtfLi3W/:NN?_7[s~ PϾC߫ 63xN,lൣ؆$ D*\Z/C7l$b UNx UC"ytuh P!Ic@ eCxCtЇu z.@B;-+d(7N16Tc.5rٖsT/J?}3Lsy9H w^Ԋe^Og@ ϒ$ P$BoK_g+'mofZ|O=Oޕq@##Rܰ _vc XXʋ1XU_Wb lnv5!A HBQo:73sjlj:aS]} jeC8$V,%tƀ֜MOboCK0D[zL";cB0]ih>2d0PG!Y:ΨkwuiA".kQ9tkGpyP6%yvϻ+q@a._:9hw=gg+貿Z7 {~[HhhVO`rN}`nNDsj_{϶ cɅFj#Dasԟ zFoރMf3$j@\\{ڕj9U''b|0 ($"A !i|:e Z*r@@bdy)F.J@iV<=_( 'P(h8@"A|QvH!-"+"-L\\f{k˸][Ʋ[ ǑВgՕ;zk?A1]B!SB13bf(Kl{ln٭ѭѪѮA^'D{3hh3pvP+fZg t'W'`ZjzjSm965ybӝ6ӓ$o4]?f+j / yscٝjomkTxq2%\nsO\<+mOK-gS\_tE>εF߲-ִ|tPsv8Y߿ lA$֒_z3I;r-36mpZN-Ԅb"ܹPir_"Mʅly[+~u| {c/h]v<= dζ״Qwe*$˜yU!^ãwmO$}eW2]jBEW;TK眗НX:%Z͋-N$֗[T/o&fm9ZKnnhyR%vynxQK>N`dnRT嬑dr-!WJm9~{JTw)^@j05?{(IENDB`emelfm2-0.4.1/icons/view_48.png0000600000175000017500000000530010521644703015115 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org< RIDAThy?vgwUx"U&MyVJDcJM&e+^5!0`DKQ'va٣螣{?fvœԖU|~ǯ~]2- n08$`Q@€耮\KJW 4`@Iyo7W9kc#FdT*t0,njbpeZ=(wTK{ChXM-BۆrݪzwlfXE´ <ɏ8h4eFc\trQ`Hx )׾}/m?rt7[ b=f2(>4J.V2@O937A>_[@\Mhuj^;rs>; G6ձ`OO*jbZQ ‘c\ra7mؼ+~ٷe6;t7LJore?z쑧7]}mQ/PT8Q?`6:r46zM}]b#x:omdGrOr-mӛKO#ZnRܧՄW7O "- _',u}Qx.DMUy`T{l=}kmi>vBv*aHbZE/gIH2{12)Sk)$]E$8GmH)9z'Ŷn+,ն;655/.y?P(xrk[9Sz?~-왍 gyR3Vm.3"#Ə <3jC !*G)=DQB]XO(ёktӹ3 5ZU !q*CJy抏mˎ]>Z;;, M*!3I۲ i~*C>+T?#*Jz6=~ TU08[mض k m_ɺHyz|ғxkVOw/bB[>P@>~M1~|=v@x zd|&ˮE 9Oߧ^XmﭲS6W!ѥ?0ѹ7Ėi(_*P0v)QǍ $gofͽ6v.3Z'еJB̙ y۶rI>+}݅|!_6duVUUߔc&~ݿNiL4% ng! Rضtш佷_mj<)̺jZBaÛ[qʉ@Œu\pʛ o&KO/0TݤR*g)6=憛~n=l"pϷ9@O J2'ϨgFT_?WzMc [6b1ݝ6*qB |\.G.0 ^zU|}k:p&](%ҕ}w;IR뺕=!٧Ô)~O0z>E|사.̘Q6TT*E:Fu`.o-}*_VM5sq5!ہ˅JCcؑ+3[O'  4 Mظ9ˋ:7@M uOaI.TE`f}bʫMP,zGζv4rz/&*!7 !Qc4ML>{0x4M#YFYO8^H5$4,%,o:9뺸ڂ:3ٌIg0/#hՎ BQ:0νO=7:v)|$QRT#,Uѣ%̰u]V,ޞ5 3핈6C"e*pMy)áh0j#-S}G4sh~J.WҽVa9R2v˄j [X荾vBu\Q&w@_&JH =p_ jP( qG9\ԏDGyHHIyl*^=z@x@w/~)*J ̢9z;Ue-RJ4s1Rz`&WPb'GQL ;?8ef 4r Ku78(fGWP#p(Fk#,WA0\>$`qHp㐀!Í＀ 至ف>$TKS6KN(N{pVk (8.vŔh;w& \-rm fA(C>3<jF@J:F$w 1 @lF3O3;53&+{e9z(1鯽سErl": }j8YғB@r:>9 `_<:Xp[!ux_\죀H4e4q1?܊SHУA}HI'V\M~ Dt L# RiSud׼v(Uc܊ns?T nW:H)z$@ІU/}fIENDB`emelfm2-0.4.1/icons/mirror_vert_48.png0000600000175000017500000000513310521644703016521 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org< IDATh͚Yl"%Z-Y$K%Yezbgm41n>Hi4KNӇbEP4@ڢh4uq8b[R(əpbn;9=s\^n kX1 ;*z.I$xo$onlh`AC]}ϕΜp{yU%Y獍N=yg5UT =vXS[m;Z9ItN}EjҹWMZ_؟MyU/*S5U1k:o~Flv[ q:Ľ9bD9 \OSל}0n=mFgͺJqp9dR03JX4*]nw55e{Ќn~? tuaMU^[ \Rf \@ Kq8.~xLSp{KtqS]]CK .F, B;L_>!~MU>Z^,,?~},.$#. gl.ɟ&C\zEx*r* $ruvk"[fb|{lhÚg!̞uaFz>vkONz=YYSvvE6=M& >NML pˉ TmD]G?͞[[eUβŢuz 2>8qxPSwVV]R]\,[R-+S='iG[kd,q]G ⺁u=16R&<[@{<@bMr4t.|KHBu=%[C70X<ʛd4U=_:5% X$Kfv2}؝v\FciND!N\`>y!iD6O N&CTe&+  |zH׿*+,KOB=ލ~' 󍩓qͮD cӃ8x"[i !ƹ<0Nh*r5-.u5t9 ?N7w0뫞8)…?W_t|SlyzsGuY4Dc|b?*ep{V|js8T6O: `v#;B-UEBaQ\ PCS;4URMu(*.=;pw4vH=H}W \r{$I:ٺ;(td9s͟ɬy2Zjd@RnQZuKwّ_\Zxk]nl_Z..8[\7g[s3 NRB`YJ 6q{~pf]KͮBaQ~INu9 *E+"2֭ T)-:7G^aMUFӀWh(QKʟ>;92X[$VWR[[z/Й YWw6*q>_|1;mgNNOlt?nbydS47Mp D##yv([]2lKO}ߐ?%ďEB]-[e9],wug?Xmֹ.G u//*p9lQ**s,EѢ`Ҭ2'k3ҍ4"$7oAH)` `Rۆ`R-8Y {FA*gùTs8,.߼gelcaLhsLE4U\r?,6Xݰ iqgYds !`glT!MU+yQ*-۰[ȱuFb w ,Ȋ X-ivV$h'G xTSWj_^w;onBWz4إ1G^)fdRJpy^@?gn䨢=:"G[A39J4!1}MPFcJb-F6<~ IENDB`emelfm2-0.4.1/icons/owners_48.png0000600000175000017500000001011510521644703015460 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org<IDAThřil\u-fᐔHj!Q%Gɒ;M ;KMEhB7EPt (FM>iLv$F䅉ZHٖPDQܗ7wO?pdC^{{{{QU^Mvt.rE?"eǖszk7u(3?\G厬ֵ5tEkR$TYT>}( lhka޲f+mlFl0M0zA' =`HCW~K8Ww4ɤ%X^;d4V#9 nl4kvtómڇ`%X4N&+N:+Dm6gT7$M#zo~hS2R?GBM`':t@l[!nh=k~]_815sۮ6NA,꯲Xu kM~xO*7Aʶ H1jE)|kU;56JX8m8nMYA_9>Qdb#C>rxڝP'#Cx&#ܵM״f*°ŋ?;̱Rm>U"[?>‰VݖNZ0V/kn&I zh3yOF %zbBAu;;o-˒TGJ\ᇾ:`ߗw_D1#ضmB2{o[Mѫ/*>HoOkHy2Mo|^k]uߟj Op0T*A៛G}|l XJm/]*͊l˦%!ȏψ_,ѣ:?$[7HFD~6<%cAjEvAvV*ݷw9 "``Y&"K~}ֶv5{^˗jj[†fbcB1%.Q} ֘JrEY`,l zqkֶyfPzUm46w~8ʇ^J@cGM:@1лښԪcj}=va)f&XPݕc˲i۴t'=H$Ja+ڜțAK.-=;"44 8BU1Q1h~:~͝~ƍn5cWm0LױҍSRܟ9s極pԸ_CZ]`jmz'gyY8" jtj r$H^6IfV#zb/L#p̄~EMDl 8x|?тc^zTĆ(؍%O<]R_Q^a8v D׶D,Cއ .jmzT噲lKEu RLŖ |^hͰ&6jKSDZ(0QTHxLP. ^gӣض{fq2;m,斾Je*&iҒͫb߫VS-\OP *8Zضp{8ǩ04t{r( p:MvrE"m/81G's}J2TBU ᶦZ"QTV(Y+*Ώ(,oj'Ux[AV^©Gؼe'}W;5d-EDRғK*ؖߓ.oPbksu`0f2uѱȉT(Ŝ(ێ:a!|_߾׫ B\OHS['?mۘX"jNV׹bxx<0 .IZ߬1,o #\VUv4ʳP4npʄjt0!Ly,/>pRGںn!}/ֶ\3Ś (so8l>UZB D?$޳+Rɵx3$mFV׳rGnpرk9Xemx4 YiU+v5|0~(&9#Q7pa8ٶ5c/%͡\ϣٱmRU2EֵiO|հ9EX,5I'CҩbI C?eu xc飠uq-2`$X Ƙl.ˊ[EH`ٚ.39iRn%q,ƫm`ffu&2L կqO]b83gϦP4F4nNa[x c%p$ISG- BJ1sϾ}J\k*~{~Y,_2_>!yb+,2tE--_:k!a,Kl,j(9 Ǚ# APatt\8#Kĉ…3_(X?8ѹDLϭ+NiYxTs{ XHm'96%1`ZhTs$ew{F)x'+]˦}9sSbrW mV,L".d d&T )AXd'N~O2E3.RLQ_D]rRFTob_W/U5pc]nZ59p$_I²!n_ݍNs kwdXCɵs07 U-Jԩs'ƣWvEH-0sN{z U[Rخp0EѧrύbS7 0?. `jfbERLwO⇿SJp{>kZ|VӖw-/2x@2cf")r/L aYV{Q4I"}߸illDp!ϹsҩSGcG+ott\w4mHѸ#TmefBJ(jK8T.|.Fw~w&R=ɁbYy?0:} {N=&lV,qu8[l\e ,'u+{85vZr>Cg#Pb%9"&KwoXT3̽W.FK>g(&54||B0=qQEJ3y>`EG ,* (<j*p;@˜Sv!Yg&ڈ:ֆ<:;Mo&_Iވ OEGaZ,&wxSEUCeB٢>V7=Z?kT]s7iQ'B\1ۻ~c=Zߋ`],s]ʅ\ 55A^*o7 |U FxF ʽ%(_C' IENDB`emelfm2-0.4.1/icons/su_48.png0000600000175000017500000000553410521644703014603 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org< IDAThř{p\}?gjWڕ'6F6LHץ4ɔ60a0LiC4MYyL)A෣XW/l녥}H޽?e[B3gF:w}~~)%WP-N&`kB󽎘o߿a`z~J&t|7/1`gЩl %W!1ǜ cqpԇp$zKGM ]&?xLH^n[e.;^J2m؜ejΞcb-w477g2 }qQ# ^!gOڒwK2sUcb |bUzn {" @hǻN-?l /CL%񫟥Orl^c"<#>uh2Dl[w|@.76cB0y3mH]Tm[T[Ήոp$𒅊o}EK4Tza#ٳ?wfLyDMw7q @9_$x%. ]BL((٩ѱҏVA\B"UH詞BQ.bR<.~o!"@- 7Ԑ0m^}%S;80[?R}]C[݌F-9wtL?+f3<H${>^}_r jK$[5m:2m~R3g=8r2fC}hk"iѣpB ~`]yvEv?%;teTR3JMl ( PJſۗt Kv]"[Y~!E]^x\+>mɁFQW8\mKc۵`XPcJHO\rDo/d7Y{ Lg dF5U1( RPnul[O~ Q5h<~89l>+QL.b*w|q~ kD<8/е{?LH)W}68 k4R*rm;^イHV OԷZ-Yza rWh"c_O~vh 3 .]Ųs!BpS}o_!mr9)ͯܯ(bZm$tT,K 1Nm2s%bNsq >GHg*g0l+%e#ar`|':4m$ty 3%x9)M-=ۖ6mlƪژU iվ6ːҀ=Y~֬ <JRw>s/Dp,|xV m :mQi1kJVʙ$?2H6{YQǯw}x02sݙϾ2uy,$۟FNZP%=1_YܷU<&F`OJfוc!@uKYy tqXd#ks;x}ŸlM0% 4 I&f{-,_E5W),ctVx$_ ]~eX<ܔvpwAk3G]kRJUJEcglj~$w ]>=oxL|Ϸm ZȶaLlqr0=w6N[|jU>w/Z_+W֪3w'tYSm]BX0 0M[ai[K"1K[h{ɏӜ&UլI"ad2@Z6cRXUclP ֫tc2&>Hs>xlRׄoU!ghReHe"SɍPuڛ²m#B?M]u |HI a?Zå1'u.orf;d6?z.Id?7&=ҿ_J AۖW䪤U:GhR4k%!18dgeJ:=v6/:¡#ٲ5$*uG#d)mJ%;Q,.oPTASiù}}NAf O<#Î& ǽdjr&puFe%J(_C Y1RE9\4.p|aT"&?EmX6\m\VŜErK/NR=k'،]Mx\[Aq+ܪs/ ,gOysUfx*>NFy9֯f{ w1h>!`>K}ҳ܎g6wuv;S=wy) INgf5l^c}Idžuu۷B`,"BԼ0J)lv0`ޯUiHt2UB.2="͠f44! C_~UkVf0ƽ?w{hCVX6͸r360`rI" jb"iR H!꼃7CdXY^]j 6~\JS\DcX4mϥ06)LʓRZ@"{Gdlӥܴgp`=nb?C[pnؘȗ Fx ͆]XԜ4Б@$n16Ls`s$w(0?a'iϷws5&)F_K20kAD/ΚфB[|'~DD$ZEr11 n{;_(M/1^eFb@'hrSҍRҷl>z2]\M +@1EK<mX~i2FpCt1\j I+Z.0%n [`)m|9wZ@c2/N6mOw=j" d3Fcބ_4FN?SF "}o*Rc_l&'afVJ)嫚= MVW\wIENDB`emelfm2-0.4.1/icons/emelfm2_32.png0000600000175000017500000000470410521644703015472 0ustar cairocairoPNG  IHDR szzsBIT|dtEXtSoftwarewww.inkscape.org< VIDATXŖylW?3kH]9MB%(mH"EH"EiCi#*TEZJZRIڤNN9w׻zc; $Fzқ|;""?/bvh(vyV5m1ElUsfNj=o[u_,TUlh^Va6hxkꛙYYCOxցv X+E]jZ]iϨm稝⩩CU A9-K[7;wr7uݯ'a(˲#P6ӧ68jZ<5uq:ׂDI0Ub04f﫿/}yW.@!  >B~~uZ:?nwє#bYdb<4הɹXжyM`룿PpP0Xz@5\=>MYyUPs©)GS 4MK]' RYC$?M.aNsXx R5$xy ɨnE.-̦p8B(\#1xmWXsjK<=Vs:卹<^l['Vs#b"brC?Rp!cYc7Dvk;f'v!uPRb|i?UUi.^IFI7=萆i6k/rh)JYuwDEzʟ!c\Q ɉjm$Gy+Jx%; +̻uk/e9EغY^=ʻ El0igԫ#0t[!>WaXl{r7zo]\Qsl\'eNӶ[(O"Ӽ:m$cm`&.S{BJ ?"&j0^lI9"45aDLb$v ů@Mi*Ngx4IKk5o>u*TIrs]tw!vt:3Od2 ;~v"9ndmås ޾NP6ÍӉG%@82ȲQ''q $3Ȭ b" ;Ù.֬,ΟfoD@r}Q+<d0V(-& C,^uQjʞ7x,G^&g wpz{b46O8;`*Ti]Ǣ!Z 4&RyW~12w>r0u i銱r}#`K ߿G*} 3+`X6dagQ52KW/dS$tfq!v3reP&f^ ۟ J BLG$_ت%  *o* 4Mga$")DLp>;0U}BȹlD,b%,q] X"ubeUgv 3k S}[ʾ< 6KYsw%Y4YD (R,"X ð1Y2?ǮW \P9$8sbrY|qK tEi45N< c[ʁ'giY޻!F?(%sӃP\8`H7çVs,@٧ۋ6iihaĭKϾsO?z#>_XW/1rsl$Sk7:ss{Uy"̙VFCCp+Tӗ,[ܐcmsc8FR8/!(Y#z|̹8 #E;[1rcv1jNӊsﻺq (iTe:+p5'3 lob$ jf"cPq]wNpR+w!k}BF'͙hӃKܜݜ?ώO#C2?9@p2o|H%NziL$& rbv&XYm\TFG.^:SXn[TT-f1Njeے"s͡1P~huzhz2o=3T0^Sĉ.*E O>sG3$ G RQ^~KdLڸ:KU&r(1h3RQ06ZcxF`lg 5]ϻ;TUml orw+wgj67#5SKsGS%izPh8Ũe_W0 ^`Q/Ҋ:K=^5FhߚC"UtN9@6 ē/)yU隼z "#uʉ)X"0i bU!mg_,5WZN#!ĎV *)&-M+N.MP΄Ci5[/ov72"HqVCa-! `|tQ Xى*$GÇgk:׆'/~CǕ$M&j7]'&Z&M! mK<0@`gVI_4Ѹ*ljӐ*eVZ֚H>9 *~r"l4W}31\z@>ZNYBG,M By(jp~>0g,(W}(j{SXX`eA-0Ӈ'BW(BvxxT Y_yض +<=Bp\z.՞˂ΉaODSMSWN~#'h{ts İQu7;Nz߶տ۬ t:6⿮N~EPIENDB`emelfm2-0.4.1/icons/move_48.png0000600000175000017500000000376210521644703015123 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org<IDAThlW?}ﯶǸt d0mE`P 24c:5fL̦ˢd"QAũQ;M̍ar˥@[{{{_e43>~~pԷ@DLӬ~.$Yf2Z05pxەxGA3Ѫ̔jR3rYW4HbV{}}}q4Dub&-⪶(Wol۶/e"Rѿcm3 ]ObI>g~5Rϸh*T(ˍ+d&"0s`{|{~x͛'{LeglY}<y.]x~ ?';}J\MbWoM՝UۍxnoyО;hiO fHؼmR/wmko*QWWG}}= DQhnn&jOOOiS޽30yl|a{YloQi\umktT_9ֺByo'ɉcy{);wڛoO>S-L]R+j /477#3zi~]lFnW"0|/s}}D4i-S;ſuV.>v"H';n+z(ڏ>7X&נś]@z^.sDJ^/=0sq0x-iH|9}$#+qI={vI4/a&BuFUoFd72;>g%S^Uxt:=0}|XДs64j_,|J'%PT6ps֮/mgy\'d$Hcp+dɬ&ݶ*JʨjQMp 9n ΨklAzV\[J&sp=A|wO},8/=W;h|W3>Dx ^+)vfێ8ėy |C1igl[Ti>~m!ZW5bj|=!ꮭx:N|wm#S~s#-+pYøhG\|+9=l)kRSP;nm6- I.G |1]s>dt5?5Im-1ep8Xg.Z F#r2! <lԦWjುs4^qRJȠ3 $L]2=jB@|!;OH֌y|J]_PqA(8QǧM].CqU"|%s=4Л#`V)XxJ3`-Uq[ (Ÿ\\5>xJS3]ZHc }ɓug_DptH~VAXMGh]o>Nӱ/Xk]oIENDB`emelfm2-0.4.1/icons/hidden_noshow_48.png0000600000175000017500000000540610521644703017002 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org< IDATh͙k\W}>cvv7Y'ǯ1DTEUS7BQAoꭻPč$N<ܠ;1>:'I'+mkC߼[vuXX_$,|EmĩY~7砮?`/]1SYU{oa߮[Y^ʼnٙ+"[놷ݏ?j?;9[gpbqUaA O<Tk4P;vb:H}djxB1.:M ,T:!@ɟsͯq1˵ `H `liAdq ,-4t 0)rZ qGd̫4֓५ejDŽּvMU*19V/lwxw{*7%ٙrKM $j ǻ8ϛm% S״y~¬I.sV vsQiBIϞc % d"2X@8N35=]bPZSQxZ;w}vnS(c's3usPk#~̏: NDyBcy%f;ZZ%Lyjcy!J =OXaT[Kv&4/_+PHyH XM6`[ k `$6OZ-]NHnJPiZb~2iegWzL<{, "ug;S3df&#/L>jxۮ?nz(5nB75 ekA|yh1Xc"o c&X91^DMo;0wz)/lLT(4}<0lW~aJ ~Qۂu6 j&vJV*P{7ӕ 8P%ұ.4y<Ϯy0Un8gYA\VPέ^kn$d)B2.Uh8{T-1RZJ^Q}(kT Մ(:7O5J4 ˆ6=Jh)@۠zb:f\JSm.!f@ÀVWDxjeqD:suWѤm l$V ^#Jr0;SST^)c'un#W8#X-X0kX&q^({SLTw,4|1LY_[h+Z\9 zUoRv F)._vE<;Sa(7ɒ5%cuܚbSV!IENDB`emelfm2-0.4.1/icons/copy_as_48.png0000600000175000017500000000171710521644703015610 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org<aIDAThXnAfvW O!E  % #PEihx4*  GDA!PSycc[;SwwZH${2"0-hY%0+ !]*U, 7lSczqgh4=#B€yØ1V8(u䰝V{?cjt]˙tnv_6<5k w!c*50oh%Gbx  # :w1_Tﳉ@.Mö{V4+B6l=S%ްDϳDDOvIzUQ4際8TH!<B D+$D E3V)ԋ;'S`-j )%8!)4I:>4`KA8_e;ھf#t c V+6uY`@ҭA x>$og!1<٦" o:/ҹ-})q޲ dmb 0(iKNәӻ: 5kA':N\(k?D`؀d "I6_,>wbbBuxΔ? Z +DeY2j(EXB*#WJOz 9H*ҋ?ܔ`q"NbB͏ Yxs}EXJ#vTTTi%0MjD >fR+Յ7WGQ ÍbVEOy"FGʳrT Fd6vRs׺.#h`](znR%Pj*: b;q--ġp&R7ѶIENDB`emelfm2-0.4.1/icons/mounts_48.png0000600000175000017500000000534710521644703015503 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org< yIDAThieփ^ (!(!A!"cA D(eEJ!$ RpqYƘF{]9;WwUG׻kcxRM_]ޫa,L+Is3-iOr?\|1A֦]dFH1iyxؗJOt'fFPBa]e^G'G)U0\b=+Q՗^-)AX)A`=}5mO%/_R\㹣(W|ҹ[mԖK-Dn.۶~9'ly ".)u&0r)z_WW*Sq+vHBX.ͪڥֺ7fVXKIr΄?(?`ʹ0'8 U0Z*ؖמ Owafb0ST9o& (cL*">хNpj29l4ƬLǯ@J+qPک(U0aLUTeED(oJJـ<.\$7` )\h*<*פlcf۪oooRxue)Krtt7HzF}=kVX rH:O']wGgdaQtcQGsԉ5Z~XHKg16Ĭf"vSER/bi wn 6KVYZLEyܤӓulLM=):O 6HHе4JYa9Udx6֋Ͼm\%1QJsY ?>/nw<7]KRϧ,_q^WA}˜>!pUD,:>u̇d c`*5TC!`|a1 \J9"kۛ>Y#} T2}Lw*ݐRڠu:GG},L"肋;SfSFs^*>TkݓʭqUp|ԋGghgnZ$ nXDg)gTj]oz8~١W\{%%kpƌ8CfbRl䮵vt_Z6> uݸÑM*T,X^9}BZbs?l終X0AqTsJT+Ee}q;$5E6oN&%baOԖ}[c=\BF2vsx0j RHJ30|?LÚޯ̈́3u+MwDKzzQxx`nQIo6 N+~*-0$m=:]O-\3?gZ QIENDB`emelfm2-0.4.1/icons/switch_48.png0000600000175000017500000000373110521644703015452 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org<kIDATh͙ilg3x}M:Ӧ9h씦Z* $"J$TFBEBBBJ H h*SMH"H29$9|3|3{ػީV;;w)%v `>Enh1E;Cƙ=1f|tb8FzC5p@d ەc/L #ptC{wEѡRE\v5/$o^vw{?uo\I,.o*“O[}.@7n;M:? 5d+ YZ^{G9ZK0z.{b jڂD|}z(7};&<ޜNL;pִ6E޳kRS/}''8 ˴o5u 8֯,qֵ4K,/5 L 4/]p ԐT)T{±$#JvZ}a t-cDڔs(5XwNsifmuZ=g6)7T_k:FNw7#`m+UNAkɂbY eyIr mu T./┨F w?#37Z 7f&qpeݶX{S  xjlS#ەT:M$.O+ ["w(^)~P'+u$$AJ,Y,nqv xeW醶 6((s 2['<iO5HRή˛ѣ[C[O:xt r\Q'[{CyQ.H =r#'S7ܼefEs_#ZlIO8 S^]JuNjb9xq:re5" ֖mZ4&:y2?['w9fNy|*}K'R/4*`— ZF.[K'R`X{{WO0cA>ٔ 8{خw*5-7/tո/>Rِ_Ν^AIud$Cz;,R=3w[,oNN0fejJ˨(Z'6́%E)2 MQ@Qr BJdRne_jJnhC@ej{+{kB/# K i7<@O0LdRYWm6w LsIx1+ɿN[,Ӿ(|MZ1\=ؽc`[T -$#uO}il6^h?VFwo Yv +&=NmB!d w#_ݴ=(ЎDnNph\Z|2_-4f/8ڕw,CU!{ I[ ^7\iLC01LIV<ӷ/qa㹒Դeg mL0Tk#*L0Nc=O AohQI 7yq!x2aC8=Цq_>m&1%q]^Lcr{b˴Ml#;KyYtJrxx֋9Qc.+I&5` :?ხ#uv+J4 \>*2! Xg!жߣгgsi_ -Ha IENDB`emelfm2-0.4.1/icons/mirror_horiz_48.png0000600000175000017500000000541210521644703016674 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org< IDATh՚yl\?ow^G94AF8 "%j%hԔ*ZEJKŃ^AjB ?TR*PB &NlכvzNF3;YRy.5s-T5*ͩ62] P.2CT͸9®6_ǢZ;ɡt:c`eʸ{;Y}c)s0Luf/_C#U3<T{.lmm]}pմmWu6U3 %D  =Uh/{{ Mxgݒ]K\,Nl IFNL{]v m?HA˂&{-~;/f\WGqEj)i?ѻQN[ wt*-=\a)TѢ(bUL]:iDXi2 µRBQXiJՌ ⁧^|qK{chdfjc=S=~pC!w 햶רeg˒4IVԧgCozvþt"zٕ o ]%jb̦#Dcd3',SiV@b6k?t`߱rϐTJGDՌP5Cvmu ApddYB27©S br4VȬұ+k~{TT 5o^̍~<,2٢wN[HtmT[W+ P5cMM {W!$Ჺm[a9~q!Ľ?'g۳x^i-YۉCU3v{uWAp)*e=pxD=wN>rpqL>;=sfzz("ڦ:Zl!R3NI{pr6@*@J2)Qg}!zߡjWrU3 w/kIt5–#Q2Wߩv7+ uYH([P(9xLc'&+mb/qO내/m9Wem]5"_'ѱ(D& L= '2Uj-7sXo77v8lY]:Tq fz`_sj^@QTX:]L2(;B>eP  OT]X%-Mn'Kz/RQt4J:>_0R$`z6|d.HjgC݂z k_؞om iu 3b~ͅ,y>Α?38$>B&e@ҴWyTՌug~yuy[FU8m)R" ?p۫HH*z" . 4otAj&&>K߭8V}LNeqM Pr7{k7,i)kpW)l"SY~ӾP5P{ @A:_Kt4ÊFA EJ>n[`}M}e4dC$'[>krct4L P(2_9}@} &jZM:DKhdreÊ]$Ҷ&(/H7]7(]h1iD"wY>ȏq홂7gP)x20ɩ XT4C|*@t\z-SW O=2&ȥs$L =6M2 l M=[ȧ geϞazBS߶L7oǦvaIENDB`emelfm2-0.4.1/icons/apply_all_48.png0000600000175000017500000000347010521644703016126 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org<IDAThՙmlSƟ㷐L!v$1t H$LTV>ikUSjJUZʪMbb`뗪jZ RQ$&-s:}{9}cnϗ+sε3"Y63yZ\^ 9ՙU3{L3k-?/(s߳wJJ0xSBK $L6O,VW?5 H` a5XQKb 癅/hڴy%)AIJXR Xz3S3|[w]€ 0: TTƽ ۛP%)ANk>XͭhaҒڭ-JRB\U)Ύe6`l6A(P)@p ^`(h,M;WmYٕyU] IZkfͥiosU1-P(d =  \;/YЛVF8TQYn}9 2(YZ|޶QQ0 iqG6\B҈s \T@DvӭzlewBOm&? *ۗ5y.*>:Vfyf'Kw5^(cE%trfOjm**:N\z:~#0L]`l_&O8/GizDf3=?^_XX#/w`yJjXصFڇLA/"h42Wlc8U{v.2p 7,5 =9^k[г`0y?usݵp\R :WxJ` Air5IA r"&O`KF5躑"&Ch޳VƼaڭK˒,t&)蕍dl`e/򾎓6Ĵp miM ,f>_^P@_{OLCN_³k^9۰VLlY MYGo$t P3 ̆xrB ,Qjz}c=C_ہ[˘]- zwnX;jco]^r#+)iP:$1x__"vU,ЋCBo*; ;=ŹɀH%A@5i%c/wCp:ˌ~iC/9)!'@/ГOJ7AߕXfH@z;Ă@B/g_h(F^UaѼ}݄g zyU:x;Aߥ߫b #,Yց\B[υz/꺸rҍn-v™i^MSy8| @Og>sCstM?OЛ|޴ `@OzEK;}ɴɳk6- zFTō š|i3O3Y HP8ڳP17|CS=+C/#\hjY L zDtc$2՚໠g&7XHͥV!\mck#d+Z:.RkN@}{@ :w&qu,Y?f W,IENDB`emelfm2-0.4.1/icons/trash_48.png0000600000175000017500000000555210521644703015275 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org< IDAThZkl[W}k;vGm&M4#kv@:aLh 4I I ee 4u֕v]ۭy4oNؾ烝5;tdsuν&fgOcоpW&< Q'\t,9_w='KTsFqV+RƴVN'_x?ż;xZyvӱ+!Y"<_jAW]S}OyN>^x:\ )BB(mZ.'<,X glZ_۱ʦCm2{ |kGj=U7M ݢaAO"j}LD \mDNOΥ\^~i3~gpQ$N_:\rY"SNyG^6Wqr:B#3i})DnR|^mf$t 1$ղd8DS _ -){cjnyw+.2g&02%Ub`o]O6W= 좛5 i*ԅX] Q$I}Kq~5c+WƮ_Xu]5 ϼԹ`&bI}uYN uQS]jv2UL]#cD `2o˓i/3q.k*h!os_kќbs°0(z%!I"=-+Q~FqlqUm i&:2Y t Nr8@iG\)@S}yicvY0C"Z^Wng}>0;ը$i5MdU.РROb:B̼"!><. `FҰ+W& C00CW'T=g|:%X /_  hZ'"ba\5n%p{|*,`MtO\B0-hR31$ ['m^:qSuC I>?." m|< ɉ~CJJ5c<"Mq(=͖(nR\%Fd1, fM8o0g6Oh?x_}3hM z7*,'b= Ja(aM{o *8)r,dMf4 c::YUS6|i{0 u3 Mcʛ9\8z]f.0fThNq5g [@Upi~`EgT|A:nۻ񀟐@x!%m][ׁr{p`2rgljSPt2#a[g|߰uCՉz}*Z@ɿl Q cI2fؙ\98Mc'v&֩: KSiB*W HܦѬa.dhmP0\~'$vt# $L5[Y7  Hw\kMau 0;^= oBvIȼAH&c7LQMm]e];KDD$WWQoTRFHܾnU<3oQ B;>>w :Uz:Ρݳmhnjއ7mo)CztD_O6Nf8J<DV?SPODUppwvov ܆[#lZ̐eMV4>zt:5 ~ߴ6ջdL:3\4,ؽ M%ZVqКȱz!ݼP>0Ju9S8Y]l-]'^VxU$fxP)H;oy7jMm^ P}<`0o HK~/0b3qGUx(ޕɩSPzA,~47;!8t D' 贠ђvD˲IENDB`emelfm2-0.4.1/icons/plugin_copy_48.png0000600000175000017500000000361410521644703016501 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org<IDAThZ]W~ssl6R&1i &3ۤ`Cl(oF+x" *?Poԋ&m҈ mVR3;466ݙ"4K w;nm6E¤i"M 1e>sȲ y8C*psMwbp)`K?<ǧ M͒c{{+R.9I D@=@ڮܜ6,C$i0 ^ D^bhM/րe ltԂ{\Nz  ]@bp{$(,0րcw樒dz}ڇ_xYe~.8@DN !HwoiE3{LV:'\¹7㋻ǎa&6-@x29ث}O? P MkfڮbфKO_|`.bTO>G/v&c,@h [ricypz*y>G(r QQzJ +~S ݟ&"r!"Bdث]C 0'@:{OU*ND4Wdz#4ͼhcf.d[m8Vŷbs==W\-[v- +cQoz+o4Z>9f` b MI!3PT+lChBImpp!w_8'kSop3b$ ooU.=~O1 oYaO\p2ygG|JE`*R<_rA~ Ն/Mt93p$_F*=ߜF«O8B?]=↑ G?yϕ; pM7k}YuPG ?T~6|T.!)nmjߑ ;St aq'D+]{8>y<6ck 6Wv@ZhNDO͛_ 끉PށR%f1BcV+Pr,?dwy;$^AR: 3Jf82O::=03+ǑCG$_zGvGɊpdlaپ^ՇH3}4Oh|IWGӵV{ k͋nZ5N m8ӘȊ倞m9:Є%?4Y֥@.L*oIOQ̑Nh}:6?8ВL*BUSFsͽfٟGO_evy6ۑ,v"VRcK(dge7PZr\.*bskaj45IqzAVQ" !z?w˝g;ȝ,PAрϟ50LY2Ҹ#:u`~ l@y3Kh@!q%m3!..)1Ǫ*)!?#zݷ1BPV\T(1-WX>yԙqԼ&Y9ā@ώB%q 0766%Li(scHwtp@TT4#[=c 5Gaip?8-V0nw3?LUZ-8ؽg=B01)޾^|}إT*P(b2uJm[P( N1@0k>ڶ.RRR0٘: $$SPTXVV4^QhpR99F(<[P`fLGO8N22@,Ԍ\{e'VB 0sl@F *%ybyP(Bddf";suw`5~&7eSñc$PUEVF7Ͼ &M>5_B_?֮{Qru 75T͢(%Q4EU5$8dYֆ!'HOBll,^\g  Β,I{Dќ)ښ `v Wk4p CGO6-KC8"Ѹr<Ep!wFQ4G_}ZkT)u m8݁(m`^p; ya(U<D { ,!;h'oe݄S8tNtmCɈFaŊH||Q^^;fEl@r "A!D}*wxN,*0J)'STClldQZ`$Y^khTyLIe\vխ46'pD\7s7QmL84nX)LIENDB`emelfm2-0.4.1/icons/emelfm2_48.png0000600000175000017500000001013010521644703015467 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org<IDAThՙyp}? AA )JaR˶ؖ:q$M4Nױ'L6I&Nq&+mE:lK))Q$HA\b?.N=oo?~BB9~7!ɑ dܡ8V2F9r;ϿW;>OWCAEq^kVϯnJhp/,5r{9uB<7Ԃˍnr9]6eY¯kMZ6)40 v! ^&_πj>zk#5Kby,˗t=o*lU8 jH@:x! 5>yQ-w3֎ο˻?֤W!J/ Y>%(uX#%27LSKzYʘ}$pF!sgryn榷:]aTĢQs,ΕaS8]zBl6ѾIj&} ˤRh|o Zi[Pk,wk.OcL%DfY#ݡ^Blơ(dlpB^%2@4!2H$AQd%"Us3sY,^"vR6 b̀j~y3g·QPP;ԋ㪳fdM^$Y"^$:!^DS nv7_ G๧_w7׬z?'c0/Kwij:ۨYIr)Ad~h8Bt>r"MGW~:Zڬ Qԝ*C5s|ΰS~(*DNIeE/[k" k:FF ;Hd K r(j'w݆#P[2TDQKȊD7@whWGW&\:&*^@R$mcB֌#[KRů $`d*uZB^&H)dXe/ *x =~s@ڒed[-O05݁Yh /V٨*CBOcı 3Zy2Kne Y*VR u%;鴗OV5~<"3]De/M9ێT]]I|$zxr3+?oPtRm0%iZ;|8×ڂ2Y7w~m;ݷ44#g嵔?yx_͘fymoZZ4ϼA0ّm ,Ӓvx*J2*)AXE, m\vk=K6߼Ǜ\ 4"R)%b1Ů H%Vp8*\/@XEU.E]%hn-ωG[P\ڣa_a7q׏rZE.! `ffT2 0".Ec)ZڼHI:{]o_[~KV=Q*(ιW# ^x0-]D-2Q*y,rA2ӋE~zԂS[xe+t}\B>A`vj4!C占x[$,&`lδya3ghmwc 8|оkP^~0M:xgDryÏn&%d$Kd%"ֽߊXU$ovJ'ligL/=G CT>ƃLL42z2˓|&BX]؞Ʋd,"=UͼfRyZ_Z,EV A~~xk媲w5f9=ՍS\WQtLDrݟ;@T]w(̛8g×$fWrEzdRy~'2GU^Wp\l dI3w )jڲ;JrY$`Y2ECr䢋{'0-ƙE鄗@Z_@x&Mp K Te0>S (lg9w.2%ӹ Ԃ|L/` /HT$I^f/2Ȧu!7%Ϩ>Іeɤr}- ۷cd,;wγes "C9ʫy'LC9>u(.d'MO4Xb{Ţiߥ,\Dc]$t:}Xa:A[2苡KXt^h"ॱ`Iww4=  󣳜=\,a>tAfI"3hlj`aVˍ8LPd!dBlUC?Gcn𥝘BH(@X (! ƙ?)sC!(NMe m^RDEE4Dg>9_׭2#*B`qiRiïI4(ꂹ^KYwS^{Ҿb7_;S #䤟/}&n.X65gȰFsO(Ne5A[}4\XfܖE~ko W.C.l Sx?B>Ҋ|atخr KUDFA-Y=ǎP+Vt;:Jow'7޷1V[)/l[]gxGئtVSH_x9=T%$Iuk)S*r\j\yp_=SNffs5 l!ː\n`6hޚIdS%񸭍j7(qDԟ=a H_%33L IrYr'I$]J4#T\=_/Zl[l7.9f ٴG_kVF6sI\>Pcq (~u; 2kro>ͥx%$'Ȋx$O%sXēKe}ʼnXzTYۮZ@3uFCg R6uh|`@ݯ²$ ͰTx.G& -QD~ Q $yW]@jnIzWw=J?5kIENDB`emelfm2-0.4.1/icons/output_show_48.png0000600000175000017500000000401310521644703016543 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org<IDAThŚkl\Gs];ǎ J MK REBJ"Q !mQ> $D T1cݪY *Odw\1561^'=G^w̞'86MDj}1Aa)"xqݮk 3GfdCA0a-Ԗ&KI|ؒ][ 33<ضE>Wacm57Y%jne%|c>֌ȡ9;0EvFfgV/4*5l5]:[Qʺ-+ 9eJ_T4:i~t˗J*A"ܭc+>L+9q\@A֦7Z,~h[UgIKvL`:| Y͵dl D^~2gy~:6ЅG'4p;ל*P DvyrJjc%4'؝4!9U?yͷݫ|G'n[=tݚWyXSW} ubѭ{3זvT^i]_KKp\T P(1"⺱Fu!yl_I?(~CqRsG?~;C\]\mvTQoM$?;q#/]lEAn_\<9xrgjc%Tb,Jqzgv%N,qbVQ׭׭zC$#:lSMnT?x[5#C닗Znq~h.qڳaGӓBa7Nr-?ݨ>rey ӗ惪Ɣi;^>g}K),犘_J[SjN|c*8C* ^իE-Ij>:,w߂"rbSpT o&@k|hM`()s3dYn AgV 7 ?N.T@}R{EplRfaASh vՖY`?mw ۷ՀJ9zW_ 1,~-Da@1nۡFzΝO'jw@[)zuP-#m GaKdu޼ւ˨vgՔT= V_˨v'ʧvsFυKNTR | eT;]h1W(oy͍m憂8BUDlD{v^! UKZKZ3w-ШVots( X2 7 ޷U9{VHڗgM H wl <*U^,c-[msٯ~O1}leizv uafNPJ~D4Z*SA<^m+{7["#Q#Y9Aj:AU'%JP`)c/[.׷A|O6~rM) $1/@xJmDZ_6?! ,IENDB`emelfm2-0.4.1/icons/plugin_foreach_48.png0000600000175000017500000000366510521644703017144 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org<GIDATh՚mpTgݐW"/ VbRY^KR[i~hGatN #6@N@^"P_@a7{${ {99sgWTgWw5SwΥ ~s-}K說{]xdN&44T:!C5;Շ{\)Q,W@$ z争>{5+$By [^/ΐ@eýe)ݫZDGq,tRe B}0n8'2IvJŮ>HYQȽt@K3?4@ߧBۛ<^SGׅA%pW}lr/Z\|_IS. 9lW t9 Jtvᑠ[BTEITpZ>;Fr`$n|$96Y0\AvnKAc b塰nP", wwnIjW{@3&mb|K>(\cGa3zOŋLfk-}A6*Y)? weœ KT7;0Y(bXfQ%WA- [P] L-ȉ@ņ,ѐ f-S۪Dcg#L: TBWviOɷWwn%di`]O"y#Slju%[k >=W Ÿ| %f9;}6hh4x5)k&`YAzU^sgA܏[ӑ/C.DuLxkTZmU UktG" "s@* gECρc^+my?_hedn$`BE'^6ՉŤ.܁8qB-ee;%@LHZrg+Қ~K91=صv{YҎ/Bn4*nN" '>.Nb`t[sv=طl:gʆD{!p#ki{F{¥DR*p;&TZi1<Pa ѧŶK FYY ڍM58tcXv]?%kŀ[rB%:BE]N)NQˡĀ~1 OD5S71 $H@d5m_uA O#V c[#Y/F͍DW&9fZ:1u .*z<\ ;$PV{]*3ㄊNaoU#GƟjq dPS\& V v DS-vE uNP 0!TOW,gm??|ַ!Q.!a*bvMMuGo@nBOBBE#MM/y6`$dP;Tעr浃5F7̵]x$+BƘGEg~*<tĐz$ItIENDB`emelfm2-0.4.1/icons/plugin_unpack_48.png0000600000175000017500000000504110521644703017004 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org< IDAThYkl~9g;%6i$\␔MRaiTĐIhB ƴ6@+5`B[/6ۍı$|}9&bI[N=9{}&B/6 6& N.oЯ- `\iIy&:ӒQ/ t&v3Ƌj2 UWDŽ&^>6*I!pJhGNLj6;EW]k_GK,!jV;,48mwFިWVYx.l@0p)dsLlVoK>1©N/Y??>UwiknRQ7;ȇODB/5H! c2~]kr|;d\F'pmEXU7[g0bMUh+޼=`;}%\kԶdƖ[]%mZxFűޤo߽3s{߼޹*MRX T kMfN6 f={gڊwŦmtef1`A2L4liQ)1ѺXo%:D#)#`OOmDث8`MـVW/57noo()cEN|>pxAU?x| ||!dҙEqXzU\͌'eAhB5#7qv۝%h24G_2 !<:Z0TAx;4;yNzt}rӏPSMM7noYsK:_64Q<ӓ=w|W _UwJ/Ғ@ePWB]иp1XJ+qCd6[sp ?*ܭJF!0yM!K>8 $ ON o\lpYh;1!\!Oi:!aBr.vtCIϺ?N%ŕ1k*eS5p\u帰v,V_*E& @X$9Rd\2FY^l0WJڟH`4ɅxUwhnXP!jr#: KA2OVV^.Y dF2tk6TX̥J.HjB]NEtCK5BVOXR@o9/}wr \2O$Js:#ךuC^ŗ91ݭ9XIRi@#T}1ѣΗx1ˌ>@k|O;ԿoT?8Bs"xz/&p9dnsZ B# R(!g_8s|)mBп+ uam Y;ٌfz_8w G)KŢY^WByFE/!CK"ٞ>c p05O wH#CK dZ8qRZAOxEJa<| _a⤴XL@LVhzA!!g7ƿ618-;4*b`y?ּHD%dLVH2eX t(s ^^݈)ȷAACд6ЉggXLSl!pKڙă5ICf@hkUxpkŋj3bL+^,hu|e 1=sx4`+d c2r&MqzE83%5's !9L3*P6 p\2/ /ܺaOID~b+r0 Qx=(Z~|H뭂7C;N̜9[+(>(;F uu;zr#(_%#;Voo>|pEڍ,%]5+UVBF%|  'j.VOĘ<Ȕbpsm&NNzIifF#k`U j8,NaYJoYWӛT"Fp~O)QD7AS4̅jXUUMtMlŀwnތ] IENDB`emelfm2-0.4.1/icons/plugin_crypt_48.png0000600000175000017500000000564510670641767016712 0ustar cairocairoPNG  IHDR00WsBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< "IDAThZ{TUU}{W\!\GY*VjZVLԬ114G)--Yj#-35 LTD@\{0!\EZ| l?<fȰ*o8HӨ(88ѣ̧2Eɰbi367'{:,Gx|-\00CUUJʰcN͝:7яlɓ'Ç/XtMGL׮jbDKTd$WM 6lr8wJ'-4uڔ6wDDWׇoݲbq'֑QP'o:| CHH(O uh~x,'O(W_yMsmJ]}…}ꔹVرcVN.MaݰS6Z~^7O2>ёf5|KO /xvZF={OmhwCzCH {z]䐧?{Kp9Nq%q#ȻmraQqdً"Qφ6׽}0:VX!f1Yt-W/JKc۶9t֦uIL/Fkh4P ޡ#z~'7>dYZ`o?pF"ïnUq5wF_OEH9Mam[;Ǽ:}66o٢hQt^p^;bEW/B"Wï8WjW\+P9x ْxKERr 䳺jzd8uPSe}qCf[Zr 4Jw1qN]mj>o„Y&M`hSgty睓 .x: Plq3y+ *kN竄Sc?8:1Z >,Z V!<;º8{b"<Bo[AnוSD@~k_C&)fw_oTT\xkٍobF~Q8)}TW~+@_Jy`0vF߇fo#kWyPUYy>X hVE`_)d45qYUAw̌zdDHw{`Zx2wMMZw] M!8|Dv{ %`!8|g١󽊷9,kNbKEEEtǙ T06=3]-XS]}SPq> ;N63^5Wg<0u:Y4MM͘*S!{ aNN@XN` yy4~q|I4OMM: amHa}0CQf ~gB$ER=cab^ޖs'Dtqbw'03wݮ_EHaQU^GtKfEJ84ÕDq}x .9r} sDh]e>~RV}2׼QP3UZf']?#z3( DKH؆  0 Amupt}|@lӬPz< \>9""j#NXw]edhhJmJ_!jTWۃ_zxL8`lL睚'&XE . 絔gGihLhuR P?"am?ky+,r !߃8*Aq找_ӄ版Vlau뒛SiI`zh0nՖ& @+] |$O~ שv!Lnnf!i8%Z:$"T}K`Ƣbn]aoodEW"pJ=xk熐'P}yʵۇ@DW,&?ߵlqڗ#d f)q&qؼ~[V0,醅kXP A:N-6v0ii!H'6vg]Gsz^Z|پT!Pkb{{*swo|Ѱ.Ds<[ʲƶ| ?Ɯ OuǘUwJOlȴ,U~c˗|R5ٜ[Y~KW>MĽy#;h02B'u69;)N?\Ŧ{)#|`bq<8;WTY|֍yWLÇl.S=ifg`$}Znfo,㇑3 dCёB֬ѫ)l, >M c!gpbe9{Mw_OiAB] Gӑ*}r-C@p x7e2.$OZ}4j:x9"85dem-iF&UG$&p  NI^<څn4!  5YDԜ{o&H^q{rhiB>Vm̵H}%>[t]`=`9J~orjp1J'4TIENDB`emelfm2-0.4.1/icons/split_horiz_48.png0000600000175000017500000000102510521644703016511 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org<IDAThOKQk<5ň`!saKUienH"p*WѾٗ>)iiI j7^λsA 8ݠz\e9UXmYg/ͻp$,L~ߧHXͽ!.Z!DO8Lr@ruj{6x` ?GKWȀhX8$ʁNwB9  HeeLUS,H{j՚#]*A\5zQM $  TI|-ÈUNL@۞NVc;JI?XzĢ!!J+~KyHAnjΚyH!f v7G^!ɿ"3s $   Sv`AY߁l`IENDB`emelfm2-0.4.1/icons/plugin_extsort_48.png0000600000175000017500000000314610521644703017237 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org<IDAThՙleǿu[ `bz㗉`h``LE2`?N@Af$R@!2"Y .)8Ԁu[w>vtmikiIKw~3\R";]쾌0Mi80>3=҉l<5Q``WS85bL0(P&$uP"ցSAF MI/Sjw` b7O;0Zy&1=3sFD#bxM=P+K6x_|vB:A jf_`D.)$A9ƂgmY4\zg?woiSE/綾XQOm|lA/&b"=l}m 1pS{KY2'9ig[@t]jzRz!"t K(@m`z:@ Ps:@onEu%w󾓁Y;N @xSFcJfV|_Z%H@+3:׼޷5 =wj+@ٻo aab"U>]N4l' ZH[[XmSUۖ"!P鉶׋}1oWǑ$iJ;KǗ I0s',_-~{I̦kY#9:Vo\0jAﱘ"IcƜMD4W6&vrU+O:]I}SR~3VaTuƋV+'vfr$_Ċ#" {|(nyx'@N$kڝ!r;˯RnM ^QSSų=w3kXI͔ƾcXջyKn&i. D x6(޻<h3CUmu#@@6_NW5jX+8BLRb$rސo* eO BϲLe)\J)]=ӨRշ[@ F$Ev0lm$"*siTn_OH󳮺j=űzv vE&. <}|9(nϢRw$Z4g` ![BZU,P<@]ڸT W/\hY@r/̯C'E%xF`ۋO'^< NTx@ᗙ`.0Vz~zm}ki@*u|A֒YsϚ' ÀI p$"l];wuL)tܫe00vxzрhLp#J/ƕ_lT{}|>]]0w% nLVV7Θ]Orɽ<|C@8D⑺/@Jq ([hiK:yf^{/୷{ع}h> t/"L?6\O֨۵k+0?,صk ^rۻn}Ef .s|jٞ{;P,;81TkgmOkTBlƧv͍24Z߈={m=:\mnǻsFwRDϟѨ&s{<ܷNOWFx5sj TW/It2M44|Պ7"Q)}_5,p "$Dm  Y a aY "R Lj|!46|iBRw%;{dA;fGmuiC Q;ƶ<`$%H)ۆJ$ lTӥ,  ЧM4SF>l<؏N@S2ywϛ cfO,fZø..>uaQX( "z67oQ>Yc%vH?3ngmM_ VjބP(Ⱦ}fσaY==й{^>ZD)#ScKH *?"؏<>(#XkrЎ?^g']]ihǁ h 3b~ΜeLƹvtv2@[=iFk:U() ""IOJ)߻Ll)Wp`ԲHIɅ<籓lccnK{{bu6*vG B56n:kn{LwOHRZ%BR,Sn'jԞ|Sw@ z{_Z,m&Z,\jVx]3oyĉ7㶍TSӦO$?d6}1_Jh`$ě2 \()0gEF]q WoпAf IENDB`emelfm2-0.4.1/icons/user_commands_48.png0000600000175000017500000000745410521644703017016 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org<IDAThYypՙ=sh$5ǖ010L )Ɇ,l6U &a,V8lfرe[ddux4z1ьd9^0"|oؤ;%ߘJj9疪*\N'Cm_Z#0Ae.?,˺\TdW1p8];4MضeY?ӴORiΥGoڦYa `eIc "`Y@`y=q"i5B`ٲ磌km=_t.(.w(Hp0Hm,`mG C?KuwS00OS}S4'z<OͷbEQqHt1qs!eZ-|ի(j\pWUU!*2 |DX}PSǺu7Gŵiw8t 2>s&UL*HmlZ<3\s:]܆߼Pq8JK}s? r3eek̯qhh,]Hc:eda))(Ӟ?9"aJؖQU]NJ ^So.<0Š_>Ya\_9?}(0] L1 t25 ] Fm6@%ر}Lƭ|W,aAD aFeL}`kyuUcRD`f%/^Yf x|zȲM6aMmm,s&p6lGTV?C#? ϗd18H{L>attBdH_#>dfԾښojs{Pp$I9{@ĦnN\UuB*r- G0]bF itq}!ONzpcL%"}^д jm##V>(iP[z ~'t}cGnz>}^LzԾ63} DSⰉ\潄O뇱1&C[^4A7::C£k&+-CK]/Z0lyx823_{-/]H@W~f9ej!ITU- Y1} }`\B ,p@gpM'`{:jPZf_ՓR9A4 (F3\ IR LyE1l,]%M]lA*gle~rgz 56~R8Z, ~奕()4RF #EEI ~~ #,tβY !_?0t 7mcYpki4B=;>^" I@q|l \<O/8*rqsS ۰ bG@ς/ : 4Θc}qkoML7ŵ.'ࢮ|&Hlg+W\W(c_l$\QDCp:=(**]M (9 V?f?'8BDs1>_wOLC. =pI7i/Bf$:zQ] 勡B-{;v@yOwcqZQg?e֝H*S@8}ꊂ'%`lUU\4_,V<29'b_1$m iY~?3 γcԨN<\Yfmk5'h%S󚙗Gg⛞Kx81~c\*猈"fY^I]e!ĺe |'5ds׹Zҧ"!d ΅q{c^Kf_-<ĊdG|oU-urnzJ8U1HQ \;'Mk3ԆwmpG΂q iZho[ťGM㱃ɩzP8nI0Ľ,xm#d71Bw2j'dZ2EWrRu#I %e6ؽH4 'o#]f~qh(u>E,pd^e)/ժNyP"";wEK#-X' >ld 3v6 <p$}"p8lbMQyF|Q?|~Г1'>1sX[O*inS.$rg, "pbrl҃Xof% | Yh>vsBO¶`z꽁TSshD&f^>%QkReCIENDB`emelfm2-0.4.1/icons/symlink_48.png0000600000175000017500000000150110521644703015630 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org<IDAThՙoAc_2DQ T:DAhA4C  -Ф*I@N{>ǾϾݻus{3vfvƼpWͅITjxZ%O=w)?w\hR;h89 Zz!5uL4*Bb]/Չ<DQ40V7.68"D{ bN qh@AƠeJA :9ZCo%^m =@jSH~|SMB0 &OI3 !'RhKQ5y.Ks>XNJq`E 8D)IvR tvlw {Ɲ5jLB)qjӥ`;̹= cx T]򲐙Y Xc`Q|[xTW/v<ےfߝyxra'J=3{x;nijleNHE=\x +@wb2,Z#KRHf+߲ JQEY_`U;F@RhfsAcs̢;IlvP~\ Xn'C`[8~0n4(t d2 %C񻝹oܽxq큕`k=e֓L/XiIENDB`emelfm2-0.4.1/icons/open_with_48.png0000600000175000017500000000557310521644703016153 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org< IDAThZil\{oxov n-BhIX,VjiZ$ h*R(R(- KKHb;qlg<Y=1 HiRG=}}=3OÈ&ikʑ&^ֹKzu{alx `-tyE15$ڥDl ;BpMJ ûGT5-D# 󼖕|3;[@ud*tGT؊OYu͸w5 κ1 0HPCޭ'0@RC,Kg/[4l{%4%/GO! ~aC~6/a 4fq\R?(Sm`zE,ZWŪ^QmLa Hjzg$o}&}?V.^p؆C94$4&nf O9H\?sdks_8Ka' NA;C9\SQ*&ç\ҝ-#8*qRc*]̉.5$}VپMq9 ˒ω8Xv*97`~+N).=|f8s O;_r-YZntjCvt<*2kXi~ARgmP-Pv Ȕ1b N U񠤰?w|kɲeA= yN_Xsf}\>FM?|’t:W$՞B̺LJ:O֡[n&*`V 耍=/y>/+2Mx5}A|\=TIeMȅ{.ao4qkuqyac\e۾e %?GgK9ψN\z_3 Gwڀ 3&5P9hˣ}0J>\"dh iw$  +`VWe)*➃iY|fdNzyŪ$*aE` !h w}'ȻBTcCU`)D3H޶-6"YrmCߣ$"cÀ\$%;2C8,IgOfa|Q^YW;l(a^eQ1ܵ=]09 O!j`6 7զ Hj̀TI㞇Ki=8&r/hjxe5b8ΆWrQY oU6v55 55r\{F]/:y`W]{헯9}zaUle"T Og"K-lVKO`w:5^j 'tyk 3~ ỳ@}?̘}"Ȩ\UW'8n͖BַKJR;O<9'y0⸔f!3;*ccL!YU6P`976[߻z)Mﱀ̢8@ƘYu00 j(OSo;y7GdFeu$ e@o^Q$!41F1#Z[h[ݴ?H㘎 BHR̍?>ouZr Adz-̀}f Wu9z!Ƙto[KmF DvSJjc?c`܉YB(޳\M.~"*-%4ce,Eap=76stxP.Q듉zܒp*asw w̬ė;xMXziZH\> %փA#u<ܑ1\B;osd%5]'=8ZۙM/IAȡ0pvO>_/~-iz75~xR]J%$C!D$]13R\߇JhN>106-,!|jdlӠ\,Xo ɲ&1+Zzl8<-6eEᓣ1&:Y-DLVu%өvFc=΂mZP<>;}=pMJNaАuW8Ζ} uBྼd|$?K2np,1DTbgY5*@Dž-i2*b=h͊X<(:E1،1;syD4`h%5/l3a4kɳ7}ڽf;Kv%|OcϿS.KOx[%MEhcl#*](ڬJAvqp,se xCVa7a  SIENDB`emelfm2-0.4.1/icons/cl_clear_48.png0000600000175000017500000000501010521644703015705 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org< IDATh[\G_{wO/ώ$q؄$ BC@a6ObWe "e J6 $q3KOOSCq٣HKR*NZoo7 Xo\x ph``x(h20̕ƋKW%!ED ](nڑnܪ(1Ƥf5]ҹ95_^4u\יGt[?<]/.tsA`͇QJm39hKũ ᡡf2Cwu dg<(\DB#iRn=s~[]4fa_fdhe i6ʐl3ʆ"?"C"/"#(jqJB%PnT'/yň֏Ƌ0o0Dz:]\G~(G./#0֐Dh-'ds~ K62A QJ!J.JP(D՝ D86{/uR=k;JL&zˊMQTG\C^˔K B/$ :J)qPjqbBq6p$;+:t`{.)&] ViS:u=urE) |ZOOx<|s\\:N@=4uLDƏ r8ʹ؛]C4bHhᮾ}whUkx吗K[rY:,B5E\WrpDƐm }J= ~02`$jEQ^$ w?$‚1pH=#1{wh P+|SN5 B!Tf>O,҂_-ʭj‡Yl!d~ͨ@t`S79xmFG +G؛8@0;uRt,;naL\9NJgM,Tļ !x1 \IkU;AZhӁ $o۶^L~4^C\{tɦ0}ǘI!i KeM!6ĘRj[W2J9t E[W9D'ߖƋg*s;w\+ycЌ]$M;x7:5Jec\*+K7Ci&iM('~CcQWLN|`oiU|[7e1NOFA))r8Pc͡SAXulK\ haC۟YqV" n"ݱ+QXZ_SXj 쩨Q *tWτ8ps ~s޵ xЌҚ>g<8c{Z]I?=8,F+nzߨ>rlbfշ;=nGX~uҟө+~C7[1|-GW<rwHwƈ} w楃LL$ZDZk< e Vrݻ-@{E^8놷Cg??t@HzH}_Z}G{LhRmŷ8ȵ@ zdb!.ZvlN1'f_.2ʍlaߏuXW=Pb7x_Y&aw{0m{]^M,ck6>MFR*::(\xuZ'W00D@2t&΋gp?RJ[a6{ѕ r[gN|24ZBDB (/`+c q\7p7Ū?iq@ PήEZӉ ]u9k|k笵e`Оۀ6uؙ#Ťk3 >#He7V[9Z'ԯ`{Y:1J8쏜kOSFUjc5Y8%ZÅA s[!v̿yr Tӫ4țCd%#;_L7 XoO4IENDB`emelfm2-0.4.1/icons/terminal_48.png0000600000175000017500000000544210521644703015765 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org< IDAThKeUUUsv7 CqH$`$"D`bHH B<`ALxHAв4{v={WŠ9v#YRR]gsYjmi>wr'ݵWwkιd]f61Z&:QJՂ8TD"D"DA"B+"f⫳Y7^S,f~3'V|pp3'c_bevu=qtt*0+&5T$0<$QU{PMm$b1v=}֝=67v۷g־>Tߏ ??Nn?cOg+tf - k367hcs/x>ȅg>r0K,a68hq|#V9Hn~BD*ȇ.?6Y/̛9aC5 ~0kk+a٣[7us۷wظ6 x U@9m#vFFYC۶f^761!h9-wz8uzgW9<&+,P܉ڗon'Wd ODM)2kf53fG4mCJ9SJ1 )yb}uK)SoDuO^ҥSc~gB.PM©tAW_G棼os7ϸ!U"(R;DN%Tb6lŋ'(YX[n: ύ+W^;ׯr{k__>ENaYsƺ?;s:[ Y"߻~w)7^BXj;9]t߹|>ox*1HYR9R~-[F *R2 ĉ^דBIͽ;*^Iܳ|hSD{$w(U4M^ujNtdm}}ٌm'3|O&4%TLjh U@92zm,%\EHlm::(ml䔁6)Q:1LHp(-)(Rx^^1+a hږx_??1Ŏ (u)esc[ʟD6dݔPi:]3R/$E{J1/R-}'[8ȐN@̰p8Dwn0k7_7IdG:\{zc5ʯb1e4+@Mf"© AwN|4_W+Pb,q]4{}>F@$A֠L!(1 AKW~+W Xg7]"<&dydQUe05RhVB2[T,nJ($+4!/K^*M/ 5+_ifL4/tCkf-`A3MSB1#0FX]hd/&ubs\[ hU1aE6̃j -2\.,AX"@)P#s.yCy`E.{\ȿQUC„@E`^ﻢv)JK\821 ~.sZ΃ET5P(g@YeZ^o @cĬr-S(#X@{ZʃJE.W9 Es2,vN ]q߽Ę Z@25f_DU%En;< E$BG6(xaNo" jt2*{{;sES=q].??ݸܜ?J/LƝbF)%֌>\z;k[<0 KDM%Mw )l.O~"{-mK,4411fP\pxp"'*bUTU@Ex))mVmlhCۤݣ9$.`дJ3SR4L EHsy=޻nB0/}s lM mnK+پ_˻Ԉm>a?v>*Rд `+f<|pd+8'<8֯˶&q7z][̿W8lx.o;r3 OBUz>iUȅU_kM]TP} qp87/^ύwoUڹGPlV> ޹{:8HH-PT@Tf`{3 x5)ك/21"BbΑ#qvo1Qͫm+f])u^DVsYpoorߎzE-31{On ķàIENDB`emelfm2-0.4.1/icons/add_mark_top_48.png0000600000175000017500000000346210521644703016576 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org<IDAThZ]$Uν5;3=?;fvaaQL>QH,T(Q1Dc$-!bb| Q1R6"efk=?5=S'tչsO713B,ۼU a1 'g&Ӊo=OLǿ|/O;y׏V,B3㓹/>r*@Yϟ{977w眼[fNeeg}יRms^FaW`rl }/ߙMBaӈ&pr|`47:;_<%FNi8_n,l+C '^Ug8v4<>[ۻ&`BҋRʍcԗ@caR]|w"`!q3~:G#bQ`κ~C[kɻΠĠm?ԷgfġkƲ$eӃ0 3󱌩Dѻ_5 !ΐm@8Ql6i7dҾ33ަiC7`;,ۼLH=sjb8;1qLs,~OSXG雜;<`٦vp<$SYG/B{qtz&ql"c)J l4dz(-ڙA +YT1F_r{鉀eB?)w wa2BhV%iW֘q ͎%%K!TPAC3X3X1Xkh5C))!%$B IRF'S#bYmGXy$~ `R (B h?d"e< ! $2TX~*`h,+$X`EI^ЊsQ[:m |ч p o7]Zh<ݟa7tzUBCf;ʞ ewtvB_>e>5'F6e3O_1ъ z11k0W㆙A11Fd `#D2l;X435 U`fjbA kiN];tDHL' ЂڢDX#nO3tj/t$pʴ8pezt;tfCt$@WM_^1m_ Vf E\. oG;L/4Oll`|M,;{`b$IXcH |s fol%ޣJc,BMׇ¸V@PgEx\m\GE¥Џ ]:"QZፕ~WN#hȑ3x߬,% ?." =j?~F κ$jMf `&b>z}NWb^F$P,Je$ 4bx W$@}]Qac94e7N=x5w%IENDB`emelfm2-0.4.1/icons/permissions_48.png0000600000175000017500000000726410521644703016531 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org<FIDAThZ{u==k`S @TBUBA})jTDI4R1@ҀҔ@IhlU !k]<޵=q9c}ks{wι~&FTH/L*vX(~R.J$@/,I >P)WF} osm۷:tyfh8c܅x}S+A~R>q6'DxWmPJi~7/JlT"_#靻'z\ӺuSgs?}=4@wWEl gl FR=2 Zpo:^aE DdD]T/䯤F0qwf^* 7U ͏dl~>aqa m[o1λ=926˶}gk!m=zݖ9L)8ڹq. ˏf2aGO>j2afg pzgr=|mjts k@k 7r 1)weϭGq3y:4a ZgM[]ag]}Rlk񾏰K`(~`. {/szoihӃmʼn(%blʍ ;HD J'7p{w&79jm'ǶL0۱#pgv(߲cє׍,a>\,sP gzGfd5 ͎%V#.[0 ޷;ޘj}^ CHfkɈ]nn svA_qe$ffjF"P,Ơ…%L9ڏEj&bF0LfmV O;fHу;qhIG ܻ}j;Y/zeτUD&9;-ȌR?鎕퉬ek'0!pG:︩!g&,[=vdz)Fq{JibӬ8ě-lGsg୅^:u ,m0E#ȩ栓>P59kmWCCxj%m4f0)B2:F;rKrgRl5[s6\|RNuX](%"`axda/}nQMnN4S1ޓ;++N-M/b^_b,J"ph . @2 Зw]O 1 k܄W \?RH%{͓/M(v&/ͼlˎ:)WDŽE:)Cb rKik=)B_dˀJvv#M|nZNjAHsY#_ ǵ!*Ȍ1'1?,,++so6ZQr(')AxRnģl}7,'X Ev䝤3A62],r-&_xLJ eR3%VlW7|o# ,Cb K,)Z] -+ _Luy7J:^L(o_KݔKAs)LxSm$1l D:'K.k1rSu4E"Ҍ֒E!vbZ\]_?*ꂲ|ж('":jJGw\}54 \7Z(ZƻS6j™WzOv\6jprIi85y/̞lW3ox|a@ZYˉ3pRѳac!-Gd N*Nk_78Z~cbbIAax 1a,"\ @^mW)D6h\I#/TW׳&=2Cf#`Y=;U` ay3@8 R)A"b!;&SN֊Ɖcuaf-m1;/MgU DbY&';؁ן󍖯T@6kb5|Al8׉aewH|Jd8!`EXAYR c7']ei֝=e_zyzTFKQ FKw4' p/ )6)tp^MV#@HktB#6quMهH%P)WkP49d{/݌$nGa=)BHٙO~~#"+gHѧ io^Xi 䋥¥эq׍-Nx3" @GzqaXzݤʦoKStvWv|1K hJ{u e\\= _=(MSXِ0^oX HY=b0b\JIc1Ta?"h@cB=y].,K4C?c;`HX9χ ͍DMoO8J0;"3j<.W&lڕRʦ?X*Hg\D#+A~ &?}X8* + FOIc+3.ukD*3M-0P)WFfksH' ɼp@r,:^;N)eRur+,P6fFF?@)(3-zY&g9zkyr_H!q3woP!q7NGý85M(k>b@ܛtɈܓg}07\Q,H@O.L{¦/7'pm㹃oJ=7 lwi{6 g}ɵ! t& Օ· ̂} TD`Wr}kF]-!E?#'ȿDҾ M(b-vY2|azU-0VD B©fkH@F_O ..a7 ,R)w{ZRTɚ=a-{s/Є~P"_nմ_:$Hd- pZ= s,V ~wvtE! !ׇ0į^i+ jM])W=̉f5ҟQfC_?Y?BZ RFgykD) |S[z2յGDFڕg~k-رhFz@ |uA`4ï4u3Jx JzX*Gk925 ٠Z}rUoHXkF|2!&Vշ87 BpQ6?C5bQem!H4%ǹ#q6R{9s|ιϐ[@k`qv㮁\{>/dnԀ)Jw)g^K5@$]>a)zó>spڔrN\XL\ϥ3f-#=8 ETvw[vڧXIuW8#FW2|bzϩ+nl(a5R.ON/,I뫃va:PJ~b8wf*k* zw-)FE.,WDDvNOʭMkr4S,"u{_w5*T ;gԱjH?i安Wm5 S.b_l~shzz.u54xղI_i_˳oZ4(?^ᤣpTLQN7ΨQo~장U7_-_\L|Y%hw' A$SiTH0!P/ 3^ۤVS6ˆn`9 #MTb!cn%vDU9H\AxNWY @ѦMYYi`pJ _&+{0S U{r#boLM.+/y5# CY0=i^=U[mm.F|9fPұ{V+!][|zXL5 rd4A6YTÜwGe(biԼȭH8ɱo9jG9`"ᱸL {AU-+5 ,%4LD%1Odu7h`kmq5:a{QվVFc {70+I+ կgft2Oi;nGGǘ}I4s$>d#na9_eYq3G>F73H5jJ>ÄWG~6)]>p[kN=-m~G+)̗&xS26a5rΑD${؜ߒx\I&S}LQL^@Wתc!c~[~v0{a ӄ,3Cú~T% zA:xoяMFji9g\AR=_[gXmFX(j@Uz+p0j0%{ { ĒHdj+kho^@, /Hy"񣺺FFF*o'[L,r9-Ӊ?mgP`  $2 I1ñ7gA(B 9nAvCDpbve# }I@pDU n,}(0Oז-f+X9 \~ e=gsqhmU{KAbbtHIENDB`emelfm2-0.4.1/icons/output_hide_48.png0000600000175000017500000000432610521644703016503 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org<hIDAThZ[]WZsfd2zAj@hPjh*^VD -4H}/ߊh,>}N`g{}_kH"ߞ7L9X^Υj_5ɟhI^]uaݷY;t͈sy6 o~ R} &1~5٘G6e N ϵhE)yrZ#;)!#ڂi#V?J3ۨD_Z tb%7pMO8QPA~tZ]Z xqXkIAɝܻ_z#k1T20Jayy&_W|g*&_ ֬Y [G;X?pAcFWreRjɅ,ئS 9q /veUK8nxzR c6/Jk%s""/B8/ؕe ! A_RX H[rB6caok,KW@.c0xm2]_\ 2?VNJt"]~m!p!ҟ$m!tr %& %Ӏ~&~Ym-S e LA5BL΂&+ -DH@2xJ(`'5WR>I!/g֕,'k`0R6X( .Ju\8AGGU(\o.NKP,9]VIe1dmPww =:2r+zVq !Ŭm0%3'fobjv_yp+Vmԓl@h(9_6k( ,4Ǯ,Sc!2ymk0i q{ ve)kT 5{ۍvt{l ~PmXm~G-*suKUמ;+ZS}k$&=nS"C8}q5/.qU < 81Uu<TLv$xx)dJ~f3ޜ&NjXrT\r|0$="4X[X?!rчL +P2 Lͯ߫dft_,wŜi뇰yOW[͐A| I tX0xm8:,!F"۳YW{$ѣ.OӿIyy=x|;~%dϘ.uEޛWV:E=qpHXh^- n1\^z 't!%IENDB`emelfm2-0.4.1/icons/filter_48.png0000600000175000017500000000106310521644703015432 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org<IDAThX;N@q"A&M @I%9=K$$R"~Nq:?v$Kw=76]؆`U-عNYֆ:*]U}W"i f0tl.c{2@@U@鐱YpޅBἀB${Oxp t:eE60hLeN &!  t45Iy h ht:'I@1pUs@p10"XbW%%с: 5ZҾHJRT$T9$ٹm8/@M걑FC0Aֲ pp!ن&~"P &&0y:x!5 `E^ϣ&^r |xἀ_$:p IENDB`emelfm2-0.4.1/icons/dragsel.png0000600000175000017500000000037410521644703015257 0ustar cairocairoPNG  IHDRJLbKGD pHYs  tIME vIDATHK 0 g{7q]:.DB@(ʗ@ZJB::z5z@fR z7=/RswNP3#FFWWN˚3usu{cG 8B ZZs[i IENDB`emelfm2-0.4.1/icons/plugin_move_48.png0000600000175000017500000000442210521644703016473 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org<IDATh՘}p\UƟsͦIK22:6HERԱ`;*+ȗ3 2 # U QڒpCatġl-ߒ6%i4:iM6;sg{9=DP3 0x0j\tuTb H2KBX@> R('/~\ $+W3>~\C"xK k ЄA # BA R6o8%h:ivJ)8JGKoj@XL[q+} @N[HB4@ ,3@g n%^!u,eS 0[שD .dF 7}炅+6πq+uvF2`1}7e9ZZcÆ{>m\Ti gFyqB8Sglt.WU7;_Tl- *֭@ Kp"՜@=m$"c;ma!0 nCKlgHz+ xwG 0*}n~YpBB7-6фoI (\}1lB 0H.Cus*ө+Ea\v5k \%xD`@i-@C{~+^";D!,FX]\w"w yDDrDcb 2(pMDtQ 46^N3tooM_Q0G ~嗟(S ")yk3G)$D3E8èRzI P>V#;qaѧd2'Uĭ.C* AWk/4z4{[?O2o΋y@S/8UϤә_wW:h?2#P}p? ?&40lT^ʩ}H< aGa ОNtAD_M,rk}/<  { wȍ̙8ݼ'm]u{B:0UQPok(F})5Egw@FD刌YJ{av[RZvYÝU^ý;[BP^Lm ZwV{TW"~:tL)pg2D|}[gxTar-IENDB`emelfm2-0.4.1/icons/ps_48.png0000600000175000017500000000676310521644703014603 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org< IDAThYiTTWުSխ*PQQD(DԧIEI%_:qHKb8Ѭ$;YqcZ;1QQ&AA@FB5~|C[9g߽s\!?gP'vXcD5ܮA' Ap̘?dumZƸh2H:z- 7v]. .5ulLxܦhF4]V'>:+Y~LD o洁RP\k_Ym;u"7 c))`Y-換m )UQc&8vGG^j#@oܐ6z[o[ة+s!PR3UOe1| QYR I S*n93āg-.? f5݉iduqo,˲nu }\%Nĸhd">W7E 4"~0(EO$^G$IBP`G}}/< Zfb:"55x`m!ATr iS .b!uvI Ƣ`g.nQAĠ(QH+鬿H˲ؘh4;@A3bè,w+T>ۉg97鐛Qeb՜oL<1 F$#|*(qbԄ((U4avl)ZQ&,v/F؊è*1n1zHi4ѭxiS&Oj*=BgFY3+" hu(Q틷aeߧAt9y!2%X <{uVxi;]oNԿL=\O&c>O-|w4ɯHvYN,$~L2dѷ ٽɀe^6~l[[㹟_=#Ӈ#~ش֙hsoStz-S'/Nwt Z, Y;LB<n5Ii'P;RB9,*JP  oy1z "@57Z2Uؕx寋p5vRdWgr؟eiJ#N nC<z/ F)Ŕc?]814B3B~m6|{-u6gOyE%T*u\NȜ?e^9mx{JS0,f0*%rpڂ'-ġGQK7.(—w#7Ϻj5ƴXZ@Ԁ[i32!{ieYս!~k04:MeƼn y AD Ci#-Jucސ,t -VAˈ-ym] W^oocp(ʒjܯԪo8#h  łBp4 zK7~b(t"J H'uY;wKaB}{kc9A!9l؏?+y|y dWgu#yØbdy]bhg9ص7 9uJTy9W 5"'B&2+ 6d._vb.8 ;~! ߮Z䑬>,7:ܱ`Z 0,(ӳiKPuπĤ JA!6d`b<cF,JIߠ_Z˫|558'N&ǒM'6wj+2K'S8?>, #7*n(}(/ :c?fYyzpzyvսdp61"dx OQn Btϊn. /[xy$T$87vVfl o)VDX0>EUu8ֽc]%ey$h9%3q_oXGB! 4p9Hxv ΪygƬ{ O"d]&iddx)qXO8Y :Qu;6F=1AaDȲ b4N`ÁҲrX,Ō+GRZbݺu! /@|M,4Mc3<6|i9RhhxT^!"TEIjMҝ[[[;:rt]ˠ\tCC`OEEE ȘZ(o SW=ĊN5Y3͓kup:<9Y ;+z{2s-dI,B ;+SQ +4kNrNɛD*>~Cojx4A('4&t`ƅMU> P}k}9S剛T]x]ظyt|i[tCF2a!,ˎ,3U>"Jb"x4Of_-q9Y2o ~6{VGڅOxg.~v|l6[!44Mؽw?^na1qRǒ3gٜP+!ض \vm]tz;yg>(,͆(Ɏcаe5qz$P$=StF.zV&_3w[V|A[Lxl Hg$d=g |׃bvoXg;gŒL8@ 2$4M!>8n NE |B~yP^.cRF/ﴙI[3@BtGT|?"1z`1 ٺU&M 33@ p*n"qZ"afI;ۛ Ϩ¥~OHҙE=@5To>-euwmπ6PUG% C'iV!!eTf!:Te>7F2t(xk#)?n[.Z;<;i`S$NӮʆ,G.jRxS_\ሓ HCa6EMYpĖD:hj8r83|J\.aiZ&[t]Z,Po#0\?wiZՃW^<R~Q%ݭýG:֌:6;۽c_j۹(9TU!|x[hZhPb?dĤ`؄OTotHuv~W$?}m, ߧ?pSwIENDB`emelfm2-0.4.1/icons/split_vert_48.png0000600000175000017500000000076610521644703016351 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org<IDATh혿K@?1iҊRv[Q BANo N,v*CP:"D;CI|EZ(M*FycH|ܾpvқrU((Q\-OtVZBy &J-/]:^yE R>/eÕ@\\UTe\@n>e$0)t>ʄkFsf t xǠ+I/}#fKY;$1бU*tDk! 㬓mEfBhodWfwPhqUV.ȾF{#0bU@dnD4~Oxќy}˫0]W Xu0H mwF'TzOx?h\j҅9Abu aVI6ˍkE{wԆvcCk~qzm{|}c2U#&R/63~ Z_?@`<(eٶk= >i6-'^'  ]ug:ܑ;$ ձv±(&Ҥ&BQhCٳ!@F H"kCP,M'|/W/1Xӿ&Í̋o#&}4ZTrhEV5F#hY`žBauyG+F|%J RE0SԷ53~r=Pb{cd O{ߩ U@N+DNr)E``Ime_o̔mE !+KҤ S> hy AQA&/(bdΑo4l4g^yH- M2\!H(Qp)l/E_ F6dZ^G68|Z5`N` Ew%o-i< >Z6:& TQG$hڐ>P@nr;-U+d. /C" DP ];4Ӥќ9I=pZhtT>LH+%2eWj~1b9!L&e0zR&)LY;#r+:}<~.&Zilnυd467 "X )| V'nYW3‚U so-H굣E(H` J`(Kȫ"'BkSD_7V_e\}h_~ZlTx-$@xH` >R5fǾ{oΖIE8XD@ &@rH $BPX|FFS/Vo@ 򱡟9,s! /A#LB& !BDn6nhD[[/>+T[sJOO *9YQ!yPQ OefxAKk -M@ԫ `͝7z K^Х2g"d^%FF|}%Neb n|!k f^azΓĔg.A 2BbIwiRsGQ!EIP jbƵ{Wo.UL>cFE˕"qG f&_; _ܙ.jOM33kl~,G/?Hk+0_|w)20ۏ7ӤoҤ~95~`7M}7`/=jcO~o@66i!X驀xۄ9p`|"M}!ѱw"Q8ȷZ+f'ϾO(=FNnt I@wƤL& W_-Ɔ>3"@)u;.>l!LS!˫W&췁x-޵M7WBި'Πz'3>rU6cժ/W&Ƨǎj8B(y Bgg# FsW#^R_e/w||M-dnc׾?z23/ ~ڑL=_gIENDB`emelfm2-0.4.1/icons/filter_off_48.png0000600000175000017500000000330310521644703016263 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org<UIDATh͘[l#W8v1ٮEU\WZUeA]BU_ UUK7!PQH-jT\ZZjiQnMwMǎϙ؉/38fFg9.#ZD&'-% nn xS;oVr9W6\9& &g{ ܵ[wg8x'=K. K@`JQff6~8S9>x׷G+SvG'bj=U40|Mj8&zT$rDBNMgF1ukj}3]Nk=UG ֲUHVm\r:ompSײ bB:zs#;eh?Ӹ$ځ5a&0&rhx%S,?⥎VETW"օZH2xMvDa,i, H@0=- @RXs{…ثԋ[`Z=8= @p<8ϖJ7wތ%g[/.d2PYp~3j=$!1F8>ЀvtqFBۄ֙A'z:cddqϣcl'|fv1.ԪWEoRKz)|&ĭZ ԁuT𱶂͋Rd*%"q뺮u,I͛ !^eko*Q*Hi=)cPFt b(O stCo T+sycx{J{[Ǟs oe‰_w^cNs'1uyƍ$S.t Fz}_pfAW"h1t,֨ox7P.?4IENDB`emelfm2-0.4.1/icons/plugin_dircmp_48.png0000600000175000017500000000514210521644703017003 0ustar cairocairoPNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org< IDAThՙkpT=MB0-Y h[/ tv:3/NYM[J:SqBҋ"fawS($4ݳO?fs &9{yFD2Kk3՗@4%0 DӔ|Vm& 2x ĦҞM%s[Tķ\b^t]h74x|ixzo͢$N[}G1iVM5+aZ`/w`xZI7c>/+.mTaaٰb*x5Wf;Y~MΪu78}k A4O ȠY.c;8ĄϺeIpmfgUE{ PZFCtd4(gu@[VU:(ĻԦ*,y %Yݟv=/7c@c~R^=Y>ku5AϽ78d+ڲpWA M ku풬_iESn#]eA*& hDM5F7 ]z91ծ+L 0#dpT2VU.pw(1&v6H rMbD, DӠh hp ѣ:ǣ aG > ?%r!D\Hr"*C*<J= x`Zݩ#C 0]=h#m?y>YGR<~a#˯w"4?`SsP_n%۪]! BbFnij٦I)TB1 \8`O*e_e`W*] a74Ʈ UO-%*a" 06eg!1_al>! lpX+ jbrrI ^iGxhNgޯzDCvIg._dThgbq&,UTn Ƀi7`9\N$ -lz _|'O [Τ}z#㪀Esvd32-uT8R8# h)'"q}@Dlb(vUKL/tT&Pkw8ׯ(4m-} dhODbBۏw[q\b(ޯ'GF ̤`y#q+.~k9\eEkzfҸ8bўB;Oxڴ|<eT.$$áп WӜ4[\Y02t%sgލ"cjeVy$ Eg<_L]no(,L]X4oR}Lx?T8Oh*krL!x3 UUCoGGh,20N^.73d3b5:QO5qS0)rr7`MIYpNd4ĵt(4{c\Q;ͦȧ3fH7yf\ݔs$[{;{׫6TbQ"8`ן8ug:GA<Qax J"\1R {Oӧ]KEI,;Oip-=Y{$aUcͅk@ ;[ H@jpaޚ5[k*YF1[/A@H ;A/df]Oc_Q  ʤLh`$k4عn۽툪6/eE\8mgS`B; 6H- ˫1h8VW8w <,DehCkm/jofkIM@0L7I3)ɖ+Ѥ;Vh3Քlp%H>A$?e$YBcy 3ªU2~fޤ'kDk;l- Tun7VeA (8 оgm xܤosLL@]1iEEb\zb&#s DKDso~ɹF|֝N"m hdh0AUş # # This file is part of emelFM2. # emelFM2 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 3, or (at your option) # any later version. # # emelFM2 is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with emelFM2; see the file GPL. If not, contact the Free Software # Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # This is the Makefile of emelFM2. You should not change it! # Edit the file Makefile.config to change build options. # include user configuration include Makefile.config # this is effectively obsolete ICON_SIZE = 48 # internal configuration DIRS = src src/actions src/build src/command src/command/complete src/config \ src/dialogs src/filesystem src/utils LIBS = plugins OPTLIBS = optional ICONS = icons OBJECTS_DIR = objs TARGET = emelfm2 API_DOC_DIR = docs/api # build info for build.h VERSION=0.4.1 #this is effectively a sub-version, to manage config file upgrades while not changing the version RELEASE= #RELEASE=.1 PROGNAME=emelFM2 BUILDDATE=$(shell date) BUILDSTAMP=$(shell date +"%02d %b %04Y") BUILDINFO=$(shell uname) $(shell uname -r)/$(shell uname -m) COPYRIGHT=2003-$(shell date +"%04Y"), tooar #these are only used here for installation purposes BIN_DIR = $(PREFIX)/bin MAN_DIR = $(PREFIX)/share/man/man1 #as well as usage here, all in-code uses of these is assumed localised ifeq ($(DOCS_VERSION), 1) DOC_DIR = $(PREFIX)/share/doc/$(TARGET)-$(VERSION) else DOC_DIR = $(PREFIX)/share/doc/$(TARGET) endif #FIXME By default, apps should look for icons in $XDG_DATA_DIRS/icons and in /usr/share/pixmaps (in that order). ICON_DIR = $(PREFIX)/share/pixmaps/$(TARGET) LOCALE_DIR = $(PREFIX)/share/locale PLUGINS_DIR = $(PREFIX)/lib/$(TARGET)/$(LIBS) # optional plugins (and related icons, below) PLUGFILEPREFIX=e2p_ ICONFILEPREFIX=plugin_ BASENAME1:=thumbs BASENAME2:=track BASENAME3:=acl BASENAME4:=vfs LIBS_XSOURCES= LIBS_XHEADERS= # when doing make install, make i18n etc don't want to have to supply optional plugin parameters ifeq ($(WITH_THUMBS),0) WITH_THUMBS:=$(shell test -f $(OBJECTS_DIR)/$(LIBS)/$(OPTLIBS)/$(PLUGFILEPREFIX)$(BASENAME1).so && echo "1" || echo "0" 2>&1) endif ifeq ($(WITH_TRACKER),0) WITH_TRACKER:=$(shell test -f $(OBJECTS_DIR)/$(LIBS)/$(OPTLIBS)/$(PLUGFILEPREFIX)$(BASENAME2).so && echo "1" || echo "0" 2>&1) endif ifeq ($(WITH_ACL),0) WITH_ACL:=$(shell test -f $(OBJECTS_DIR)/$(LIBS)/$(OPTLIBS)/$(PLUGFILEPREFIX)$(BASENAME3).so && echo "1" || echo "0" 2>&1) endif ifeq ($(WITH_VFS),0) WITH_VFS:=$(shell test -f $(OBJECTS_DIR)/$(LIBS)/$(OPTLIBS)/$(PLUGFILEPREFIX)$(BASENAME4).so && echo "1" || echo "0" 2>&1) endif ifeq ($(WITH_THUMBS),1) LIBS_XSOURCES += $(wildcard $(LIBS)/$(OPTLIBS)/$(PLUGFILEPREFIX)$(BASENAME1)*.c) LIBS_XHEADERS += $(wildcard $(LIBS)/$(OPTLIBS)/$(PLUGFILEPREFIX)$(BASENAME1)*.h) endif ifeq ($(WITH_TRACKER),1) LIBS_XSOURCES += $(wildcard $(LIBS)/$(OPTLIBS)/$(PLUGFILEPREFIX)$(BASENAME2)*.c) LIBS_XHEADERS += $(wildcard $(LIBS)/$(OPTLIBS)/$(PLUGFILEPREFIX)$(BASENAME2)*.h) endif ifeq ($(WITH_ACL),1) LIBS_XSOURCES += $(wildcard $(LIBS)/$(OPTLIBS)/$(PLUGFILEPREFIX)$(BASENAME3)*.c) LIBS_XHEADERS += $(wildcard $(LIBS)/$(OPTLIBS)/$(PLUGFILEPREFIX)$(BASENAME3)*.h) endif ifeq ($(WITH_VFS),1) LIBS_XSOURCES += $(wildcard $(LIBS)/$(OPTLIBS)/$(PLUGFILEPREFIX)$(BASENAME4)*.c) endif # object directories that have to be created MKOBJDIRS = $(foreach dir, $(DIRS) $(LIBS), $(OBJECTS_DIR)/$(dir)) $(OBJECTS_DIR)/$(LIBS)/$(OPTLIBS) SOURCES = $(foreach dir, $(DIRS), $(wildcard $(dir)/*.c)) HEADERS = $(foreach dir, $(DIRS), $(wildcard $(dir)/*.h)) OBJECTS = $(SOURCES:%.c=$(OBJECTS_DIR)/%.o) LIBS_SOURCES = $(foreach dir, $(LIBS), $(wildcard $(dir)/*.c)) $(LIBS_XSOURCES) LIBS_HEADERS = $(foreach dir, $(LIBS), $(wildcard $(dir)/*.h)) $(LIBS_XHEADERS) # this includes none of any optional extra plugins, workaround provided below LIBS_OBJECTS = $(LIBS_SOURCES:%.c=$(OBJECTS_DIR)/%.so) DEP_FILES = $(SOURCES:%.c=$(OBJECTS_DIR)/%.deps) LIBS_DEP_FILES = $(LIBS_SOURCES:%.c=$(OBJECTS_DIR)/%.deps) PO_DIR = po PO = $(wildcard $(PO_DIR)/*.po) MO = $(PO:%.po=%.mo) BUILD_FILE = src/build/build.h # local include directories LINC = $(foreach dir, $(DIRS), -I$(dir)) lCFLAGS = $(CFLAGS) #these are redundant now #lCFLAGS += -DGTK_DISABLE_DEPRECATED #lCFLAGS += -DGDK_DISABLE_DEPRECATED #lCFLAGS += -DG_DISABLE_DEPRECATED # set up debugging ifeq ($(DEBUG), 0) # this default level omits all warning-message code DEBUG_LEVEL ?= 2 else #this is the highest level DEBUG_LEVEL ?= 5 lCFLAGS += -g -O0 endif #gamin code is a superset of FAM code, so gamin needs fam as well ifeq ($(USE_GAMIN), 1) USE_FAM = 1 endif #FAM & gamin prevail over specific kernel monitoring ifeq ($(USE_FAM),1) WITH_KERNELFAM = 0 USE_INOTIFY = 0 USE_DNOTIFY = 0 endif ifeq ($(USE_DNOTIFY),1) WITH_KERNELFAM = 1 USE_INOTIFY = 0 else ifeq ($(WITH_KERNELFAM),1) WITH_KERNELFAM = 1 USE_INOTIFY = 1 endif endif #kernel-based monitoring also needs some of the fam code #but we have to handle that below, to prevent incorrect lLDFLAGS #other conditional defines set in $(BUILD_FILE) - see below # set up i18n BIN_XGETTEXT = $(shell which xgettext 2>/dev/null) ifeq ($(BIN_XGETTEXT), "") I18N = 0 endif BIN_MSGFMT = $(shell which msgfmt 2>/dev/null) ifeq ($(BIN_MSGFMT), "") I18N = 0 endif BIN_MSGMERGE = $(shell which msgmerge 2>/dev/null) ifeq ($(BIN_MSGMERGE), "") I18N = 0 endif ifeq ($(I18N), 1) lCFLAGS += -DENABLE_NLS endif # some code can be compiler-version-specific #GCCV=$(shell $(CC) -v 2>&1 | grep '^gcc version ' | sed 's/ (.*//; s/.* //') # set up flags # overflow protection with gcc4 #lCFLAGS += -D_FORTIFY_SOURCE lCFLAGS += -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_REENTRANT -I. $(LINC) ifeq ($(EDITOR_SPELLCHECK),1) lCFLAGS += $(shell pkg-config --cflags gtk+-2.0 gthread-2.0 gtkspell-2.0) else lCFLAGS += $(shell pkg-config --cflags gtk+-2.0 gthread-2.0) endif ifeq ($(WITH_HAL),1) lCFLAGS += $(shell pkg-config --cflags dbus-1 dbus-glib-1 hal hal-storage) endif lLIBS_CFLAGS += -shared -fPIC -DPIC ifeq ($(WITH_THUMBS),1) lLIBS_CFLAGS += $(shell pkg-config --cflags gimpthumb-2.0) endif #for vfs and gvfs plugin development ifeq ($(WITH_VFS),1) lLIBS_CFLAGS += $(shell pkg-config --cflags gio-2.0 gio-unix-2.0) else ifeq ($(WITH_GVFS),1) lLIBS_CFLAGS += $(shell pkg-config --cflags gio-2.0 gio-unix-2.0) endif endif lLDFLAGS = $(LDFLAGS) # -lrt needed for clock_gettime() lLIBS = $(shell pkg-config --libs gtk+-2.0 gthread-2.0 gmodule-2.0) -lrt ifeq ($(USE_FAM),1) lLIBS += -lfam endif ifeq ($(EDITOR_SPELLCHECK),1) lLIBS += -lgtkspell endif ifeq ($(WITH_THUMBS),1) lLIBS += $(shell pkg-config --libs gimpthumb-2.0) endif #ifeq ($(WITH_TRACKER),1) #lLIBS += $(shell pkg-config --libs ??) #endif ifeq ($(WITH_ACL),1) lLIBS += -lacl endif ifeq ($(WITH_HAL),1) lLIBS += $(shell pkg-config --libs dbus-1 dbus-glib-1 hal hal-storage) endif # for vfs & gvfs development ifeq ($(WITH_VFS),1) lLIBS += $(shell pkg-config --libs gio-2.0 gio-unix-2.0) else ifeq ($(WITH_GVFS),1) lLIBS += $(shell pkg-config --libs gio-2.0 gio-unix-2.0) endif endif # should not need translation OPSYS := $(shell uname) ifeq ($(OPSYS),FreeBSD) OSREL = $(shell sysctl -n kern.osreldate) lLIBS += $(shell if test $(OSREL) -lt 500041 ; then echo "-lgnugetopt"; fi) endif .PHONY: all plugins install install_plugins uninstall uninstall_plugins doc \ clean deps i18n install_i18n uninstall_i18n \ test test2 help # targets all: $(OBJECTS_DIR) $(BUILD_FILE) $(TARGET) $(LIBS) plugins: $(OBJECTS_DIR) $(LIBS_OBJECTS) install: all install_plugins @echo "installing $(TARGET) to prefix '$(PREFIX)'" @install -d -m 755 $(BIN_DIR) @install -m 755 $(TARGET) $(BIN_DIR) @install -d $(ICON_DIR) @for file in `ls icons/ |grep -v svn`; do \ install -m 644 icons/$$file $(ICON_DIR); \ done @install -d $(DOC_DIR) @for file in `ls docs/ |grep -v svn |grep -v desktop_environment |grep -v api |grep -v $(TARGET).1`; do \ install -m 644 docs/$$file $(DOC_DIR); \ done @install -d $(MAN_DIR) @install -m 644 docs/$(TARGET).1 $(MAN_DIR); # @bzip2 -f $(MAN_DIR)/$(TARGET).1; ifeq ($(XDG_INTEGRATION), 1) @install -d $(XDG_DESKTOP_DIR) @install -m 644 docs/desktop_environment/$(TARGET).desktop $(XDG_DESKTOP_DIR) @install -d $(XDG_APPLICATION_DIR) @install -m 644 docs/desktop_environment/$(TARGET).applications $(XDG_APPLICATION_DIR) endif install_plugins: plugins @echo "installing plugins to prefix '$(PREFIX)'" @install -d $(PLUGINS_DIR) @for file in "$(LIBS_OBJECTS)"; do \ install -c -m 755 $$file $(PLUGINS_DIR); \ done @for file in $$(ls -x $(OBJECTS_DIR)/$(LIBS)/$(OPTLIBS)/*.so 2>/dev/null); do \ install -c -m 755 $$file $(PLUGINS_DIR); \ done @install -d $(ICON_DIR) ifeq ($(WITH_THUMBS),1) @install -c -m 644 $(LIBS)/$(OPTLIBS)/$(ICONFILEPREFIX)$(BASENAME1)_$(ICON_SIZE).png $(ICON_DIR); endif ifeq ($(WITH_TRACKER),1) @install -c -m 644 $(LIBS)/$(OPTLIBS)/$(ICONFILEPREFIX)$(BASENAME2)_$(ICON_SIZE).png $(ICON_DIR); endif ifeq ($(WITH_ACL),1) @install -c -m 644 $(LIBS)/$(OPTLIBS)/$(ICONFILEPREFIX)$(BASENAME3)_$(ICON_SIZE).png $(ICON_DIR); endif uninstall: uninstall_plugins @echo "uninstalling $(TARGET) from prefix '$(PREFIX)'" @rm -f $(BIN_DIR)/$(TARGET) @rm -rfd $(DOC_DIR) # @echo -e "\nif you like you can also delete the icon directory:\n\t$(ICON_DIR)\n" @rm -rfd $(ICON_DIR) @rm -f $(MAN_DIR)/$(TARGET).1.bz2; uninstall_plugins: @echo "uninstalling plugins from '$(PREFIX)'" @test -d "$(PLUGINS_DIR)" && rm -rfd $(PLUGINS_DIR) @test -d "$(PREFIX)/lib/$(TARGET)" && rm -rfd $(PREFIX)/lib/$(TARGET) doc: @echo -n "generating api documentation in '$(API_DOC_DIR)'" @rm -rfd $(API_DOC_DIR) @doxygen >/dev/null @echo clean: @echo "cleaning up" @rm -f $(TARGET) @rm -f $(BUILD_FILE) @rm -f *.bak @rm -f $(PO_DIR)/*.mo @rm -rfd $(OBJECTS_DIR) @rm -rfd $(API_DOC_DIR) $(TARGET): $(OBJECTS) @echo "linking binary '$(TARGET)'" @$(CC) $(lCFLAGS) $(lLDFLAGS) $(OBJECTS) $(lLIBS) -o $(TARGET) ifneq ($(DEBUG), 1) @echo "stripping binary '$(TARGET)'" @strip $(TARGET) endif $(OBJECTS): $(OBJECTS_DIR)/%.o: %.c @echo "compiling '$*.c'" @$(CC) $(lCFLAGS) -c -o $@ $*.c $(LIBS_OBJECTS): $(OBJECTS_DIR)/%.so: %.c @echo "compiling '$*.c'" @$(CC) $(lCFLAGS) $(lLIBS_CFLAGS) -o $@ $*.c deps: $(DEP_FILES) $(LIB_DEP_FILES) $(DEP_FILES): $(OBJECTS_DIR) $(DEP_FILES): $(BUILD_FILE) $(DEP_FILES): $(OBJECTS_DIR)/%.deps: %.c @echo "generating '$@'" @$(CC) $(lCFLAGS) -MM -o $@ $*.c $(LIB_DEP_FILES): $(OBJECTS_DIR) $(LIB_DEP_FILES): $(BUILD_FILE) $(LIB_DEP_FILES): $(OBJECTS_DIR)/%.deps: %.c @echo "generating '$@'" @$(CC) $(lCFLAGS) $(lLIBS_CFLAGS) -MM -o $@ $*.c $(DEPS_FILE): $(BUILD_FILE) @echo "generating dependencies: '$(DEPS_FILE)'" @touch $(DEPS_FILE) @makedepend -s "# generated dependencies" -f $(DEPS_FILE) -- $(lCFLAGS) -- $(SOURCES) @rm $(DEPS_FILE).bak $(MO): $(PO_DIR)/%.mo: $(PO_DIR)/%.po @echo "formatting '$*.po'" @$(BIN_MSGFMT) $(PO_DIR)/$*.po -o $@ #gettext: i18n: @$(BIN_XGETTEXT) $(foreach dir, $(DIRS) $(LIBS) $(LIBS)/$(OPTLIBS), -D $(dir)) \ -p ./$(PO_DIR) --from-code=UTF-8 --no-wrap --omit-header -i -F \ --copyright-holder="$(COPYRIGHT)" --keyword=_ --keyword=N_ \ $(foreach file, $(SOURCES) $(HEADERS) $(LIBS_SOURCES) $(LIBS_HEADERS), $(shell basename $(file))) @mv -f $(PO_DIR)/messages.po $(PO_DIR)/$(TARGET).pot # if we want new messages start with english equivalent instead of empty # create a po to use for updating # @cd $(PO_DIR); msginit --input $(TARGET).pot --output en_US.po --no-translator \ # --locale=en_US >/dev/null 2>/dev/null # @cd $(PO_DIR); for i in `ls *.po` ; do \ # if [ "$$i" != "en_US.po" ] ; then \ # echo "updating $$i" ; \ # $(BIN_MSGMERGE) --update --backup=none $$i en_US.po ; \ # $(BIN_MSGFMT) $$i -o `echo $$i | sed -e s/.po/.mo/` ; \ # fi \ # done # @rm $(PO_DIR)/en_US.po @cd $(PO_DIR); for i in `ls *.po` ; do \ echo "updating $$i" ; \ $(BIN_MSGMERGE) --update --backup=none $$i $(TARGET).pot ; \ $(BIN_MSGFMT) $$i -o `echo $$i | sed -e s/.po/.mo/` ; \ done #i18n: gettext $(MO) install_i18n: i18n @echo "installing *.mo files to prefix '$(PREFIX)'" @cd po; for i in `ls *.mo` ; do \ mkdir -p $(LOCALE_DIR)/`echo $$i|sed -e s/.mo//`/LC_MESSAGES;\ install -c -m644 $$i $(LOCALE_DIR)/`echo $$i | sed -e s/.mo//`/LC_MESSAGES/$(TARGET).mo ; \ done uninstall_i18n: @echo "uninstalling *.mo files from prefix '$(PREFIX)'" @cd po; for i in `ls *.po` ; do \ rm -f $(LOCALE_DIR)/`echo $$i | sed -e s/.po//`/LC_MESSAGES/$(TARGET).mo || test -z "" ; \ done test: @echo "testing with splint.." @splint -preproc -weak -warnposix $(LINC) `pkg-config --cflags 'gtk+-2.0'` -I. $(SOURCES) test2: $(TARGET) @echo "testing with valgrind.." @valgrind --num-callers=99 --leak-check=yes --leak-check=full --show-reachable=yes ./$(TARGET) -n help: @echo -e "the following make targets are available:\n \ \t all\n \ \t plugins\n \ \t i18n\n \ \t install, install_plugins, install_i18n\n \ \t uninstall, uninstall_plugins, uninstall_i18n\n \ \t clean\n \ \t doc, test, test2\n \ \t deps" $(OBJECTS_DIR): @echo "creating object directories in '$(OBJECTS_DIR)'" @mkdir -p $(MKOBJDIRS) $(BUILD_FILE): @echo "updating build info: '$(BUILD_FILE)'" @echo "#ifndef __BUILD_H__" > $(BUILD_FILE) @echo "#define __BUILD_H__" >> $(BUILD_FILE) # @echo "#define GCCVERSION \"$(GCCV)\"" >> $(BUILD_FILE) @echo "#define PROGNAME \"$(PROGNAME)\"" >> $(BUILD_FILE) @echo "#define BINNAME \"$(TARGET)\"" >> $(BUILD_FILE) @echo "#define VERSION \"$(VERSION)\"" >> $(BUILD_FILE) @echo "#define RELEASE \"$(RELEASE)\"" >> $(BUILD_FILE) @echo "#define BUILDSTAMP \"$(BUILDSTAMP)\"" >> $(BUILD_FILE) @echo "#define BUILDDATE \"$(BUILDDATE)\"" >> $(BUILD_FILE) @echo "#define BUILDINFO \"$(BUILDINFO)\"" >> $(BUILD_FILE) @echo "#define COPYRIGHT \"$(COPYRIGHT)\"" >> $(BUILD_FILE) @echo "#define PREFIX \"$(PREFIX)\"" >> $(BUILD_FILE) @echo "#define PLUGINS_DIR \"$(PLUGINS_DIR)\"" >> $(BUILD_FILE) @echo "#define DOC_DIR \"$(DOC_DIR)\"" >> $(BUILD_FILE) @echo "#define ICON_DIR \"$(ICON_DIR)\"" >> $(BUILD_FILE) @echo "#define LOCALE_DIR \"$(LOCALE_DIR)\"" >> $(BUILD_FILE) @echo "#define E2_DEBUG_LEVEL $(DEBUG_LEVEL)" >> $(BUILD_FILE) ifeq ($(WITH_ASSIST),1) @echo "#define E2_ASSISTED" >> $(BUILD_FILE) endif ifeq ($(WITH_VFS),1) @echo "#define E2_VFS" >> $(BUILD_FILE) endif ifeq ($(WITH_HAL),1) @echo "#define E2_HAL" >> $(BUILD_FILE) endif ifeq ($(EDITOR_SPELLCHECK),1) @echo "#define E2_SPELLCHECK" >> $(BUILD_FILE) endif ifeq ($(EXTRA_BINDINGS),1) @echo "#define E2_TRANSIENTKEYS" >> $(BUILD_FILE) endif ifeq ($(FILES_UTF8ONLY), 1) @echo "#define E2_FILES_UTF8ONLY" >> $(BUILD_FILE) endif ifeq ($(USE_LATEST), 1) @echo "#define E2_CURRENTGTK_VERSION" >> $(BUILD_FILE) ifeq ($(WITH_TRANSPARENCY), 1) @echo "#define E2_COMPOSIT" >> $(BUILD_FILE) endif endif ifeq ($(NEW_COMMAND), 1) @echo "#define E2_NEW_COMMAND" >> $(BUILD_FILE) endif ifeq ($(DOCS_VERSION), 1) @echo "#define E2_VERSIONDOCS" >> $(BUILD_FILE) endif @echo "#define MAIN_HELP \"$(HELPDOC)\"" >> $(BUILD_FILE) @echo "#define CFG_HELP \"$(CONFIGDOC)\"" >> $(BUILD_FILE) ifeq ($(ICON_SIZE), 48) @echo "#define E2IP \"48\"" >> $(BUILD_FILE) else @echo "#define E2IP \"24\"" >> $(BUILD_FILE) endif ifeq ($(PANES_HORIZONTAL), 1) @echo "#define E2_PANES_HORIZONTAL" >> $(BUILD_FILE) endif ifeq ($(USE_FAM), 1) @echo "#define E2_FAM" >> $(BUILD_FILE) endif ifeq ($(USE_GAMIN), 1) @echo "#define E2_GAMIN" >> $(BUILD_FILE) endif ifeq ($(USE_INOTIFY), 1) @echo "#ifdef __linux__" >> $(BUILD_FILE) @echo "#define E2_FAM_INOTIFY" >> $(BUILD_FILE) @echo "#endif" >> $(BUILD_FILE) else ifeq ($(USE_DNOTIFY), 1) @echo "#ifdef __linux__" >> $(BUILD_FILE) @echo "#define E2_FAM_DNOTIFY" >> $(BUILD_FILE) @echo "#endif" >> $(BUILD_FILE) endif endif ifeq ($(WITH_KERNELFAM), 1) @echo "#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(darwin)" >> $(BUILD_FILE) @echo "#define E2_FAM_KQUEUE" >> $(BUILD_FILE) @echo "#endif" >> $(BUILD_FILE) @echo "#ifdef __solaris__" >> $(BUILD_FILE) @echo "#define E2_FAM_PORTEVENT" >> $(BUILD_FILE) @echo "#endif" >> $(BUILD_FILE) endif @echo "#endif //ndef __BUILD_H__" >> $(BUILD_FILE) #include dependencies ifneq ($(MAKECMDGOALS),deps) ifneq ($(MAKECMDGOALS),clean) ifneq ($(MAKECMDGOALS),gettext) ifneq ($(MAKECMDGOALS),test) ifneq ($(MAKECMDGOALS),test2) ifneq ($(MAKECMDGOALS),help) ifneq ($(MAKECMDGOALS),doc) -include $(DEP_FILES) -include $(LIBS_DEP_FILES) endif endif endif endif endif endif endif # DO NOT DELETE emelfm2-0.4.1/src/0000700000175000017500000000000011015120161012560 5ustar cairocairoemelfm2-0.4.1/src/e2_toolbar.c0000600000175000017500000033150011010212775014770 0ustar cairocairo/* $Id: e2_toolbar.c 872 2008-05-07 02:37:49Z tpgww $ Copyright (C) 2003-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/e2_toolbar.c @brief toolbar functions This file contains the functions for creation, re-creation and manipuation of toolbars and their contents, and bar context menu. Except that initialisation of panebars is largely handled in e2_pane.c */ /** \page toolbar toolbars ToDo \section buttons buttons ToDo \section address address entries ToDo */ #include "emelfm2.h" #include #include #include "e2_toolbar.h" #include "e2_dialog.h" #include "e2_filelist.h" #include "e2_complete.h" //static void resize_cb (GtkWidget *handlebox, GtkAllocation *alloc, E2_ToolbarRuntime *rt); static GtkWidget *_e2_toolbar_add_button (E2_ToolbarRuntime *rt, gint insertat, gchar *name, gchar *icon, gchar *tip, E2_ActionRuntime *data); static void _e2_toolbar_add_menu_items (GtkWidget *menu, GtkTreeModel *model, GtkTreeIter *iter, gint level, gboolean forwards, E2_ToolbarRuntime *rt); //static void e2_toolbar_add_items (GtkTreeModel *model, GtkTreeIter *iter, gint level, // E2_ToolbarRuntime *rt); /**********************************/ /*** bar context-menu callbacks ***/ /**********************************/ /** @brief edit the contents of a toolbar, via a config dialog This is performed when the toolbar context-menu item 'edit bar' is selected @param menuitem UNUSED the clicked menu widget @param rt ptr to E2_ToolbarRuntime for the bar @return */ static void _e2_toolbar_edit_cb (GtkWidget *menuitem, E2_ToolbarRuntime *rt) { //FIXME just recreate the edited bar (need data for the 'apply' function e2_config_dialog_single (rt->set->name, e2_toolbar_recreate_all, TRUE); } /** @brief hide specified toolbar This is performed when the toolbar context-menu item 'show bar' is selected @param menuitem UNUSED the clicked menu widget @param rt ptr to E2_ToolbarRuntime for the bar @return */ static void _e2_toolbar_hide_cb (GtkWidget *menuitem, E2_ToolbarRuntime *rt) { if (!rt->blocked) //bar's menu is not now being created { e2_option_bool_toggle_direct (rt->show); //now FALSE e2_toolbar_recreate (rt); } } /** @brief toggle display of tooltips for a specified bar This is performed when the toolbar context-menu item 'show tips' is selected @param menuitem UNUSED the clicked menu widget @param rt ptr to E2_ToolbarRuntime for the bar @return */ static void _e2_toolbar_toggle_tt_cb (GtkWidget *menuitem, E2_ToolbarRuntime *rt) { if (!rt->blocked) //bar's menu is not now being created { gtk_toolbar_set_tooltips (GTK_TOOLBAR (rt->toolbar), e2_option_bool_toggle_direct (rt->tooltips)); e2_toolbar_recreate (rt); } } /** @brief change a toolbar's space-handling mechanism This is performed when any of the options on the space handling sub-menu of the bar's context-menu is selected @param menuitem the clicked sub-menu widget @param rt ptr to E2_ToolbarRuntime for the bar @return */ static void _e2_toolbar_update_space_cb (GtkWidget *menuitem, E2_ToolbarRuntime *rt) { if (!rt->blocked) //bar's menu is not now being created { GSList *group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (menuitem)); gint index = 2 - g_slist_index (group, menuitem); if (e2_option_int_get_direct (rt->space) != index) { e2_option_sel_set_direct (rt->space, index); e2_toolbar_recreate (rt); } } } /** @brief toggle toolbar between horizontal and vertical layout This is performed when the 'horizontal' option on the 'type' sub-menu of the bar's context-menu is selected @param menuitem UNUSED the clicked menu widget @param rt ptr to E2_ToolbarRuntime @return */ static void _e2_toolbar_toggle_hori_cb (GtkWidget *menuitem, E2_ToolbarRuntime *rt) { if (!rt->blocked) //bar's menu is not now being created { e2_option_bool_toggle_direct (rt->hori); e2_toolbar_recreate (rt); } } /** @brief change type of toolbar type = main toolbar, big panebar etc This is performed when any of the 'types' on the 'type' sub-menu of the bar's context-menu is selected @param menuitem the clicked menu widget @param rt ptr to E2_ToolbarRuntime @return */ static void _e2_toolbar_update_type_cb (GtkWidget *menuitem, E2_ToolbarRuntime *rt) { if (!rt->blocked) //bar's menu is not now being created { GSList *group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (menuitem)); gint index = 3 - g_slist_index (group, menuitem); if (e2_option_int_get_direct (rt->type) != index) { e2_option_sel_set_direct (rt->type, index); e2_toolbar_recreate (rt); } } } /** @brief move bar 1 place right or down in its container This is performed when the corresponding choice is made from the 'type' sub-menu of the bar's context-menu @param menuitem UNUSED the clicked menu widget @param rt ptr to E2_ToolbarRuntime @return */ static void _e2_toolbar_increase_priority_cb (GtkWidget *menuitem, E2_ToolbarRuntime *rt) { if (!rt->blocked) //bar's menu is not now being created { gint i = e2_option_int_get_direct (rt->priority); if (e2_option_int_set_direct (rt->priority, i + 1) != i) e2_toolbar_recreate (rt); } } /** @brief move bar 1 place left or up in its container This is performed when the corresponding choice is made from the 'type' sub-menu of the bar's context-menu It causes the bar to be moved left or up in its container @param menuitem UNUSED the clicked menu widget @param rt ptr to E2_ToolbarRuntime @return */ static void _e2_toolbar_decrease_priority_cb (GtkWidget *menuitem, E2_ToolbarRuntime *rt) { if (!rt->blocked) //bar's menu is not now being created { gint i = e2_option_int_get_direct (rt->priority); if (e2_option_int_set_direct (rt->priority, i - 1) != i) e2_toolbar_recreate (rt); } } /** * @brief move toolbar to the top or left of its container This is performed when the corresponding choice is made from the 'type' sub-menu of the bar's context-menu @param menuitem UNUSED the clicked menu widget @param rt ptr to E2_ToolbarRuntime @return */ static void _e2_toolbar_reset_priority_cb (GtkWidget *menuitem, E2_ToolbarRuntime *rt) { if (!rt->blocked && (e2_option_int_get_direct (rt->priority) != 0)) { e2_option_int_set_direct (rt->priority, 0); e2_toolbar_recreate (rt); } } /** @brief change whether bar buttons are shown with relief This is performed when the corresponding choice is made from the bar's context-menu @param menuitem UNUSED the clicked menu widget @param rt ptr to E2_ToolbarRuntime @return */ static void _e2_toolbar_toggle_relief_cb (GtkWidget *menuitem, E2_ToolbarRuntime *rt) { if (!rt->blocked) //bar's menu is not now being created { e2_option_bool_toggle_direct (rt->relief); e2_toolbar_recreate (rt); } } /** @brief change whether bar buttons are shown all with the same width This is performed when the corresponding choice is made from the bar's context-menu @param menuitem UNUSED the clicked menu widget @param rt ptr to E2_ToolbarRuntime @return */ static void _e2_toolbar_toggle_same_cb (GtkWidget *menuitem, E2_ToolbarRuntime *rt) { if (!rt->blocked) //bar's menu is not now being created { e2_option_bool_toggle_direct (rt->same); e2_toolbar_recreate (rt); } } /** @brief change the way bar buttons are presented This is performed when the corresponding choice is made from the bar's context-menu options include: with/without icon, with/without label, label below/beside icon @param menuitem the clicked menu widget @param rt ptr to E2_ToolbarRuntime @return */ static void _e2_toolbar_update_style_cb (GtkWidget *menuitem, E2_ToolbarRuntime *rt) { if (!rt->blocked) //bar's menu is not now being created { GSList *group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (menuitem)); gint index = 4 - g_slist_index (group, menuitem); if (e2_option_int_get_direct (rt->style) != index) { e2_option_sel_set_direct (rt->style, index); e2_toolbar_recreate (rt); } } } /** @brief change the size of bar button icons This is performed when the corresponding choice is made from the icon size sub-menu of the bar's context-menu @param menuitem the clicked menu widget @param rt ptr to E2_ToolbarRuntime @return */ static void _e2_toolbar_update_isize_cb (GtkWidget *menuitem, E2_ToolbarRuntime *rt) { if (!rt->blocked) //bar's menu is not now being created { GSList *group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (menuitem)); gint index = 6 - g_slist_index (group, menuitem); if (e2_option_int_get_direct (rt->isize) != index) { e2_option_sel_set_direct (rt->isize, index); e2_toolbar_recreate (rt); } } } /*****************/ /***** menus *****/ /*****************/ /** @brief create toolbar context menu This is the callback for a button click on a toolbar. Right clicks cause a context menu to be created @param widget the clicked bar container @param event Gdk event data struct @param rt ptr to E2_ToolbarRuntime for the bar @return TRUE (ie handled) for a right-click, else FALSE */ static gboolean _e2_toolbar_click_cb (GtkWidget *widget, GdkEventButton *event, E2_ToolbarRuntime *rt) { if (event->button == 3) { //block local callbacks while the menu is being constructed rt->blocked = TRUE; GtkWidget *menu, *submenu; menu = gtk_menu_new (); e2_menu_add_check (menu, _("Show _bar"), e2_option_bool_get_direct (rt->show), _e2_toolbar_hide_cb, rt); e2_menu_add_check (menu, _("Show _tooltips"), e2_option_bool_get_direct (rt->tooltips), _e2_toolbar_toggle_tt_cb, rt); submenu = e2_menu_add_submenu (menu, _("Space _handling"), NULL); GSList *group = NULL; gint i = e2_option_int_get_direct (rt->space); e2_menu_add_radio (submenu, &group, _("_none"), (i == 0), _e2_toolbar_update_space_cb, rt); e2_menu_add_radio (submenu, &group, _("_scrollbars"), (i == 1), _e2_toolbar_update_space_cb, rt); e2_menu_add_radio (submenu, &group, _("_rest button"), (i == 2), _e2_toolbar_update_space_cb, rt); submenu = e2_menu_add_submenu (menu, _("_Placement"), NULL); e2_menu_add_check (submenu, _("_horizontal"), e2_option_bool_get_direct (rt->hori), _e2_toolbar_toggle_hori_cb, rt); GtkWidget *submenu2 = e2_menu_add_submenu (submenu, _("_Container"), NULL); group = NULL; i = e2_option_int_get_direct (rt->type); e2_menu_add_radio (submenu2, &group, _("_main window"), (i == 0), _e2_toolbar_update_type_cb, rt); e2_menu_add_radio (submenu2, &group, _("_both panes"), (i == 1), _e2_toolbar_update_type_cb, rt); e2_menu_add_radio (submenu2, &group, _("file-pane _1"), (i == 2), _e2_toolbar_update_type_cb, rt); e2_menu_add_radio (submenu2, &group, _("flle-pane _2"), (i == 3), _e2_toolbar_update_type_cb, rt); if (e2_option_bool_get_direct (rt->hori)) { e2_menu_add (submenu, _("_Up"), GTK_STOCK_GO_UP, NULL, _e2_toolbar_decrease_priority_cb, rt); e2_menu_add (submenu, _("_Down"), GTK_STOCK_GO_DOWN, NULL, _e2_toolbar_increase_priority_cb, rt); } else { e2_menu_add (submenu, _("_Left"), GTK_STOCK_GO_BACK, NULL, _e2_toolbar_decrease_priority_cb, rt); e2_menu_add (submenu, _("_Right"), GTK_STOCK_GO_FORWARD, NULL, _e2_toolbar_increase_priority_cb, rt); } e2_menu_add (submenu, _("_Reset order"), NULL, NULL, _e2_toolbar_reset_priority_cb, rt); e2_menu_add_check (menu, _("Buttons _relief"), e2_option_bool_get_direct (rt->relief), _e2_toolbar_toggle_relief_cb, rt); e2_menu_add_check (menu, _("Buttons _same size"), e2_option_bool_get_direct (rt->same), _e2_toolbar_toggle_same_cb, rt); submenu = e2_menu_add_submenu (menu, _("_Button style"), NULL); group = NULL; i = e2_option_int_get_direct (rt->style); //conform these labels with those in the config setup, below e2_menu_add_radio (submenu, &group, _("_theme"), (i == 0), _e2_toolbar_update_style_cb, rt); e2_menu_add_radio (submenu, &group, _("icon _only"), (i == 1), _e2_toolbar_update_style_cb, rt); e2_menu_add_radio (submenu, &group, _("_label only"), (i == 2), _e2_toolbar_update_style_cb, rt); e2_menu_add_radio (submenu, &group, _("icon _above label"), (i == 3), _e2_toolbar_update_style_cb, rt); e2_menu_add_radio (submenu, &group, _("icon _beside label"), (i == 4), _e2_toolbar_update_style_cb, rt); submenu = e2_menu_add_submenu (menu, _("_Icon size"), NULL); group = NULL; i = e2_option_int_get_direct (rt->isize); //conform these labels with those in the config setup, below e2_menu_add_radio (submenu, &group, _("_theme"), (i == 0), _e2_toolbar_update_isize_cb, rt); e2_menu_add_radio (submenu, &group, _("_menu"), (i == 1), _e2_toolbar_update_isize_cb, rt); e2_menu_add_radio (submenu, &group, _("toolbar _small"), (i == 2), _e2_toolbar_update_isize_cb, rt); e2_menu_add_radio (submenu, &group, _("toolbar _large"), (i == 3), _e2_toolbar_update_isize_cb, rt); e2_menu_add_radio (submenu, &group, _("_button"), (i == 4), _e2_toolbar_update_isize_cb, rt); e2_menu_add_radio (submenu, &group, _("drag '_n' drop"), (i == 5), _e2_toolbar_update_isize_cb, rt); e2_menu_add_radio (submenu, &group, _("_dialog"), (i == 6), _e2_toolbar_update_isize_cb, rt); // e2_menu_add_separator (menu); e2_menu_add (menu, _("Bar i_tems"), GTK_STOCK_PREFERENCES, NULL, _e2_toolbar_edit_cb, rt); e2_menu_popup (menu, 3, event->time); rt->blocked = FALSE; return TRUE; } return FALSE; } /** @brief destroy rest-menu after a choice has been made @param menu the rest-menu widget @param rt pointer to data struct for the toolbar that has the rest button @return */ static void _e2_toolbar_destroyrest_menu_cb (GtkWidget *menu, E2_ToolbarRuntime *rt) { gtk_container_foreach (GTK_CONTAINER (menu), (GtkCallback) gtk_widget_destroy, NULL); gtk_widget_destroy (menu); gtk_button_released (GTK_BUTTON (rt->button_rest)); //pop the button back up } /** @brief create a rest-menu for a bar Menu content is determined by data "path" of bar item rt->menu_starter @param rt ptr to E2_ToolbarRuntime for the toolbar @return the menu widget */ static GtkWidget *_e2_toolbar_createrest_menu (E2_ToolbarRuntime *rt) { GtkTreePath *path = g_object_get_data (G_OBJECT (rt->menu_starter), "path"); if (path != NULL) { GtkTreeIter iter; if (gtk_tree_model_get_iter (rt->set->ex.tree.model, &iter, path)) { GtkWidget *menu = gtk_menu_new (); _e2_toolbar_add_menu_items (menu, rt->set->ex.tree.model, &iter, 1, !rt->reversed, rt); g_signal_connect (G_OBJECT (menu), "selection-done", G_CALLBACK (_e2_toolbar_destroyrest_menu_cb), rt); return menu; } } return NULL; } /** @brief pop up the button pressed to initiate a toolbar submenu This is the callback for 'selection done' signal for the respective menu and in the bookmarks setup, and a general submenu setup ... @param menu UNUSED the menu that is now being closed @param button the widget clicked to initiate the menu @return */ static void _e2_toolbar_menu_finished_cb (GtkWidget *menu, GtkWidget *button) { //for permanent menus, do not destroy, but only applies to bookmarks now ?? //LEAKS ?? // gtk_container_foreach (GTK_CONTAINER (menu), (GtkCallback) gtk_widget_destroy, NULL); gtk_button_released (GTK_BUTTON (button)); } /** @brief set popup menu position This function is supplied when calling gtk_menu_popup(), to position the displayed menu. set @a push_in to TRUE for menu completely inside the screen, FALSE for menu clamped to screen size @param menu the GtkMenu to be positioned @param x place to store gint representing the menu left @param y place to store gint representing the menu top @param push_in place to store pushin flag @param button the activated widget on the toolbar @return */ void e2_toolbar_set_menu_position (GtkMenu *menu, gint *x, gint *y, gboolean *push_in, GtkWidget *button) { gint button_x, button_y, left, top; e2_utils_get_abs_pos (button, &button_x, &button_y); GtkRequisition menu_size; gtk_widget_size_request (GTK_WIDGET (menu), &menu_size); E2_ToolbarRuntime *rt = g_object_get_data (G_OBJECT (button), "bar-runtime"); if (e2_option_bool_get_direct (rt->hori)) //horizontal bar { //place below or above button, left-aligned left = button_x; top = button_y + rt->toolbar_container_box->allocation.height; if (top + menu_size.height > gdk_screen_height ()) top = button_y - menu_size.height; *push_in = (top < 0); printd (DEBUG, "menu top coordinate = %d", top); } else //vertical bar { //place right or left of button, top-aligned left = button_x + rt->toolbar_container_box->allocation.width; if (left + menu_size.width > gdk_screen_width ()) left = button_x - menu_size.width; top = button_y; *push_in = (left < 0); } *x = left; *y = top; } /** @brief pop up a menu, if it's a 'rest' menu, create it first If it's a rest-menu, @a item will have data "menu" = NULL, otherwise "menu" points to an existing menu widget @param button widget clicked to initiate the menu @param x coordinate of left of window relative to left of screen @param y coordinate of top of window relative to screen (WAS + button height) @param mouse_button no. of mouse button which was pressed @param time time at which the activation event occurred @param rt ptr to E2_ToolbarRuntime for the bar @return FALSE, always (though that seems to be irrelevant) */ static gboolean _e2_toolbar_menu_popup (GtkWidget *button, gint x, gint y, gint mouse_button, guint32 time, E2_ToolbarRuntime *rt) { GtkWidget *menu = g_object_get_data (G_OBJECT (button), "menu"); if (menu == NULL) { if (rt != NULL) menu = _e2_toolbar_createrest_menu (rt); else //can never happen ?? return FALSE; } gtk_menu_popup (GTK_MENU (menu), NULL, NULL, (GtkMenuPositionFunc) e2_toolbar_set_menu_position, button, mouse_button, time); return FALSE; } /** @brief create and pop-up menu at a position related to the button pos This is the callback for 'mnemonic activate' signal for rest-menu button, and in the bookmarks setup, and a general submenu setup ... @param button activated widget @param arg UNUSED T/F @param rt ptr to E2_ToolbarRuntime @return FALSE always */ static gboolean _e2_toolbar_menu_popup_mnemonic_cb (GtkWidget *button, gboolean arg, E2_ToolbarRuntime *rt) { gint x, y; e2_utils_get_abs_pos (button, &x, &y); // y += button->allocation.height; _e2_toolbar_menu_popup (button, x, y, 0, 0, rt); // _e2_toolbar_menu_popup (button, 0, 0, rt); return FALSE; } /** @brief create and pop-up the menu at a position related to the mouse pos this is the callback for a button release on menu button, rest, bookmarks button, and a general submenu setup ... @param button clicked button widget @param event Gdk event data struct @param rt ptr to E2_ToolbarRuntime @return TRUE for btn 1 event, else FALSE */ static gboolean _e2_toolbar_menu_popup_cb (GtkWidget *button, GdkEventButton *event, E2_ToolbarRuntime *rt) { if (event->button == 1) { /* gint x = event->x_root - event->x; gint y = event->y_root - event->y + button->requisition.height; return _e2_toolbar_menu_popup (button, x, y, event->button, event->time, rt); */ //check mouse has not moved from the clicked button if ( event->x <= button->allocation.width && event->y <= button->allocation.height ) { gint x = event->x_root - event->x; gint y = event->y_root - event->y; return _e2_toolbar_menu_popup (button, x, y, 1, event->time, rt); } } return FALSE; } /* * @brief UNUSED resize all command lines in a toolbar This is called when a bar is resized, so that commandlines Commandlines are recognised by a data field "command_line" CHECKME = this is needed only if max width of command line is implemented @param child toolbar-container child widget @param rt ptr to E2_ToolbarRuntime for the bar @return */ /*static void _e2_toolbar_resize_combo (GtkWidget *child, E2_ToolbarRuntime *rt) { GtkWidget *command_line = g_object_get_data (G_OBJECT (child), "command_line"); if (command_line != NULL) { gint max_width = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (command_line), "cl_max_width")); if (max_width == -1) return; GtkRequisition req_cl, req_hb; gtk_widget_size_request (GTK_WIDGET (rt->toolbar), &req_hb); if (req_hb.width == rt->alloc->width) return; printd (DEBUG, "resizing command line for %s", rt->option); gtk_widget_size_request (command_line, &req_cl); //req_hb.width _should_ be bigger than req_cl.width gint new_width = rt->alloc->width - req_hb.width + req_cl.width; //check upper limit if (new_width > max_width) new_width = max_width; if (new_width > rt->alloc->width) //bug, hb width < cl width !! new_width = rt->alloc->width; //check lower limit if (new_width < 1) new_width = 1; gint min_width = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (command_line), "cl_min_width")); if ((min_width != -1) && (new_width < min_width)) new_width = min_width; if (req_cl.width != new_width) gtk_widget_set_size_request (GTK_WIDGET (child), new_width, -1); } } */ /** @brief show 'rest' button at end of toolbar if not done before, create a 'rest' button, add it to end of bar container, and show the button if button already created, just show it the button visibility is signalled by rt->restbtn_shown @param rt ptr to data struct for toolbar that has been resized (=rt->toolbar) @return */ static void _e2_toolbar_add_rest_button (E2_ToolbarRuntime *rt) { if (rt->restbtn_shown) { //button is already displayed // printd (DEBUG, "rest button already displayed"); return; } if (rt->button_rest != NULL) { //button is already created gtk_widget_show (rt->button_rest); rt->restbtn_shown = TRUE; // printd (DEBUG, "re-displayed existing rest button"); return; } //create button with style according to options in force gint i = e2_option_int_get_direct (rt->style); if (i == 0) { //convert default bar style to corresponding local enumerator GtkSettings* defs = gtk_settings_get_default (); g_object_get (G_OBJECT (defs), "gtk-toolbar-style", &i, NULL); i++; //adjust for 0=default } gchar *img; if (i == 2) img = NULL; else img = (e2_option_bool_get_direct (rt->hori)) ? GTK_STOCK_GOTO_LAST : GTK_STOCK_GOTO_BOTTOM; rt->button_rest = e2_button_get_full (i == 1 ? "" : _("_Rest"), img, rt->icon_size, _("Show a menu of hidden items"), NULL, NULL, (i == 3 ? E2_BUTTON_ICON_ABOVE_LABEL : 0) | (i == 1 ? E2_BUTTON_SHOW_MISSING_ICON : 0)); gtk_button_set_relief (GTK_BUTTON (rt->button_rest), e2_option_bool_get_direct (rt->relief) ? GTK_RELIEF_NORMAL : GTK_RELIEF_NONE); g_object_set_data (G_OBJECT (rt->button_rest), "bar-runtime", rt); g_signal_connect (rt->button_rest, "button-press-event", G_CALLBACK (_e2_toolbar_menu_popup_cb), rt); g_signal_connect (rt->button_rest, "mnemonic-activate", G_CALLBACK (_e2_toolbar_menu_popup_mnemonic_cb), rt); gtk_box_pack_end (GTK_BOX (rt->toolbar_container_box), rt->button_rest, FALSE, FALSE, 0); gtk_widget_show_all (rt->button_rest); rt->restbtn_shown = TRUE; printd (DEBUG, "rest button created and added to %s",rt->name); } /** @brief after a toolbar item is mapped, adjust the rest-menu start item or hide the rest button This callback is connected to toolitems (other than command-lines) only when the toolbar space-handling mode is "rest-menu" @param tool ptr to toolitem that has just been mapped @param rt data struct for the toolbar @return */ static void _e2_toolbar_mapitem_cb (GtkToolItem *tool, E2_ToolbarRuntime *rt) { //#ifdef DEBUG_MESSAGES // gint index = g_list_index (rt->bar_items_list, tool); // printd (DEBUG, "%s item %d MAPPED", rt->name, index); //#endif GList *item = g_list_find (rt->bar_items_list, tool); //find the next item on the bar #ifdef FOLDBARS if (rt->reversed) item = item->prev; else #endif item = item->next; if (item != NULL) rt->menu_starter = GTK_BIN (item->data)->child; else //last item on the bar is now mapped if (rt->button_rest != NULL) { gtk_widget_hide (rt->button_rest); rt->restbtn_shown = FALSE; } } /** @brief after a toolbar item is unmapped, adjust the space-handing arrangements accordingly This callback is connected to toolitems (other than command-lines) only when the toolbar space-handling mode is "rest-menu" As appropriate, the rest-menu start item will be altered, the rest button displayed @param tool ptr to toolitem that has just been unmapped @param rt data struct for the toolbar @return */ static void _e2_toolbar_unmapitem_cb (GtkToolItem *tool, E2_ToolbarRuntime *rt) { //#ifdef DEBUG_MESSAGES // gint index = g_list_index (rt->bar_items_list, tool); // printd (DEBUG, "%s item %d UNMAPPED", rt->name, index); //#endif rt->menu_starter = GTK_BIN (tool)->child; //flag the initial item in the rest-menu _e2_toolbar_add_rest_button (rt); //create/(re)display rest menu btn } #ifdef FOLDBARS static gboolean _e2_toolbar_idle_resize (E2_ToolbarRuntime *rt); /** @brief if necessary, adjust things when a toolbar's size changes If scrollbar space-handling is in force, then a size request is issued, locking the minimum bar size and displaying the scrollbar Or, if rest-menu is in force for a complying panebar, this takes the commandbar out of the toolbar, and into a box stacked underneath @param toolbar ptr to panebar that has been resized (=rt->toolbar) @param alloc ptr to allocation struct for the toolbar (=&(GTK_WIDGET (toolbar))->allocation) @param rt data struct for the toolbar @return */ static void _e2_toolbar_resize_cb (GtkToolbar *toolbar, GtkAllocation *alloc, E2_ToolbarRuntime *rt) { // printd (DEBUG, "%s resize signal cb", rt->name); if (rt->size_queued) return; rt->size_queued = TRUE; //to avoid gtk issues when lots of resizes are reported before idle-time //screen refresh, we also defer the resize processing to idle time g_idle_add_full (G_PRIORITY_HIGH, (GSourceFunc) _e2_toolbar_idle_resize, rt, NULL); } static gboolean _e2_toolbar_idle_resize (E2_ToolbarRuntime *rt) { GtkAllocation *alloc = &(GTK_WIDGET(rt->toolbar))->allocation; gboolean horiz = e2_option_bool_get_direct (rt->hori); /*Buttons can be of variable size and number, and the rest-button may need to be accounted for, but it can't be actually sized if hidden We cannot simply check some aggregate allocation, or even perimeter-item allocation, because the command-line or padder size is most likely smaller or larger than the command-line minimum size. So we must aggregate the actual sizes of the other items, to get a "rest" size, then add that to the minimum command-line size, to get the fold threshold. This needs to be done each time the bar is resized, because button sizes can change on the fly (in the case of a swapped toggle button with a different sized label) The non-command-line tools are logged in the toolbar's items list. After the bar is mapped, the button sizes will be relevant */ GList *iterator; GtkAllocation item_alloc; gint button_total = 0; for (iterator = rt->bar_items_list; iterator != NULL; iterator = iterator->next) { item_alloc = GTK_WIDGET (iterator->data)->allocation; button_total += (horiz) ? item_alloc.width : item_alloc.height; } gint clmin; if (rt->has_command_line) { GtkToolItem *tool = rt->dirline_tool; clmin = (horiz) ? GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tool), "cl_swap_width")): GTK_WIDGET(tool)->allocation.height; } else clmin = 0; gint handlertype = e2_option_int_get_direct (rt->space); if (handlertype == 1) { //space handling by scroll bar if (horiz) //dunno why the +20 fudge works ... gtk_widget_set_size_request (GTK_WIDGET (rt->toolbar), button_total + clmin + 20, -1); else gtk_widget_set_size_request (GTK_WIDGET (rt->toolbar), -1, button_total + clmin + 20); rt->size_queued = FALSE; return FALSE; } //handler = 2, for folded bar with rest-menu gint sizenow = (horiz) ? alloc->width : alloc->height; // if (horiz) // printd (DEBUG, "%s resize, allocation width %d", rt->name, sizenow); // else // printd (DEBUG, "%s resize, allocation height %d", rt->name, sizenow); if (horiz && handlertype == 2 //rest-btn for space-handling && rt->has_command_line // && !(GTK_HANDLE_BOX (rt->toolbar_container)->child_detached) ) { //determine current fold-threshold GtkToolItem *tool = rt->dirline_tool; rt->restbtn_width = (rt->button_rest == NULL) ? 0 : rt->button_rest->allocation.width; gint threshold = button_total + clmin + rt->restbtn_width; // printd (DEBUG, "fold threshold for %s is %d", rt->name, threshold); // printd (DEBUG, "rest button width is %d", rt->restbtn_width); //the -8 fudge is visually good! (1 pixel per bar item ?) gint hsize = sizenow - button_total - 8; if (hsize < -1) hsize = -1; // GtkToolItem *tool = rt->dirline_tool; // gint minwidth = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tool), "cl_swap_width")); // gint iconadj = e2_utils_get_icon_size (rt->icon_size) + 10; //adjustment for rest-button if (!rt->folded) //haven't already split off the dirline { if (sizenow < threshold) { //transfer the commandline to the other box /* g_object_ref (tool); gtk_container_remove (GTK_CONTAINER (rt->toolbar), GTK_WIDGET (tool)); gtk_box_pack_end (GTK_BOX (rt->toolbarvbox), GTK_WIDGET (tool), TRUE, TRUE, 0); g_object_unref(tool); */ gtk_widget_reparent (GTK_WIDGET (tool), rt->toolbarvbox); rt->folded = TRUE; //FIXME find a smoother way to right-justify the post-cmdline bar-items //normally, toolbar items have to be packed at box-start, for rest-btn etc //placements to work properly GtkToolItem *padder = gtk_tool_item_new (); //set initial padder width gtk_widget_set_size_request (GTK_WIDGET (padder), hsize, -1); gtk_toolbar_insert (rt->toolbar, padder, rt->dirline_index); gtk_widget_show_all (GTK_WIDGET (padder)); } } else //is folded already { GtkToolItem *padder = gtk_toolbar_get_nth_item (rt->toolbar, rt->dirline_index); if (sizenow >= threshold) { gtk_container_remove (GTK_CONTAINER (rt->toolbar), GTK_WIDGET (padder)); //transfer the commandline back to the toolbar g_object_ref (tool); gtk_container_remove (GTK_CONTAINER (rt->toolbarvbox), GTK_WIDGET (tool)); gtk_toolbar_insert (rt->toolbar, tool, rt->dirline_index); g_object_unref(tool); rt->folded = FALSE; } else { //adjust padder size if we can //fixme clunky !! gtk_widget_set_size_request (GTK_WIDGET (padder), hsize, -1); } } } rt->size_queued = FALSE; return FALSE; } #endif /** @brief after a toolbar is torn-off, retain its size so that its contents are still displayed @param handlebox the object which received the signal = rt->toolbar_container @param widget the child widget of the handlebox = rt->toolbar_container_box @param rt data struct for the toolbar @return */ static void _e2_toolbar_detach_cb (GtkHandleBox *handlebox, GtkWidget *widget, E2_ToolbarRuntime *rt) { printd (DEBUG, "_e2_toolbar_detach_cb"); gboolean horiz = // (gtk_toolbar_get_orientation (rt->toolbar) == GTK_ORIENTATION_HORIZONTAL); e2_option_bool_get_direct (rt->hori); //determine the "full" bar size which will be the minimum when torn off gint barsize, clmin, barmin = 0; GList *iterator; GtkAllocation item_alloc; for (iterator = rt->bar_items_list; iterator != NULL; iterator = iterator->next) { item_alloc = GTK_WIDGET (iterator->data)->allocation; barmin += (horiz) ? item_alloc.width : item_alloc.height; } if (rt->has_command_line) { GtkToolItem *tool = rt->dirline_tool; if (horiz) { clmin = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tool), "cl_swap_width")); if (clmin == 0) clmin = 100; } else clmin = GTK_WIDGET(tool)->allocation.height; } else clmin = 0; barmin += clmin; if (rt->button_rest != NULL) barmin += (horiz) ? rt->button_rest->allocation.width : rt->button_rest->allocation.height + 20; //CHECKME why is +20 needed to show the last item ? if (horiz) { barsize = widget->allocation.x + widget->allocation.width; barsize = MAX (barsize, barmin); gtk_widget_set_size_request (widget, barsize, -1); } else { barsize = widget->allocation.y + widget->allocation.height; barsize = MAX (barsize, barmin); gtk_widget_set_size_request (widget, -1, barsize); } _e2_toolbar_resize_cb (rt->toolbar, &(GTK_WIDGET(rt->toolbar))->allocation, rt); } /** @brief after a torn-off toolbar is re-connected, allow its size to be adjusted as normal @param handlebox UNUSED the object which received the signal = rt->toolbar_container @param widget the child widget of the handlebox = rt->toolbar_container_box @param data UNUSED pointer to data specified when callback was connected @return */ static void _e2_toolbar_attach_cb (GtkHandleBox *handlebox, GtkWidget *widget, gpointer data) { printd (DEBUG, "_e2_toolbar_attach_cb"); gtk_widget_set_size_request (widget, -1, -1); } /** @brief initialise one or all (4) toolbars' space-handing arrangements This is called at session-start, when the main window is displayed, and after window re-builds Since this is after the bars and their contents are mapped, the map, unmap, resize signals for bar buttons can be connected to their callbacks without irrelevant calls (hence delays). As appropriate, the rest-menu start item will be set, and the rest button displayed @param rt pointer to data struct for bar to be processed, or NULL for all bars @return */ //FIXME rationalise the walks void e2_toolbar_initialise_space_handler (E2_ToolbarRuntime *rt) { gint i, count; gint barsize, fullsize; if (rt != NULL) count = 1; else { count = 0; E2_ToolbarData **thisbar; for (thisbar = app.bars; *thisbar != NULL; thisbar++) count++; } E2_ToolbarRuntime *barsrt [count]; if (rt != NULL) barsrt [0] = rt; else { for (i = 0; i < count; i++) barsrt [i] = app.bars[i]->rt; } for (i=0; ishow)) continue; //don't do anything if the bar is hidden gint htype = e2_option_int_get_direct (barsrt[i]->space); if (htype != 0) { //there is some sort of space-handling GList *iterator; //find the currently-allocated end of the bar GtkToolbar *tbar = barsrt[i]->toolbar; GtkOrientation bar_direction = gtk_toolbar_get_orientation (tbar); GtkWidget *wid = GTK_WIDGET (tbar); barsize = (bar_direction == GTK_ORIENTATION_HORIZONTAL) ? wid->allocation.x + wid->allocation.width : wid->allocation.y + wid->allocation.height ; //find the last visible item on the bar //note: after a menu-initiated change of handler-type, //barsize = 0 and all .x's = -1 gboolean visflg = (gtk_toolbar_get_n_items (tbar) > 0); if (visflg) { //find the notional (no-space-constraint) end of that last item //CHECKME need to account for fold-threshold effect ? fullsize = (bar_direction == GTK_ORIENTATION_HORIZONTAL) ? wid->allocation.x + wid->allocation.width: wid->allocation.y + wid->allocation.height; } else //nominal size when no item is visible on bar //(this includes afer a space-handler change) fullsize = 1; //checking barsize against fullsize can cause last btn to be hidden //without any rest btn FIXME find a way to show that btn (show_all, size_request N/A) if (barsize < fullsize + 1) { //there is a restriction, initial handler setup is needed if (htype == 2) { //space handling by rest menu //find the first (if any) unmapped button on the bar iterator = g_list_first (barsrt [i]->bar_items_list); while (iterator != NULL) { if (!GTK_WIDGET_MAPPED (iterator->data)) { barsrt [i]->menu_starter = GTK_BIN (iterator->data)->child; //create/display rest menu btn //this will also decrease the bar size accordingly, //and therefore hide other button(s) !! _e2_toolbar_add_rest_button (barsrt [i]); break; } iterator = iterator->next; } if ( iterator == NULL // probably after a space-handler change && barsize < fullsize ) printd (DEBUG, "no index for menu starter !!"); } else //not rest-menu if (htype == 1) //no need for test, really { //make sure scrollbar will be visible, if bar starts too small if (e2_option_bool_get_direct (barsrt [i]->hori)) // + .. to ensure the last item is actually shown FIXME actual button size gtk_widget_set_size_request (GTK_WIDGET (tbar), fullsize + 26, -1); else gtk_widget_set_size_request (GTK_WIDGET (tbar), -1, fullsize + 26); } } if (htype == 2) //rest-menu applies { printd (DEBUG, "connecting [un]maps for %s", barsrt[i]->name); //space handlers need to know when things change for (iterator = barsrt[i]->bar_items_list; iterator != NULL ; iterator = iterator->next) { /* //cleanup any old callback CHECKME needed ? g_signal_handlers_disconnect_matched (G_OBJECT (iterator->data), G_SIGNAL_MATCH_FUNC, 0, 0, NULL, _e2_toolbar_mapitem_cb, NULL); g_signal_handlers_disconnect_matched (G_OBJECT (iterator->data), G_SIGNAL_MATCH_FUNC, 0, 0, NULL, _e2_toolbar_unmapitem_cb, NULL); */ g_signal_connect (G_OBJECT (iterator->data), "map", G_CALLBACK (_e2_toolbar_mapitem_cb), barsrt[i]); g_signal_connect (G_OBJECT (iterator->data), "unmap", G_CALLBACK (_e2_toolbar_unmapitem_cb), barsrt[i]); } } #ifdef FOLDBARS //if appropriate if (htype == 1 //scrollbar space-handling || (htype == 2 && g_str_has_prefix (barsrt[i]->name, "panebar") //no translation && barsrt[i]->has_command_line && e2_option_bool_get_direct (barsrt[i]->hori)) ) { /* //cleanup any old callback CHECKME needed ? guint debug = g_signal_handlers_disconnect_matched (barsrt[i]->toolbar, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, _e2_toolbar_resize_cb, NULL); printd (DEBUG, "%d signal handler(s) disconnected for %s", debug, barsrt[i]->name); */ _e2_toolbar_resize_cb (barsrt[i]->toolbar, &(GTK_WIDGET(barsrt[i]->toolbar))->allocation, barsrt[i]); //connect to dirline-shifter cb g_signal_connect (G_OBJECT (barsrt[i]->toolbar), "size-allocate", G_CALLBACK (_e2_toolbar_resize_cb), barsrt[i]); //because gtk may not send "size-allocate" signal(s) during setup, //we need to explicitly send one, to setup toolbar layout //including to set the dirline into its other place, if appropriate // g_signal_emit_by_name (G_OBJECT (barsrt[i]->toolbar),"size-allocate", // &(GTK_WIDGET (barsrt[i]->toolbar))->allocation, barsrt [i]); } #endif } } } /*****************/ /***** utils *****/ /*****************/ /* * @brief UNUSED pack toolbar into a container @param box container widget into which the bar will be packed @param rt ptr to E2_ToolbarRuntime @return */ /*static void _e2_toolbar_pack_and_prio (GtkWidget *box, E2_ToolbarRuntime *rt) { gtk_box_pack_start (GTK_BOX (box), rt->toolbar_container, FALSE, FALSE, 0); gtk_box_reorder_child (GTK_BOX (box), rt->toolbar_container, e2_option_int_get_direct (rt->priority)); } */ /** @brief get option data from a row in the relevant treestore @param rt ptr to E2_ToolbarRuntime @param model model for the treestore @param iter pointer to iter to use for interrogating the model @param name pointer to location to store the retrieved name @param icon pointer to location to store the retrieved icon identifier string @param tip pointer to location to store the retrieved tooltip string @param type pointer to location to store the retrieved type string @param arg pointer to location to store the retrieved argument(s) string @return */ static void _e2_toolbar_get_option_row (E2_ToolbarRuntime *rt, GtkTreeModel *model, GtkTreeIter *iter, gchar **name, gchar **icon, gchar **tip, gchar **type, gchar **arg) { // gchar *_name, *_icon, *_tip, *_type, *_arg; gchar *_tip; if (type == NULL) gtk_tree_model_get (model, iter, 0, name, 1, icon, 2, &_tip, 3, arg, -1); else { gtk_tree_model_get (model, iter, 0, name, 1, icon, 2, &_tip, 3, type, 4, arg, -1); // *type = _type; } // *name = e2_utils_replace_vars (_name); // *icon = _icon; *tip = e2_utils_replace_vars (_tip); // *arg = e2_utils_replace_vars (_arg); DON'T EXPAND UNTIL USED if ((*tip != NULL) && (**tip == '\0')) { g_free (*tip); *tip = NULL; } // g_free (_name); g_free (_tip); // g_free (_arg); } /** @brief get a toolbar-configured button This gets a 'bare' button, intended to be cannibalized for a toggle. So some aspects of the button layout are effectively irrelevant @param rt toolbar data struct @param style index of toolbar button style @param name translated string used for button label @param icon path/name string of button icon (may be name of a stock icon) @return the button widget (not shown) */ static GtkWidget *_e2_toolbar_get_button (E2_ToolbarRuntime *rt, gint style, gchar *name, gchar *icon) { GtkWidget *button = e2_button_get_full (style == 1 ? NULL : name, style == 2 ? NULL : icon, rt->icon_size, NULL, NULL, NULL, style == 1 ? E2_BUTTON_SHOW_MISSING_ICON : 0); return button; } /** @brief add button to (the end of) a toolbar Button content and style are determined by options in force, including whether buttons are same-size or not A NULL label prevents any mnemonic action (applying a non-NULL but hidden label won't make a mnemonic work) In the calling code, the button is generally added to a toolitem, which is added to the bar @param rt toolbar data struct @param label translated string used for button label, or NULL if none is wanted @param icon path/name string of button icon (may be name of a stock icon or NULL) @param tip translated string for button tooltip (or NULL) @param data pointer to data for the button callback (or NULL) @return the button widget */ static GtkWidget *_e2_toolbar_add_button (E2_ToolbarRuntime *rt, gint insertat, gchar *label, gchar *icon, gchar *tip, E2_ActionRuntime *data) { gint i = e2_option_int_get_direct (rt->style); if (i == 0) { //convert default bar style to corresponding local enumerator GtkSettings* defs = gtk_settings_get_default (); g_object_get (G_OBJECT (defs), "gtk-toolbar-style", &i, NULL); i++; //adjust for 0=default } GtkWidget *button = e2_button_get_full (i == 1 ? NULL : label, i == 2 ? NULL : icon, rt->icon_size, NULL, e2_action_run_cb, data, (i == 3 ? E2_BUTTON_ICON_ABOVE_LABEL : 0) | (i == 1 ? E2_BUTTON_SHOW_MISSING_ICON : 0)); // CHECKME - CAN THERE BE A LEAK WHEN BAR IS DESTROYED ? if (data != NULL) g_object_set_data_full (G_OBJECT (button), "free-callback-data", data, (GDestroyNotify) e2_action_free_runtime); gtk_button_set_relief (GTK_BUTTON (button), e2_option_bool_get_direct (rt->relief) ? GTK_RELIEF_NORMAL : GTK_RELIEF_NONE); GtkToolItem *tool = gtk_tool_item_new (); //this line is based on default status being FALSE, //which seems to be correct, but is contrary to the API doc if (e2_option_bool_get_direct (rt->same)) // if buttons are to be same size gtk_tool_item_set_homogeneous (tool, TRUE); gtk_container_add (GTK_CONTAINER (tool), button); if (e2_option_bool_get_direct (rt->tooltips)) #ifdef USE_GTK2_12TIPS gtk_tool_item_set_tooltip_text (tool, tip); #else gtk_tool_item_set_tooltip (tool, app.tooltips, tip, NULL); #endif gtk_toolbar_insert (rt->toolbar, tool, insertat); gtk_widget_show_all (GTK_WIDGET (tool)); //add to bar-items cache rt->bar_items_list = g_list_append (rt->bar_items_list, tool); return button; } /*******************************/ /*** toggle-button functions ***/ /*******************************/ /** @brief swap the state of toggle button associated with @a action_name For a specified action, this hides the current toggle button, and shows the alternate button for its new state The button rt data is accessed via the action->data2 @param action_name string with (translated) name of the action initiated by the button @return boolean T/F, the state of the toggle button after this toggle */ gboolean e2_toolbar_button_toggle (gchar *action_name) { gchar *hashkey = g_strconcat (action_name, ".", action_name, NULL); gboolean retval = e2_toolbar_button_toggle_custom (hashkey); g_free (hashkey); return retval; } /** @brief swap the state of toggle button associated with @a hashkey This hides the current toggle button, and shows the alternate button for its new state The button rt data is accessed via the action->data2 For a predefined toggle, the key will be like action.action Otherwise it will be like action1.action2 or cmd1.cmd2 @param hashkey toggles hash key, (translated) string @return boolean T/F, the state of the toggle button after this toggle */ gboolean e2_toolbar_button_toggle_custom (gchar *hashkey) { E2_ToggleData *data = g_hash_table_lookup (toggles_hash, hashkey); if (data == NULL) //but allow toggle if there are no buttons return FALSE; data->current_state = ! data->current_state; gboolean state_new = data->current_state; GList *member; for (member = data->boxes; member != NULL; member = member->next) { E2_ToggleBox *ex = member->data; if (state_new) { if (ex->false_image != NULL) gtk_widget_hide (ex->false_image); if (ex->false_label != NULL) gtk_widget_hide (ex->false_label); if (ex->true_image != NULL) printd (DEBUG, "showing TRUE image"); if (ex->true_image != NULL) gtk_widget_show (ex->true_image); if (ex->true_label != NULL) gtk_widget_show (ex->true_label); #ifdef USE_GTK2_12TIPS if (ex->tipped && !ex->trueactive) { printd (DEBUG, "applying TRUE tip"); gtk_tool_item_set_tooltip_text (ex->tool, ex->true_tip); ex->trueactive = TRUE; } #endif } else //false now { if (ex->true_image != NULL) gtk_widget_hide (ex->true_image); if (ex->true_label != NULL) gtk_widget_hide (ex->true_label); if (ex->false_image != NULL) printd (DEBUG, "showing FALSE image"); if (ex->false_image != NULL) gtk_widget_show (ex->false_image); if (ex->false_label != NULL) gtk_widget_show (ex->false_label); #ifdef USE_GTK2_12TIPS if (ex->tipped && ex->trueactive) { printd (DEBUG, "applying FALSE tip"); gtk_tool_item_set_tooltip_text (ex->tool, ex->false_tip); ex->trueactive = FALSE; } #endif } #ifndef USE_GTK2_12TIPS if (ex->tips != NULL) { //tips are enabled for the bar where the button is located gchar *tmp = ex->tips->tip_text; ex->tips->tip_text = ex->tips->tip_private; ex->tips->tip_private = tmp; printd (DEBUG, "using old tip process, tip is now %s", ex->tips->tip_text); } #endif } printd (DEBUG, "state for toggle action %s changed to %s", data->true_action, (state_new) ? "true" : "false" ); return state_new; } /** @brief set state of toggle button associated with @a action For a specified action, this shows the appropriate toggle button, The button rt data is accessed via the action->data2 Buttons must have been created before this is called @param action_name string with (translated) name of the action initiated by the button @param state value to be set, T or F @return */ void e2_toolbar_toggle_button_set_state (gchar *action_name, gboolean state) { gchar *hashkey = g_strconcat (action_name, ".", action_name, NULL); E2_ToggleData *data = g_hash_table_lookup (toggles_hash, hashkey); g_free (hashkey); if (data == NULL) //but allow set if there are no buttons return; if (state == data->current_state) { printd (DEBUG, "SAME state for toggle '%s' (%s)", action_name, (state) ? "true" : "false" ); return; } printd (DEBUG, "set state for toggle '%s' to %s", action_name, (state) ? "true" : "false" ); data->current_state = state; GList *member; for (member = data->boxes; member != NULL; member = member->next) { E2_ToggleBox *ex = member->data; if (state) { if (ex->false_image != NULL) gtk_widget_hide (ex->false_image); if (ex->false_label != NULL) gtk_widget_hide (ex->false_label); if (ex->true_image != NULL) gtk_widget_show (ex->true_image); if (ex->true_label != NULL) gtk_widget_show (ex->true_label); #ifdef USE_GTK2_12TIPS if (ex->tipped && !ex->trueactive) { gtk_tool_item_set_tooltip_text (ex->tool, ex->true_tip); ex->trueactive = TRUE; } #endif } else //false now { if (ex->true_image != NULL) gtk_widget_hide (ex->true_image); if (ex->true_label != NULL) gtk_widget_hide (ex->true_label); if (ex->false_image != NULL) gtk_widget_show (ex->false_image); if (ex->false_label != NULL) gtk_widget_show (ex->false_label); #ifdef USE_GTK2_12TIPS if (ex->tipped && ex->trueactive) { gtk_tool_item_set_tooltip_text (ex->tool, ex->false_tip); ex->trueactive = FALSE; } #endif } #ifndef USE_GTK2_12TIPS if (ex->tips != NULL) { //tips are enabled for the bar where the button is located gchar *tmp = ex->tips->tip_text; ex->tips->tip_text = ex->tips->tip_private; ex->tips->tip_private = tmp; // printd (DEBUG, "tip is now %s", ex->tips->tip_text); } #endif } } /** @brief get current state of toggle button associated with @a action The button rt data is accessed via the action->data2 Buttons must have been created before this is called @param action_name string with (translated) name of the action initiated by the button @return current state of the button, T or F, of F if there is no data */ gboolean e2_toolbar_toggle_button_get_state (gchar *action_name) { gchar *hashkey = g_strconcat (action_name, ".", action_name, NULL); E2_ToggleData *data = g_hash_table_lookup (toggles_hash, hashkey); g_free (hashkey); return ((data != NULL) ? data->current_state : FALSE); } /** @brief clear toggle buttons data for all on toolbar associated with @a rt @param key UNUSED hash key comprising joined action names @param ex pointer to toggle data struct @param rt ptr to E2_ToolbarRuntime for a bar @return */ static void _e2_toolbar_toggle_button_clear (gchar *key, E2_ToggleData *ex, E2_ToolbarRuntime *rt) { GList *member; for (member = ex->boxes; member != NULL; member = member->next) { E2_ToggleBox *box = member->data; if (box->bar_rt == rt) { //CHECKME tooltip text cleanup ? DEALLOCATE (E2_ToggleBox, box); if (member->prev != NULL) { member = member->prev; ex->boxes = g_list_delete_link (ex->boxes, member->next); } else { //can't iterate if we kill the 1st link //clean that up later member->data = NULL; } } } //if the first entry is now unused ... if (ex->boxes != NULL && ex->boxes->data == NULL) ex->boxes = g_list_delete_link (ex->boxes, ex->boxes); } /** @brief helper to clear toggle hash entries that have no content @param key UNUSED hash key comprising joined action names @param ex pointer to toggle data struct @param data UNUSED ptr to data specified when setup @return TRUE if the table item is to be removed */ /*static gboolean _e2_toolbar_toggle_hash_clean (gchar *key, E2_ToggleData *ex, gpointer data) { return (ex->boxes == NULL); } */ /** @brief clear all toggle button default flags for a bar, or for all bars To ensure that each toggle button (pair) is recreated, clear each toggle-button's 'default_set' flag @param rt ptr to rt data struct for a bar, or NULL for all bars @return */ void e2_toolbar_toggle_buttons_set_destroyed (E2_ToolbarRuntime *rt) { if (rt == NULL) { E2_BarType barnum; for (barnum = 0; barnumrt->has_toggle) { g_hash_table_foreach (toggles_hash, (GHFunc) _e2_toolbar_toggle_button_clear, app.bars[barnum]->rt); } } } else if (rt->has_toggle) g_hash_table_foreach (toggles_hash, (GHFunc) _e2_toolbar_toggle_button_clear, rt); // g_hash_table_foreach_remove (toggles_hash, // (GHRFunc) _e2_toolbar_toggle_hash_clean, NULL); } /** @brief update filter toggle button for @a view, and if appropriate, update tooltip to reflect current filters @param view data structure for view being processed @return */ void e2_toolbar_toggle_filter_button (ViewInfo *view) { E2_ToggleType num = (view == &app.pane1_view) ? E2_TOGGLE_PANE1FILTERS : E2_TOGGLE_PANE2FILTERS; gchar *hashkey = g_strconcat (toggles_array[num], ".", toggles_array[num], NULL); E2_ToggleData *data = g_hash_table_lookup (toggles_hash, hashkey); g_free (hashkey); if (data == NULL) return; gboolean filter_on = view->name_filter.active || view->size_filter.active || view->date_filter.active; //filter_state = TRUE is 'normal' un-filtered state gboolean filter_state = e2_toolbar_toggle_button_get_state (toggles_array [num]); if (filter_on && filter_state) e2_toolbar_toggle_button_set_state (toggles_array [num], FALSE); else if (!filter_on && !filter_state) e2_toolbar_toggle_button_set_state (toggles_array [num], TRUE); if (!e2_toolbar_toggle_button_get_state (toggles_array [num])) { //construct 2nd line of tip GString *filters = g_string_sized_new (64); filters = g_string_append_c (filters, '('); //ascii ok gboolean previous = FALSE; if (view->name_filter.active) { if (view->name_filter.invert_mask) { filters = g_string_append (filters, _("not")); filters = g_string_append_c (filters, ' '); } //FIXME case-sensitive filters = g_string_append (filters, view->name_filter.patternptr); previous = TRUE; } if (view->size_filter.active) { if (previous) filters = g_string_append (filters, ", "); filters = g_string_append_c (filters, '<' + view->size_filter.op); filters = g_string_append_c (filters, ' '); if (view->size_filter.size < 10240) // less than 10k g_string_append_printf (filters, "%u", view->size_filter.size); else if (view->size_filter.size < 1048576) // less than a meg { g_string_append_printf (filters, "%.1f%s", (gfloat) view->size_filter.size / 1024.0, _("k")); } else // more than a meg { g_string_append_printf (filters, "%.1f%s", (gfloat) view->size_filter.size / 1048576.0, _("M")); } previous = TRUE; } if (view->date_filter.active) { if (previous) filters = g_string_append (filters, ", "); gchar *timenames [3] = {_("modified"), _("accessed"), _("changed")}; filters = g_string_append (filters, timenames [view->date_filter.time_type]); filters = g_string_append_c (filters, ' '); filters = g_string_append_c (filters, '<' + view->date_filter.op); filters = g_string_append_c (filters, ' '); //get which date format to use gint date_index = e2_option_int_get ("date-string"); if (date_index > 4) date_index = 0; //out of range, use default format (should never happen) extern gchar *date_format [5]; gchar date_buf[25]; struct tm *tm_ptr = localtime (&(view->date_filter.time)); strftime (date_buf, sizeof(date_buf), date_format[date_index], tm_ptr); gchar *utf = e2_utf8_from_locale (date_buf); filters = g_string_append (filters, utf); g_free (utf); } filters = g_string_append_c (filters, ')'); //update 2nd line of current tooltip to reflect current filter settings GList *tmp; for (tmp = data->boxes; tmp != NULL; tmp = tmp->next) { E2_ToggleBox *ex = tmp->data; #ifdef USE_GTK2_12TIPS if (ex->tipped && ex->true_tip != NULL) { gchar *current_tip = ex->true_tip; gchar *s = strchr (current_tip, '\n'); if (s != NULL) *s = '\0'; gchar *newtip = g_strconcat (current_tip, "\n", filters->str, NULL); g_free (current_tip); ex->true_tip = newtip; // if (status is active) gtk_tool_item_set_tooltip_text (ex->tool, newtip); } #else if (ex->tips != NULL) { //tips are enabled for the bar where the button is located gchar *current_tip = ex->tips->tip_text; gchar *s = strchr (current_tip, '\n'); if (s != NULL) *s = '\0'; gchar *newtip = g_strconcat (current_tip, "\n", filters->str, NULL); g_free (current_tip); //CHECKME ok ? ex->tips->tip_text = newtip; } #endif } g_string_free (filters, TRUE); } } /**************************/ /**** toolbar creation ****/ /**************************/ /** @brief add bookmarks to toolbar specified by @a rt This is the handler for bookmark. pseudo-action in a toolbar. It adds buttons to the bar, each with a menu of 'children' if they exist. The bookmark 'context menu' is disabled for each bookmark button (but not for any children) @param set data struct for the bookmarks option @param rt ptr to E2_ToolbarRuntime @param iter pointer to iter to be used for interrogating the set model @param action @param insertat whether the insertion order is reversed @return */ static void _e2_toolbar_add_bookmarks (E2_OptionSet *set, E2_ToolbarRuntime *rt, GtkTreeIter *iter, E2_Action *action, gint insertat) { do { gchar *name, *icon, *tip, *path; _e2_toolbar_get_option_row (rt, set->ex.tree.model, iter, &name, &icon, &tip, NULL, &path); GtkWidget *button = _e2_toolbar_add_button (rt, insertat, name, icon, tip, e2_action_pack_runtime (action, path, g_free)); g_signal_connect_data (G_OBJECT (button), "button-press-event", G_CALLBACK (e2_bookmark_click_cb), gtk_tree_model_get_path (set->ex.tree.model, iter), (GClosureNotify) gtk_tree_path_free, 0); g_object_set_data (G_OBJECT (button), "bookmark-path", path); //prevent context menu for this button //(can't use FALSE, as that returns NULL when queried) g_object_set_data (G_OBJECT (button), "with-context", GINT_TO_POINTER (1)); if (gtk_tree_model_iter_has_child (set->ex.tree.model, iter)) { GtkWidget *menu = gtk_menu_new (); GtkTreeIter iter2; gtk_tree_model_iter_children (set->ex.tree.model, &iter2, iter); e2_menu_create_bookmarks_menu (action, set, menu, &iter2); //create menu of child marks g_object_set_data_full (G_OBJECT (button), "menu", menu, (GDestroyNotify) gtk_widget_destroy); g_signal_connect (button, "button-press-event", G_CALLBACK (_e2_toolbar_menu_popup_cb), rt); g_signal_connect (button, "mnemonic-activate", G_CALLBACK (_e2_toolbar_menu_popup_mnemonic_cb), rt); g_signal_connect (G_OBJECT (menu), "selection-done", G_CALLBACK (_e2_toolbar_menu_finished_cb), button); } g_free (name); g_free (icon); g_free (tip); } while (gtk_tree_model_iter_next (set->ex.tree.model, iter)); } /** @brief @param menu widget to which the items will be added @param model pointer to model for treeestore with the menu's contents @param iter pointer to iter to be used for interrogating @a model @param level treestore depth @param forwards TRUE to scan @a model forwards, FALSE for backwards scan @param rt ptr to toolbar data struct @return */ static void _e2_toolbar_add_menu_items (GtkWidget *menu, GtkTreeModel *model, GtkTreeIter *iter, gint level, gboolean forwards, E2_ToolbarRuntime *rt) { gboolean (*nextfunc) (GtkTreeModel*, GtkTreeIter*) = (forwards) ? gtk_tree_model_iter_next : e2_tree_iter_previous; do { gchar *name, *icon, *tip, *type, *arg, *realarg; _e2_toolbar_get_option_row (rt, model, iter, &name, &icon, &tip, &type, &arg); E2_Action *action = e2_action_get_with_custom (type, arg, &realarg); switch (action->type) { case E2_ACTION_TYPE_SUBMENU: { GtkWidget *item = e2_menu_add (menu, name, icon, tip, NULL, NULL); if (arg == NULL || *arg == '\0') //no data == not a custom sub-menu { GtkTreeIter iter2; if (gtk_tree_model_iter_children (model, &iter2, iter)) { GtkWidget *menu2 = gtk_menu_new (); _e2_toolbar_add_menu_items (menu2, model, &iter2, level + 1, TRUE, rt); gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), menu2); } } else //custom submenu { GtkWidget *menu2 = e2_menu_create_custom_menu (arg); if (menu2 != NULL) gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), menu2); } break; } case E2_ACTION_TYPE_BOOKMARKS: { E2_OptionSet *set = e2_option_get ("bookmarks"); GtkTreeIter iter; if ((arg != NULL) && (*arg != '\0')) { if (e2_tree_find_iter_from_str (set->ex.tree.model, 0, arg, &iter, TRUE)) { GtkTreeIter iter2; if (gtk_tree_model_iter_children (set->ex.tree.model, &iter2, &iter)) { e2_menu_create_bookmarks_menu (action, set, menu, &iter2); rt->has_bookmarks = TRUE; } } } else if (gtk_tree_model_get_iter_first (set->ex.tree.model, &iter)) { e2_menu_create_bookmarks_menu (action, set, menu, &iter); rt->has_bookmarks = TRUE; } } break; case E2_ACTION_TYPE_PLUGINS: e2_menu_create_plugins_menu (menu, TRUE); //FIXME use real value of is_selection break; case E2_ACTION_TYPE_SEPARATOR: e2_menu_add_separator (menu); break; case E2_ACTION_TYPE_TOGGLE: e2_menu_add_toggle (menu, name, icon, tip, type, arg); break; case E2_ACTION_TYPE_ITEM: { E2_ActionRuntime *actionrt = e2_action_pack_runtime (action, realarg, g_free); GtkWidget *item = e2_menu_add (menu, name, icon, tip, e2_action_run_cb, actionrt); g_object_set_data_full (G_OBJECT (item), "free-callback-data", actionrt, (GDestroyNotify) e2_action_free_runtime); } break; default: break; } //cleanup g_free (name); g_free (icon); g_free (tip); g_free (type); if (action->type != E2_ACTION_TYPE_TOGGLE) g_free (arg); if (action->type != E2_ACTION_TYPE_ITEM) g_free (realarg); } while ((*nextfunc)(model, iter)); } /** @brief add items to the toolbar specified by @a rt @param model pointer to model for treeestore with the bar's contents @param iter pointer to iter to be used for interrogating @a model @param level treestore depth @param rt ptr to toolbar data struct @return */ static void _e2_toolbar_add_items (GtkTreeModel *model, GtkTreeIter *iter, gint level, E2_ToolbarRuntime *rt) { //in one case, we want to order the items in reverse gint insertat = (rt->reversed) ? 0 : -1; gboolean dirline = FALSE; do { gchar *label, *icon, *tip, *task, *arg, *realarg; _e2_toolbar_get_option_row (rt, model, iter, &label, &icon, &tip, &task, &arg); //when a row is added to a config dialog treeview, and no string entered //for a cell, its value can be NULL FIXME prevent that at source if (label == NULL) label = g_strdup (""); if (icon == NULL) icon = g_strdup (""); if (tip == NULL) tip = g_strdup (""); //CHECKME need to verify task ? if (arg == NULL) arg = g_strdup (""); E2_Action *action = e2_action_get_with_custom (task, arg, &realarg); GtkWidget *button = NULL; switch (action->type) { case E2_ACTION_TYPE_COMMAND_LINE: { //NOTE - end-of-setup check or dirline. So setting this //here works only if 1 commandline per toolbar dirline = g_str_equal ((gchar *)action->data,"dir line"); E2_CommandLineRuntime *cl_rt = e2_command_line_create ( (gchar *) action->data, //"dir line" etc action->data2, //NULL for cmdline, pane rt for a dirline action->func, dirline ? E2_COMMAND_LINE_DIR_LINE : E2_COMMAND_LINE_ORIGINAL); GtkWidget *command_line = GTK_WIDGET (cl_rt->cl); button = command_line; GtkToolItem *tool = gtk_tool_item_new (); gtk_container_add (GTK_CONTAINER (tool), command_line); /*#ifdef FOLDBARS //we want to always but a dirline in its own box if (dirline) //this is a dir line gtk_box_pack_start (GTK_BOX (rt->dirlinebox), GTK_WIDGET (tool), TRUE, TRUE, 0); else { gtk_toolbar_insert (GTK_TOOLBAR (rt->toolbar), tool, insertat); } FIXME //#endif */ #ifdef FOLDBARS if (dirline) { //this is a dir line rt->dirline_tool = tool; //assuming only 1 dirline, we cache it //for a reversed bar, this index is wrong, but we must wait //until the total itemcount is known before correcting it rt->dirline_index = gtk_toolbar_get_n_items (GTK_TOOLBAR (rt->toolbar)); } #endif gtk_toolbar_insert (GTK_TOOLBAR (rt->toolbar), tool, insertat); gtk_widget_show_all (GTK_WIDGET (tool)); g_object_set_data_full (G_OBJECT (rt->toolbar), cl_rt->name, cl_rt, (GDestroyNotify) e2_command_line_destroy); //get the width of the box gint min, max; if (strlen (arg) > 0) { gchar **split = g_strsplit (arg, ",", 2); gchar *end = NULL; gint i; if (split[0]) { i = (gint) g_ascii_strtoull (split[0], &end, 10); if (end != split[0]) min = i; else min = 100; //fallback value } else min = 100; /* if (e2_option_bool_get_direct (rt->hori)) // gtk_widget_set_size_request (command_line, min, -1); gtk_widget_set_size_request (GTK_WIDGET (tool), min, -1); else //with horiz min = -1, GTK makes vertical bars too wide gtk_widget_set_size_request (GTK_WIDGET (tool), -1, min); 40 or so gives a reasonable width, but GTK combo-box doesn't do drop-across, so when the line is that narrow, you can't read drop-down items gtk_widget_set_size_request (command_line, 50, min); gtk_widget_set_size_request (GTK_WIDGET (tool), 50, min); */ #ifdef FOLDBARS g_object_set_data (G_OBJECT (tool), "cl_swap_width", GINT_TO_POINTER(min)); if (e2_option_int_get_direct (rt->space) == 1 //scrollbar space handling && e2_option_bool_get_direct (rt->hori)) gtk_widget_set_size_request (GTK_WIDGET (tool), min, -1); #else gtk_widget_set_size_request (GTK_WIDGET (tool), min, -1); #endif if (split[1]) { if (*split[1] == '\0' || g_str_equal (split[1], "*") // || g_str_equal (split[1], _A(108)) //_("expand" ) { max = -1; gtk_tool_item_set_expand (tool, TRUE); } else { i = (gint) strtoull (split[1], &end, 10); if (end != split[0]) max = i; else max = 200; //fallback value g_object_set_data (G_OBJECT (tool), "command_line", command_line); //min is also needed when max applies //FIXME distinguish horiz & vert bars if (e2_option_bool_get_direct (rt->hori)) #ifndef FOLDBARS g_object_set_data (G_OBJECT (command_line), "cl_min_width", GINT_TO_POINTER(min)); #endif g_object_set_data (G_OBJECT (command_line), "cl_max_width", GINT_TO_POINTER(max)); } } g_strfreev (split); } // gtk_widget_set_size_request (command_line, max, -1); no point if max = -1, bug if max != -1 // gtk_widget_set_size_request (command_line, min, -1); rt->has_command_line = TRUE; } break; case E2_ACTION_TYPE_SUBMENU: { button = _e2_toolbar_add_button (rt, insertat, label, icon, tip, NULL); gtk_widget_show_all (button); if (arg == NULL || *arg == '\0') //no data == not a custom sub-menu { GtkTreeIter iter2; if (gtk_tree_model_iter_children (model, &iter2, iter)) { GtkWidget *menu = gtk_menu_new (); _e2_toolbar_add_menu_items (menu, model, &iter2, level + 1, TRUE, rt); g_object_set_data_full (G_OBJECT (button), "menu", menu, (GDestroyNotify) gtk_widget_destroy); g_signal_connect (G_OBJECT (menu), "selection-done", G_CALLBACK (_e2_toolbar_menu_finished_cb), button); // g_signal_connect (button, "button-press-event", g_signal_connect (button, "button-release-event", G_CALLBACK (_e2_toolbar_menu_popup_cb), rt); g_signal_connect (button, "mnemonic-activate", G_CALLBACK (_e2_toolbar_menu_popup_mnemonic_cb), rt); } } else //custom menu requested { GtkWidget *menu = e2_menu_create_custom_menu (arg); if (menu != NULL) { g_object_set_data_full (G_OBJECT (button), "menu", menu, (GDestroyNotify) gtk_widget_destroy); g_signal_connect (G_OBJECT (menu), "selection-done", G_CALLBACK (_e2_toolbar_menu_finished_cb), button); // g_signal_connect (button, "button-press-event", g_signal_connect (button, "button-release-event", G_CALLBACK (_e2_toolbar_menu_popup_cb), rt); g_signal_connect (button, "mnemonic-activate", G_CALLBACK (_e2_toolbar_menu_popup_mnemonic_cb), rt); } } } break; case E2_ACTION_TYPE_BOOKMARKS: { E2_OptionSet *set = e2_option_get ("bookmarks"); GtkTreeIter iter; if ((arg != NULL) && (*arg != '\0')) { if (e2_tree_find_iter_from_str (set->ex.tree.model, 0, arg, &iter, TRUE)) { GtkTreeIter iter2; if (gtk_tree_model_iter_children (set->ex.tree.model, &iter2, &iter)) { rt->has_bookmarks = TRUE; _e2_toolbar_add_bookmarks (set, rt, &iter2, action, insertat); } } } else if (gtk_tree_model_get_iter_first (set->ex.tree.model, &iter)) { rt->has_bookmarks = TRUE; _e2_toolbar_add_bookmarks (set, rt, &iter, action, insertat); } } break; case E2_ACTION_TYPE_TOGGLE: { /* a toggle button is like a normal button, but with pair(s) of images and/or labels (in accord with the toolbar's button style). At any time, one of each pair is hidden. Tooltips are paired by using both pointers in the GtkTooltipsData struct for the tool widget which contains the button */ static gboolean first = TRUE; static gboolean firststate; static gchar *firstlabel = NULL; static gchar *firsticon = NULL; static gchar *firsttip = NULL; static gchar *firstcmd = NULL; if (first) { //process 1st of a pair first = FALSE; //park until we get the paired name, so we can make hash key firststate = g_str_has_suffix (task, _A(111)); //_(on firstlabel = g_strdup (label); firsticon = g_strdup (icon); firsttip = g_strdup (tip); //SOMETIMES FREE THIS firstcmd = g_strdup (arg);//NEVER FREE THIS } else { //process 2nd of the pair first = TRUE; rt->has_toggle = TRUE; //remember that this bar has toggle(s) //hash table key is the joined action strings gchar *hashkey = g_strconcat (firstcmd, ".", arg, NULL); //get toggle data, if any E2_ToggleData *data = g_hash_table_lookup (toggles_hash, hashkey); if (data == NULL) { data = ALLOCATE (E2_ToggleData); CHECKALLOCATEDWARN (data, break;) g_hash_table_insert (toggles_hash, g_strdup (hashkey), data); data->current_state = firststate; data->boxes = NULL; if (firststate) { data->true_action = firstcmd; //action command string data->false_action = g_strdup (arg); //ditto } else { data->false_action = firstcmd; data->true_action = g_strdup (arg); } } //setup and populate extended toggle data E2_ToggleBox *ex = ALLOCATE (E2_ToggleBox); CHECKALLOCATEDWARN (ex, break;) data->boxes = g_list_append (data->boxes, ex); ex->bar_rt = rt; //remember the bar, for specific cleanups ex->button_style = e2_option_int_get_direct (rt->style); if (ex->button_style == 0) { //convert default bar style to corresponding local enumerator GtkSettings* defs = gtk_settings_get_default (); g_object_get (G_OBJECT (defs), "gtk-toolbar-style", &(ex->button_style), NULL); ex->button_style++; //adjust for 0=default } #ifdef USE_GTK2_12TIPS button = _e2_toolbar_add_button (rt, insertat, firstlabel, firsticon, NULL, e2_action_pack_runtime (action, hashkey, g_free)); ex->tool = GTK_TOOL_ITEM (button->parent); if (e2_option_bool_get_direct (rt->tooltips)) { ex->true_tip = (firststate) ? firsttip : g_strdup (tip); ex->false_tip = (firststate) ? g_strdup (tip) : firsttip; if (firststate) { gtk_tool_item_set_tooltip_text (ex->tool, ex->true_tip); ex->trueactive = TRUE; } else { gtk_tool_item_set_tooltip_text (ex->tool, ex->false_tip); ex->trueactive = FALSE; } ex->tipped = TRUE; } else ex->tipped = FALSE; #else gchar *usetip; if (e2_option_bool_get_direct (rt->tooltips)) { usetip = (data->current_state == firststate) ? firsttip : tip; } else usetip = NULL; button = _e2_toolbar_add_button (rt, insertat, firstlabel, firsticon, usetip, e2_action_pack_runtime (action, hashkey, g_free)); if (e2_option_bool_get_direct (rt->tooltips)) { ex->tips = gtk_tooltips_data_get (button); //returns NULL on gtk 2.12 if (ex->tips != NULL) ex->tips->tip_private = (data->current_state != firststate) ? firsttip : g_strdup (tip); } else ex->tips = NULL; #endif g_free (firstlabel); g_free (firsticon); #ifndef USE_GTK2_12TIPS if (usetip == firsttip) g_free (firsttip); #endif //setup alternate image, label GtkWidget *btn2 = _e2_toolbar_get_button (rt, ex->button_style, label, icon); //move image &/| label to the real button //provided that one or both of name, icon != NULL, //button contains alignment which contains a box we want GtkWidget *bbox1 = GTK_BIN (GTK_BIN (button)->child)->child; GList *firstchildren = gtk_container_get_children (GTK_CONTAINER (bbox1)); GtkWidget *bbox2 = GTK_BIN (GTK_BIN (btn2)->child)->child; GList *children = gtk_container_get_children (GTK_CONTAINER (bbox2)); GList *tmp; for (tmp = children; tmp != NULL; tmp = tmp->next) { /* g_object_ref (G_OBJECT (tmp->data)); gtk_container_remove (GTK_CONTAINER (bbox2), tmp->data); gtk_box_pack_start (GTK_BOX (bbox1), (GtkWidget*)tmp->data, FALSE, FALSE, 0); g_object_unref (G_OBJECT (tmp->data)); */ gtk_widget_reparent ((GtkWidget *)tmp->data, bbox1); gtk_widget_show_all ((GtkWidget*)tmp->data); } gtk_widget_destroy (btn2); ex->true_image = (ex->button_style == 2) ? //there is no icon NULL : firstchildren->data; ex->false_image = (ex->button_style == 2) ? NULL : children->data; if (ex->button_style == 1) //there is no label { ex->true_label = NULL; ex->false_label = NULL; } else if (ex->button_style == 2) //there is no icon { ex->true_label = firstchildren->data; ex->false_label = children->data; } else { ex->true_label = firstchildren->next->data; ex->false_label = children->next->data; } if (!firststate) { GtkWidget *tmp; tmp = ex->true_image; ex->true_image = ex->false_image; ex->false_image = tmp; tmp = ex->true_label; ex->true_label = ex->false_label; ex->false_label = tmp; } //setup initial display if (ex->button_style != 2) //there is an icon gtk_widget_hide ((data->current_state) ? ex->false_image : ex->true_image); if (ex->button_style != 1) //there is a label gtk_widget_hide ((data->current_state) ? ex->false_label : ex->true_label); g_list_free (firstchildren); g_list_free (children); } } break; case E2_ACTION_TYPE_ITEM: button = _e2_toolbar_add_button (rt, insertat, label, icon, tip, e2_action_pack_runtime (action, realarg, g_free)); gtk_widget_show_all (button); break; case E2_ACTION_TYPE_SEPARATOR: { GtkWidget *sep, *align; if (e2_option_bool_get_direct (rt->hori)) { sep = gtk_vseparator_new (); align = gtk_alignment_new (0.5, 0.5, 1.0, 0.6); } else { sep = gtk_hseparator_new (); align = gtk_alignment_new (0.5, 0.5, 0.6, 1.0); } gtk_container_add (GTK_CONTAINER (align), sep); gtk_container_set_border_width (GTK_CONTAINER (align), E2_PADDING_XSMALL); GtkToolItem *tool = gtk_tool_item_new(); gtk_container_add (GTK_CONTAINER (tool), align); gtk_toolbar_insert (GTK_TOOLBAR (rt->toolbar), tool, insertat); gtk_widget_show_all (GTK_WIDGET (tool)); } break; default: break; } if (button != NULL) { g_object_set_data (G_OBJECT (button), "bar-runtime", rt); g_object_set_data_full (G_OBJECT (button), "path", gtk_tree_model_get_path (model, iter), (GDestroyNotify) gtk_tree_path_free); } //cleanup g_free (label); g_free (task); g_free (icon); g_free (tip); g_free (arg); if (action->type != E2_ACTION_TYPE_ITEM) g_free (realarg); } while (gtk_tree_model_iter_next (model, iter)); #ifdef FOLDBARS if (rt->has_command_line && dirline && rt->reversed) { //this is a reversed dir line, now we can correct the index rt->dirline_index = gtk_toolbar_get_n_items (GTK_TOOLBAR (rt->toolbar)) - rt->dirline_index - 1; } #endif } /** @brief create toolbar associated with @a rt @param rt ptr to toolbar data struct @return */ void e2_toolbar_create (E2_ToolbarRuntime *rt) { if (!e2_option_bool_get_direct (rt->show)) return; rt->blocked = FALSE; rt->hidden = FALSE; rt->restbtn_shown = FALSE; rt->button_rest = NULL; rt->toolbar = GTK_TOOLBAR (gtk_toolbar_new ()); #ifdef FOLDBARS //horizontal panebar2 items are in reverse order rt->reversed = ( g_str_equal (rt->name, "panebar2") //not translated && e2_option_bool_get_direct (rt->hori) && !e2_option_bool_get ("panes-horizontal") ); gboolean foldable = ( g_str_has_prefix (rt->name, "panebar") && e2_option_bool_get_direct (rt->hori) ); rt->folded = FALSE; //always clear this rt->toolbarvbox = (foldable) ? gtk_vbox_new (FALSE, 0) : NULL ; //=flag for presence #endif //setup toolbar gtk_toolbar_set_tooltips (rt->toolbar, e2_option_bool_get_direct (rt->tooltips)); //strictly, we are not supposed to set style/size - the theme should do it gtk_toolbar_set_style (rt->toolbar, e2_option_int_get_direct (rt->style) - 1); //-1 corrects for our "default" = 0 //don't want to change the option size if it's 'default', so keep a working copy //(can't use rt->toolbar->icon_size, it reverts to default whenever the bar changes size) //e2 enumerator has same order as gtk enumerator rt->icon_size = e2_option_int_get_direct (rt->isize); if (rt->icon_size == 0) //default { /* gtk 2.6.8 spits warning about gtk-toolbar-icon-size property not existing, tho' API doco says it does ! GtkSettings* defs = gtk_settings_get_default (); g_object_get (G_OBJECT (defs), "gtk-toolbar-icon-size", &(rt->icon_size), NULL); if (rt->icon_size == 0) */ rt->icon_size = GTK_ICON_SIZE_LARGE_TOOLBAR; } //handlebox as toolbar container rt->toolbar_container = gtk_handle_box_new (); gtk_handle_box_set_shadow_type (GTK_HANDLE_BOX (rt->toolbar_container), GTK_SHADOW_NONE); //don't want "size-allocate" signals before window is displayed, they are redundant //until then, so we connect to that in toolbar space-handler initializer //setup toolbar orientation if (e2_option_bool_get_direct (rt->hori)) { rt->toolbar_container_box = gtk_hbox_new (FALSE, 0); /*#ifdef FOLDBARS if (isdirline && (strchr (rt->name, '2') != NULL)) { FIXME make box right-justified, box-pack-end no works ! } #endif */ gtk_toolbar_set_orientation (rt->toolbar, GTK_ORIENTATION_HORIZONTAL); gtk_handle_box_set_handle_position (GTK_HANDLE_BOX (rt->toolbar_container), GTK_POS_LEFT); gtk_handle_box_set_snap_edge (GTK_HANDLE_BOX (rt->toolbar_container), GTK_POS_TOP); } else { rt->toolbar_container_box = gtk_vbox_new (FALSE, 0); gtk_toolbar_set_orientation (rt->toolbar, GTK_ORIENTATION_VERTICAL); gtk_handle_box_set_handle_position (GTK_HANDLE_BOX (rt->toolbar_container), GTK_POS_TOP); gtk_handle_box_set_snap_edge (GTK_HANDLE_BOX (rt->toolbar_container), GTK_POS_LEFT); } #ifdef FOLDBARS if (foldable) { gtk_container_add (GTK_CONTAINER (rt->toolbar_container), rt->toolbarvbox); //end-packing here makes no difference to left-justification // (but it does stuff up the rest-button placement) gtk_box_pack_start (GTK_BOX (rt->toolbarvbox), rt->toolbar_container_box, TRUE, TRUE, 0); } else #endif gtk_container_add (GTK_CONTAINER (rt->toolbar_container), rt->toolbar_container_box); //scrollbars in force if (e2_option_int_get_direct (rt->space) == 1) { GtkWidget *sw; if (e2_option_bool_get_direct (rt->hori)) sw = e2_widget_get_sw_plain (GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER); else sw = e2_widget_get_sw_plain (GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), GTK_WIDGET (rt->toolbar)); gtk_box_pack_start (GTK_BOX (rt->toolbar_container_box), sw, TRUE, TRUE, 0); gtk_widget_show (sw); } else { /* this is useless !! #ifdef FOLDBARS if (rt->reversed) gtk_box_pack_end (GTK_BOX (rt->toolbar_container_box), GTK_WIDGET (rt->toolbar), TRUE, TRUE, 0); else #endif */ gtk_box_pack_start (GTK_BOX (rt->toolbar_container_box), GTK_WIDGET (rt->toolbar), TRUE, TRUE, 0); } if (rt->bar_items_list != NULL) { g_list_free (rt->bar_items_list) ; rt->bar_items_list = NULL; } //add toolbar to the main window gboolean hor_now = e2_option_bool_get_direct (rt->hori); GtkWidget *box; gint type = e2_option_int_get_direct (rt->type); switch (type) { //NOTE some layout combinations will be a problem //e.g. app.pane2 stuff not yet created to put pane1 bar into ... case 3: //pane 2 box = (hor_now) ? app.pane2.inner_box : app.pane2.outer_box; break; case 2: //pane 1 box = (hor_now) ? app.pane1.inner_box : app.pane1.outer_box; break; case 1: //both panes { gboolean hor_cfg = e2_option_bool_get ("panes-horizontal"); if (hor_now) box = (hor_cfg) ? app.window.panes_inner_box : app.window.panes_outer_box; else box = (hor_cfg) ? app.window.panes_outer_box : app.window.panes_inner_box; } break; default: //main window box = (hor_now) ? app.vbox_main : app.hbox_main; break; } // _e2_toolbar_pack_and_prio (packer, rt); gtk_box_pack_start (GTK_BOX (box), rt->toolbar_container, FALSE, FALSE, 0); gtk_box_reorder_child (GTK_BOX (box), rt->toolbar_container, e2_option_int_get_direct (rt->priority)); //bar items GtkTreeIter iter; if (gtk_tree_model_get_iter_first (rt->set->ex.tree.model, &iter)) _e2_toolbar_add_items (rt->set->ex.tree.model, &iter, 0, rt); //hookup the context menu g_signal_connect (G_OBJECT (rt->toolbar_container), "button-press-event", G_CALLBACK (_e2_toolbar_click_cb), rt); /* this is redundant: non-homog. buttons are setup with these properties GList *children = rt->toolbar->children; while (children != NULL) { GtkToolbarChild *child = children->data; //deprecated if ((GTK_IS_BUTTON (child->widget)) && (GTK_IS_LABEL (child->label))) { gtk_label_set_use_markup (GTK_LABEL (child->label), TRUE); gtk_label_set_use_underline (GTK_LABEL (child->label), TRUE); } children = g_list_next (children); } */ //toolbars are in handle boxes, which can be de/attached, and if so, //the size needs to be manipulated to preserve the contents display g_signal_connect (G_OBJECT (rt->toolbar_container), "child-detached", G_CALLBACK (_e2_toolbar_detach_cb), rt); g_signal_connect (G_OBJECT (rt->toolbar_container), "child-attached", G_CALLBACK (_e2_toolbar_attach_cb), NULL); //can't show all as it displays toggle buttons // need to show bar before its space handler can be properly initialized gtk_widget_show (GTK_WIDGET (rt->toolbar)); gtk_widget_show (rt->toolbar_container_box); #ifdef FOLDBARS if (foldable) gtk_widget_show (rt->toolbarvbox); #endif gtk_widget_show (rt->toolbar_container); //FIXME: add minimum sizes to panes if the toolbar is a small panebar /* if (type > 1) { } */ } /** @brief re-create all toolbars @return */ void e2_toolbar_recreate_all (void) { E2_ToolbarData **thisbar; for (thisbar = app.bars; *thisbar != NULL; thisbar++) e2_toolbar_recreate ((*thisbar)->rt); } /** @brief destroy and re-create toolbar specified by @a rt @param rt ptr to toolbar data struct @return */ void e2_toolbar_recreate (E2_ToolbarRuntime *rt) { e2_toolbar_destroy (rt); e2_toolbar_create (rt); e2_toolbar_initialise_space_handler (rt); //CHECKME set appropriate toggle buttons ? } /** @brief destroy toolbar widget for toolbar specified by @a rt @param rt ptr to toolbar data struct @return */ void e2_toolbar_destroy (E2_ToolbarRuntime *rt) { /* if (rt->toolbar != NULL) { gtk_widget_destroy (GTK_WIDGET (rt->toolbar)); rt->toolbar = NULL; } if (rt->toolbar_container_box != NULL) { gtk_widget_destroy (rt->toolbar_container_box); rt->toolbar_container_box = NULL; } */ if (rt->toolbar_container != NULL) { //ensure any and all toggle buttons are rebuilt with their current state if (rt->has_toggle) e2_toolbar_toggle_buttons_set_destroyed (rt); //destroy the outer-most container widget of the bar gtk_widget_destroy (rt->toolbar_container); rt->toolbar_container = NULL; } } /** @brief initialise various toolbar data items, and one action @param barnum enumerator which tells which bar to process @return */ void e2_toolbar_initialise (E2_BarType barnum) { gchar *barname = (gchar *) app.bars[barnum]->name; //internal (untranslated) bar identifier, "taskbar" etc E2_ToolbarRuntime *rt = app.bars[barnum]->rt; // these prevent proper toolbar destruction as part of config refresh ... // rt->toolbar = NULL; // rt->toolbar_container = NULL; rt->has_bookmarks = FALSE; rt->has_command_line = FALSE; rt->restbtn_shown = FALSE; rt->button_rest = NULL; rt->name = barname; // rt->public_name = app.bars[barnum]->public_name; //grab references to options registered at session-start rt->set = e2_option_get (barname); gchar *option_name = g_strconcat (barname, "-show", NULL); rt->show = e2_option_get (option_name); g_free (option_name); option_name = g_strconcat (barname, "-tooltips", NULL); rt->tooltips = e2_option_get (option_name); g_free (option_name); option_name = g_strconcat (barname, "-space", NULL); rt->space = e2_option_get (option_name); g_free (option_name); option_name = g_strconcat (barname, "-type", NULL); rt->type = e2_option_get (option_name); g_free (option_name); option_name = g_strconcat (barname, "-hori", NULL); rt->hori = e2_option_get (option_name); g_free (option_name); option_name = g_strconcat (barname, "-priority", NULL); rt->priority = e2_option_get (option_name); g_free (option_name); option_name = g_strconcat (barname, "-relief", NULL); rt->relief = e2_option_get (option_name); g_free (option_name); option_name = g_strconcat (barname, "-same", NULL); rt->same = e2_option_get (option_name); g_free (option_name); option_name = g_strconcat (barname, "-style", NULL); rt->style = e2_option_get (option_name); g_free (option_name); option_name = g_strconcat (barname, "-isize", NULL); rt->isize = e2_option_get (option_name); g_free (option_name); } /** @brief setup data for all toolbars @return */ void e2_toolbar_data_create (void) { static const gchar *barnames [E2_BAR_COUNT] = { "taskbar", "panebar1", "panebar2", "commandbar" }; const gchar *transnames [E2_BAR_COUNT]; transnames[E2_BAR_TASK] = _C(39); transnames[E2_BAR_PANE1] = _C(29); transnames[E2_BAR_PANE2] = _C(31); transnames[E2_BAR_COMMAND] = _C(4); E2_ToolbarRuntime *barsrt [E2_BAR_COUNT] = { &app.toolbar, &app.pane1.toolbar, &app.pane2.toolbar, &app.commandbar }; app.bars = g_malloc (sizeof (E2_ToolbarData*) * (E2_BAR_COUNT+1)); //space for bars and trailing null E2_ToolbarData **thisbar = app.bars; gint i; for (i=0; itype = (E2_BarType) i; (*thisbar)->name = barnames [i]; (*thisbar)->public_name = transnames [i]; (*thisbar)->rt = barsrt[i]; thisbar++; } *thisbar = NULL; } /*******************/ /***** actions *****/ /*******************/ /** @brief toggle whether specified toolbar exists or not This is the action for 'toggle ' @param rt ptr to E2_ToolbarRuntime @return */ /*static void _e2_toolbar_toggle (E2_ToolbarRuntime *rt) { if (e2_option_bool_toggle_direct (rt->show)) e2_toolbar_create (rt); else e2_toolbar_destroy (rt); }*/ /** @brief register actions for toolbars @return */ /* UNUSED void e2_toolbar_actions_register (void) { } */ /** @brief set up options for toolbar @a name @param barnum code which identifies the bar @return */ void e2_toolbar_options_register (E2_BarType barnum) { const gchar *name = app.bars[barnum]->name; //internal (untranslated) bar identifier, "taskbar" etc const gchar *public_name = app.bars[barnum]->public_name; //hacks to make panebars' config pages work gchar *shared_name, *dep_name; if (barnum == E2_BAR_PANE1) { shared_name = g_strconcat (_C(28),":",NULL); dep_name = "!pane1-uses-other"; //no translation, over-ride option name } else if (barnum == E2_BAR_PANE2) { shared_name = g_strconcat (_C(30), ":", NULL); dep_name = "!pane2-uses-other"; //no translation, over-ride option name } else { shared_name = g_strconcat (public_name, ".", _C(26), ":", NULL); dep_name = NULL; } gint i = (gint) barnum; //these must be in the same order as the E2_BarType enumerator gint data[4][10] = { //defaults for ... { TRUE, TRUE, 2, 2, FALSE, 1, FALSE, TRUE, 1, 3 }, //taskbar { TRUE, TRUE, 2, 2, TRUE, 0, FALSE, FALSE, 1, 2 }, //panebar1 { TRUE, TRUE, 2, 3, TRUE, 0, FALSE, FALSE, 1, 2 }, //panebar2 { TRUE, TRUE, 2, 0, TRUE, 1, FALSE, FALSE, 1, 2 }, //commandbar }; //show the bar, j=0 gchar *group_name = g_strconcat (shared_name, _C(25), NULL); //_(":miscellaneous" gchar *option_name = g_strconcat (name, "-show", NULL); gchar *desc = g_strconcat (_("show the "), public_name, NULL); gchar *tip = g_strdup_printf (_("This determines whether the %s is displayed or hidden"), public_name); e2_option_bool_register (option_name, group_name, desc, tip, dep_name, data[i][0], E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP | E2_OPTION_FLAG_FREENAME | E2_OPTION_FLAG_FREEDESC | E2_OPTION_FLAG_FREETIP | E2_OPTION_FLAG_BUILDBARS); //show tooltips, j=1 option_name = g_strconcat (name, "-tooltips", NULL); desc = g_strdup_printf (_("show tooltips for %s buttons"), public_name); tip = g_strdup_printf(_("If deactivated, tooltips will not be displayed for %s buttons"), public_name); e2_option_bool_register (option_name, group_name, desc, tip, dep_name, data[i][1], E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREENAME | E2_OPTION_FLAG_FREEDESC | E2_OPTION_FLAG_FREETIP | E2_OPTION_FLAG_BUILDBARS); //restricted-space handling, j=2 option_name = g_strconcat (name, "-space", NULL); tip = g_strdup_printf (_("This determines the method for accessing %s elements that are hidden due to lack of screen-space"), public_name); const gchar *opt_toolbar_space[] = {_("none"), _("use scrollbars"), _("use rest button"), NULL}; e2_option_sel_register (option_name, group_name, _("space handling"), tip, dep_name, data[i][2], opt_toolbar_space, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREENAME | E2_OPTION_FLAG_FREETIP | E2_OPTION_FLAG_BUILDBARS); group_name = g_strconcat (shared_name,_C(34), NULL); //_(":position" //bar type, j=3 option_name = g_strconcat (name, "-type", NULL); desc = g_strconcat (public_name, _(" container"), NULL); tip = g_strdup_printf (_("This determines the 'box' into which the %s is placed"), public_name); const gchar *opt_toolbar_type[] = {_("main window"), _("both panes"), _("file-pane 1"), _("file-pane 2"), NULL}; //this option cannot properly be conformed for both panes e2_option_sel_register (option_name, group_name, desc, tip, NULL, data[i][3], opt_toolbar_type, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP | E2_OPTION_FLAG_FREENAME | E2_OPTION_FLAG_FREEDESC | E2_OPTION_FLAG_FREETIP | E2_OPTION_FLAG_BUILDALL); //bar is horizontal, j=4 option_name = g_strconcat (name, "-hori", NULL); desc = g_strconcat (public_name, _(" horizontal"), NULL); tip = g_strdup_printf (_("This determines whether the %s is displayed horizontally or vertically"), public_name); e2_option_bool_register (option_name, group_name, desc, tip, dep_name, data[i][4], E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREENAME | E2_OPTION_FLAG_FREEDESC | E2_OPTION_FLAG_FREETIP | E2_OPTION_FLAG_BUILDBARS); //bar placement, j=5 option_name = g_strconcat (name, "-priority", NULL); desc = g_strconcat (public_name, _(" priority"), NULL); tip = _("This determines the order of toolbars (0 = left or top)"); //this option cannot properly be conformed for both panes e2_option_int_register (option_name, group_name, desc, tip, NULL, data[i][5], 0, 10, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREENAME | E2_OPTION_FLAG_FREEDESC | E2_OPTION_FLAG_BUILDALL); group_name = g_strconcat (shared_name, _C(37), NULL); //_(":style" //buttons always have relief, j=6 option_name = g_strconcat (name, "-relief", NULL); desc = g_strconcat (public_name, _(" buttons have relief"), NULL); e2_option_bool_register (option_name, group_name, desc, _("Buttons with relief show their outline continually, not just when 'moused'"), dep_name, data[i][6], E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP | E2_OPTION_FLAG_FREENAME | E2_OPTION_FLAG_FREEDESC | E2_OPTION_FLAG_BUILDBARS); //buttons are same size, j=7 option_name = g_strconcat (name, "-same", NULL); desc = g_strconcat (public_name, _(" buttons are the same size"), NULL); e2_option_bool_register (option_name, group_name, desc, _("Equal-width buttons look good on a vertical bar, bad on a horizontal bar"), dep_name, data[i][7], E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREENAME | E2_OPTION_FLAG_FREEDESC | E2_OPTION_FLAG_BUILDBARS); //buttons style, j=8 option_name = g_strconcat (name, "-style", NULL); const gchar *opt_toolbar_style[] = {_("theme"),_("icon only"), _("label only"), _("icon above label"), _("icon beside label"), NULL}; tip = g_strdup_printf (_("'%s' uses the Gtk default, '%s' leaves most space for other things"), opt_toolbar_style[0], opt_toolbar_style[1]); e2_option_sel_register (option_name, group_name, _("button style"), tip, dep_name, data[i][8], opt_toolbar_style, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREENAME | E2_OPTION_FLAG_FREETIP | E2_OPTION_FLAG_BUILDBARS); //buttons icon size, j=9 option_name = g_strconcat (name, "-isize", NULL); const gchar *opt_toolbar_isize[] = {_("theme"),_("menu"), _("toolbar small"), _("toolbar large"), _("button"), _("dnd"), _("dialog"),NULL}; tip = g_strdup_printf (_("'%s' uses the Gtk default, '%s' is smallest, '%s' is largest"), opt_toolbar_isize[0], opt_toolbar_isize[1], opt_toolbar_isize[6]); e2_option_sel_register (option_name, group_name, _("icon size"), tip, dep_name, data[i][9], opt_toolbar_isize, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREENAME | E2_OPTION_FLAG_FREETIP | E2_OPTION_FLAG_BUILDBARS); g_free (shared_name); } /** @brief install default tree options for taskbar This function is called only if the default is missing from the config file @param set pointer to set data @return */ static void _e2_toolbar_tree_defaults (E2_OptionSet *set) { e2_option_tree_setup_defaults (set, g_strconcat(app.bars[E2_BAR_TASK]->name,"=<",NULL), g_strconcat("|||",_A(0),".",_A(24),"|",_C(39),NULL), //this arg needs to be same as in bookmarks.c g_strconcat("|||",_A(18),"|",NULL), g_strconcat(_("_Copy"),"|gtk-copy|", _("Copy item(s) selected in the active pane to the other one"),"|",_A(5),".",_A(33),"|",NULL), g_strconcat(_("_Move"),"|move_"E2IP".png|", _("Move item(s) selected in the active pane to the other one"),"|",_A(5),".",_A(58),"|",NULL), g_strconcat(_("_Link"),"|symlink_"E2IP".png|", _("Symlink item(s) selected in the active pane to the other one"),"|",_A(5),".",_A(91),"|",NULL), g_strconcat(_("Re_name.."),"|gtk-convert|", _("Rename item(s) selected in the active pane"),"|",_A(5),".",_A(73),"|",NULL), g_strconcat(_("_Trash"),"|trash_"E2IP".png|", _("Move item(s) selected in the active pane to a trashbin"),"|",_A(5),".",_A(97),"|",NULL), g_strconcat(_("Ma_ke dir.."),"|gtk-open|",_("Create new directory(ies)"),"|",_A(1),".",_A(56),"|",NULL), // g_strconcat("|||",_A(18),"|",NULL), // g_strconcat("\\",_("_Delete"),"|gtk-delete|", // _("Delete item(s) selected in the active pane"),"|",_A(5),".",_A(38),"|",NULL), // g_strconcat("|||",_A(18),"|",NULL), g_strconcat("|||",_A(18),"|",NULL), g_strconcat(_("Re_fresh"),"|gtk-refresh|",_("Update pane contents"),"|",_A(13),".",_A(70),"|",NULL), g_strconcat(_("_Switch"),"|switch_"E2IP".png|", _("Toggle the active pane"),"|",_A(10),".",_A(90),"|",NULL), g_strdup(">"), NULL); } /** @brief setup config options specific to the toolbar In particular, this sets the default content of the bar @return */ void e2_toolbar_toolbaroptions_register (void) { const gchar *internal_name = app.bars[E2_BAR_TASK]->name; //"taskbar", no translation const gchar *public_name = app.bars[E2_BAR_TASK]->public_name; E2_OptionSet *set = e2_option_tree_register ((gchar *)internal_name, (gchar *)public_name, (gchar *)public_name, NULL, NULL, NULL, E2_OPTION_TREE_UP_DOWN | E2_OPTION_TREE_ADD_DEL, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_BUILDBARS); e2_option_tree_add_column (set, _("Label"), E2_OPTION_TREE_TYPE_STR, 0, "", 0, NULL, NULL); e2_option_tree_add_column (set, _("Icon"), E2_OPTION_TREE_TYPE_ICON, 0, "", 0, NULL, NULL); e2_option_tree_add_column (set, _("Tooltip"), E2_OPTION_TREE_TYPE_STR, 0, "", 0, NULL, NULL); e2_option_tree_add_column (set, _("Action"), E2_OPTION_TREE_TYPE_SEL, 0, _A(18), 0, NULL, GINT_TO_POINTER (E2_ACTION_EXCLUDE_GENERAL | E2_ACTION_EXCLUDE_TOOLBAR)); e2_option_tree_add_column (set, _("Argument"), E2_OPTION_TREE_TYPE_SEL, 0, "", 0, NULL, GINT_TO_POINTER (E2_ACTION_EXCLUDE_GENERAL | E2_ACTION_EXCLUDE_TOOLBAR | E2_ACTION_EXCLUDE_TOGGLE)); e2_option_tree_create_store (set); e2_option_tree_prepare_defaults (set, _e2_toolbar_tree_defaults); e2_toolbar_options_register (E2_BAR_TASK); //setup options relevant to all toolbars } /** @brief install default tree options for commandbar This function is called only if the default is missing from the config file @param set pointer to set data @return */ static void _e2_toolbar_cmdbar_tree_defaults (E2_OptionSet *set) { e2_option_tree_setup_defaults (set, g_strconcat(app.bars[E2_BAR_COMMAND]->name,"=<",NULL), g_strconcat(_("Full"),"|gtk-zoom-fit|",_("Maximize output pane"),"|",_A(15),".",_A(110),"|",_A(9),".",_A(108),NULL), g_strconcat(_("Shrink"),"|gtk-zoom-out|",_("Un-maximize output pane"),"|",_A(15),".",_A(111),"|",_A(9),".",_A(108),NULL), g_strconcat(_("Hide"),"|output_hide_"E2IP".png|",_("Hide output pane"),"|",_A(15),".",_A(110),"|",_A(9),".",_A(79),NULL), g_strconcat(_("Show"),"|output_show_"E2IP".png|",_("Show output pane"),"|",_A(15),".",_A(111),"|",_A(9),".",_A(79),NULL), g_strconcat(_("Clear"),"|gtk-clear|",_("Clear output pane"),"|",_A(9),".",_A(29),"|",NULL), g_strconcat("|||",_A(1),".",_A(23),"|","100,*",NULL), //1st arg is min size, integer, do not translate g_strconcat(_("cl"),"|cl_clear_"E2IP".png|",_("Clear command line"),"|",_A(1),".",_A(29),"|",NULL), g_strconcat(_("ps"),"|ps_"E2IP".png|",_("Child processes"),"|",_A(28),".",_A(24),"|",NULL), g_strconcat(_("du"),"|plugin_du_"E2IP".png|",_("Calculate disk usage of selected item(s)"),"|",_A(5),".",_("du"),"|",NULL), //conformed to new internal name g_strconcat(_("_Find.."),"|gtk-find|",_("Find item in active pane, by name"),"|",_A(5),".",_A(6),"|",NULL), g_strconcat(_("_X"),"|terminal_"E2IP".png|",_("Open terminal at the active directory"),"|$[command-xterm] &|",NULL), //_A(17) //this command works ok with xterm, but not with gnome-terminal // g_strconcat(_("su"),"|su_"E2IP".png|",_("Run something as root"),"|$[command-xterm]|-e 'su -c \"%{",_("Enter command:"),"}\";echo -n \"",_("Done, Press enter..."),"\";read'",NULL), //_A(17) g_strconcat(_("su.."),"|su_"E2IP".png|",_("Run something as root"),"|xterm|-e 'su -c \"%{",_("Enter command:"),"}\";echo -n \"",_("Done. Press enter "),"\";read'",NULL), //_A(17) #ifdef E2_FS_MOUNTABLE g_strconcat(_("mts.."),"|mounts_"E2IP".png|",_("Mount or unmount a device"),"|",_A(57),".",_A(24),"|",_C(4),NULL), #endif g_strconcat("|||",_A(18),"|",NULL), g_strconcat(_("_Settings.."),"|gtk-preferences|",_("View/change configuration settings for this program"),"|",_A(2),".",_A(32),"|",NULL), g_strconcat(_("_Help"),"|gtk-help|",_("Get information about this program"),"|",_A(3),".",_A(25),"|",NULL), g_strconcat("|||",_A(18),"|",NULL), g_strconcat(_("_Quit"),"|gtk-quit|",_("Close this program"),"|",_A(1),".",_A(69),"|",NULL), g_strdup(">"), NULL); } /** @brief setup config options specific to the commandbar In particular, this sets the default content of the bar @return */ void e2_toolbar_commandbaroptions_register (void) { const gchar *internal_name = app.bars[E2_BAR_COMMAND]->name; // "commandbar", no translation const gchar *public_name = app.bars[E2_BAR_COMMAND]->public_name; E2_OptionSet *set = e2_option_tree_register ((gchar *)internal_name, (gchar *)public_name, (gchar *)public_name, NULL, NULL, NULL, E2_OPTION_TREE_UP_DOWN | E2_OPTION_TREE_ADD_DEL, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_BUILDBARS); e2_option_tree_add_column (set, _("Label"), E2_OPTION_TREE_TYPE_STR, 0, "", 0, NULL, NULL); e2_option_tree_add_column (set, _("Icon"), E2_OPTION_TREE_TYPE_ICON, 0, "", 0, NULL, NULL); e2_option_tree_add_column (set, _("Tooltip"), E2_OPTION_TREE_TYPE_STR, 0, "", 0, NULL, NULL); e2_option_tree_add_column (set, _("Action"), E2_OPTION_TREE_TYPE_SEL, 0, _A(18), 0, NULL, GINT_TO_POINTER (E2_ACTION_EXCLUDE_GENERAL | E2_ACTION_EXCLUDE_TOOLBAR)); e2_option_tree_add_column (set, _("Argument"), E2_OPTION_TREE_TYPE_SEL, 0, "", 0, NULL, GINT_TO_POINTER (E2_ACTION_EXCLUDE_GENERAL | E2_ACTION_EXCLUDE_TOOLBAR | E2_ACTION_EXCLUDE_TOGGLE)); e2_option_tree_create_store (set); e2_option_tree_prepare_defaults (set, _e2_toolbar_cmdbar_tree_defaults); e2_toolbar_options_register (E2_BAR_COMMAND); //setup options relevant to all toolbars } /** @brief install default tree options for pane 1 toolbar This function is called only if the default is missing from the config file @param set pointer to set data @return */ static void _e2_toolbar_bar1_tree_defaults (E2_OptionSet *set) { e2_option_tree_setup_defaults (set, g_strconcat(app.bars[E2_BAR_PANE1]->name,"=<",NULL), // panebar1, //NOTE - these 2 btns and their corresponding T/F flags are expected to be present // (text/icons can vary) see code for selecting appropriate one of the pair at session start // g_strconcat(_("_Panes"),"|gtk-zoom-fit|",_("Toggle other-pane display"),"|",_A(13),".",_A(27),"|","*,1",NULL), //no arg translation g_strconcat(_("_Panes"),"|gtk-zoom-fit|",_("Hide other pane"),"|",_A(15),".",_A(110),"|",_A(11),".",_A(108),NULL), //no arg translation #ifdef E2_PANES_HORIZONTAL g_strconcat(_("_Panes"),"|split_vert_"E2IP".png|", _("Show other pane"),"|",_A(15),".",_A(111),"|",_A(11),".",_A(108),NULL), #else g_strconcat(_("_Panes"),"|split_horiz_"E2IP".png|", _("Show other pane"),"|",_A(15),".",_A(111),"|",_A(11),".",_A(108),NULL), #endif g_strconcat(_("Show _hidden"),"|hidden_show_"E2IP".png|", _("Display hidden items in this directory"),"|",_A(15),".",_A(110),"|",_A(11),".",_A(80),NULL), g_strconcat(_("Hide _hidden"),"|hidden_noshow_"E2IP".png|", _("Do not display hidden items in this directory"),"|",_A(15),".",_A(111),"|",_A(11),".",_A(80),NULL), g_strconcat(_("Fil_ters"),"|filter_"E2IP".png|", _("Set rules for the items to be displayed"),"|",_A(15),".",_A(111),"|",_A(11),".",_A(22),NULL), g_strconcat(_("Fil_ters"),"|filter_off_"E2IP".png|", _("Set/remove rules for the items to be displayed"),"|",_A(15),".",_A(110),"|",_A(11),".", _A(22),NULL), #ifdef E2_VFS g_strconcat(_("_VFS"),"|vfs_off_"E2IP".png|", _("Show a virtual directory in this pane"),"|",_A(15),".",_A(111),"|",_A(11),".",_A(115),NULL), g_strconcat(_("_LocalFS"),"|vfs_on_"E2IP".png|", "","|",_A(15),".",_A(110),"|",_A(11),".", _A(115),NULL), #endif #ifdef FOLDBARS g_strconcat("|||",_A(11),".",_A(23),"|","300,*",NULL), //1st arg is min size, integer, #else g_strconcat("|||",_A(11),".",_A(23),"|,*",NULL), #endif g_strconcat(_("_Marks"),"|bookmark_"E2IP".png|", _("Bookmarks"),"|",_A(19),"|",NULL), g_strconcat("\t",_("Add _top"),"|add_mark_top_"E2IP".png|", _("Add the current directory to the top of the bookmarks list"), "|",_A(0),".",_A(26),"|",_A(11),",",_A(114),NULL), g_strconcat("\t|||",_A(11),".",_A(21),"|",NULL), g_strconcat("\t",_("Add _bottom"),"|add_mark_bottom_"E2IP".png|", _("Add the current directory to the bottom of the bookmarks list"), "|",_A(0),".",_A(26),"|",_A(11),NULL), g_strconcat("\t|||",_A(18),"|",NULL), g_strconcat("\t",_("_Edit bookmarks"),"|gtk-preferences|", _("Open the bookmarks configuration dialog"),"|",_A(2),".",_C(1),"|",NULL), #ifdef E2_PANES_HORIZONTAL g_strconcat(_("Mi_rror"),"|mirror_vert_"E2IP".png|", _("Go to directory shown in other pane"),"|",_A(11),".",_A(55),"|",NULL), #else g_strconcat(_("Mi_rror"),"|mirror_horiz_"E2IP".png|", _("Go to directory shown in other pane"),"|",_A(11),".",_A(55),"|",NULL), #endif g_strconcat(_("_Back"),"|gtk-go-back|",_("Go to previous directory in history"),"|",_A(11),".",_A(45),"|",NULL), g_strconcat(_("_Up"),"|gtk-go-up|",_("Go up to parent directory"),"|",_A(11),".",_A(47),"|",NULL), g_strconcat(_("_Forward"),"|gtk-go-forward|",_("Go to next directory in history"),"|",_A(11),".",_A(46),"|",NULL), g_strdup(">"), NULL); } /** @brief install default tree options for pane 2 toolbar This function is called only if the default is missing from the config file @param set pointer to set data @return */ static void _e2_toolbar_bar2_tree_defaults (E2_OptionSet *set) { e2_option_tree_setup_defaults (set, g_strconcat(app.bars[E2_BAR_PANE2]->name,"=<",NULL), // panebar2, //NOTE - these 2 btns and their corresponding T/F flags are expected to be present // (text/icons can vary) see code for selecting appropriate one of the pair at session start g_strconcat(_("_Panes"),"|gtk-zoom-fit|",_("Hide other pane"),"|",_A(15),".",_A(110),"|",_A(12),".",_A(108),NULL), #ifdef E2_PANES_HORIZONTAL g_strconcat(_("_Panes"),"|split_vert_"E2IP".png|", _("Show other pane"),"|",_A(15),".",_A(111),"|",_A(12),".",_A(108),NULL), #else g_strconcat(_("_Panes"),"|split_vert_"E2IP".png|", _("Show other pane"),"|",_A(15),".",_A(111),"|",_A(12),".",_A(108),NULL), #endif g_strconcat(_("Show _hidden"),"|hidden_show_"E2IP".png|", _("Display hidden items in this directory"),"|",_A(15),".",_A(110),"|",_A(12),".",_A(80),NULL), g_strconcat(_("Hide _hidden"),"|hidden_noshow_"E2IP".png|", _("Do not display hidden items in this directory"),"|",_A(15),".",_A(111),"|",_A(12),".",_A(80),NULL), g_strconcat(_("Fil_ters"),"|filter_"E2IP".png|", _("Set rules for the items to be displayed"),"|",_A(15),".",_A(111),"|",_A(12),".",_A(22),NULL), g_strconcat(_("Fil_ters"),"|filter_off_"E2IP".png|", _("Set/remove rules for the items to be displayed"),"|",_A(15),".",_A(110),"|",_A(12),".", _A(22),NULL), #ifdef E2_VFS g_strconcat(_("_VFS"),"|vfs_off_"E2IP".png|", _("Show a virtual directory in this pane"),"|",_A(15),".",_A(111),"|",_A(12),".",_A(115),NULL), g_strconcat(_("_LocalFS"),"|vfs_on_"E2IP".png|", "","|",_A(15),".",_A(110),"|",_A(12),".", _A(115),NULL), #endif #ifdef FOLDBARS g_strconcat("|||",_A(12),".",_A(23),"|","300,*",NULL), //1st arg is min size, integer, #else g_strconcat("|||",_A(12),".",_A(23),"|,*",NULL), #endif g_strconcat(_("_Marks"),"|bookmark_"E2IP".png|", _("Bookmarks"),"|",_A(19),"|",NULL), g_strconcat("\t",_("Add _top"),"|add_mark_top_"E2IP".png|", _("Add the current directory to the top of the bookmarks list"), "|",_A(0),".",_A(26),"|",_A(12),",",_A(114),NULL), g_strconcat("\t|||",_A(12),".",_A(21),"|",NULL), g_strconcat("\t",_("Add _bottom"),"|add_mark_bottom_"E2IP".png|", _("Add the current directory to the bottom of the bookmarks list"), "|",_A(0),".",_A(26),"|",_A(12),NULL), g_strconcat("\t|||",_A(18),"|",NULL), g_strconcat("\t",_("_Edit bookmarks"),"|gtk-preferences|", _("Open the bookmarks configuration dialog"),"|",_A(2),".",_C(1),"|",NULL), #ifdef E2_PANES_HORIZONTAL g_strconcat(_("Mi_rror"),"|mirror_vert_"E2IP".png|", _("Go to directory shown in other pane"),"|",_A(12),".",_A(55),"|",NULL), g_strconcat(_("_Back"),"|gtk-go-back|",_("Go to previous directory in history"),"|",_A(12),".",_A(45),"|",NULL), g_strconcat(_("_Up"),"|gtk-go-up|",_("Go up to parent directory"),"|",_A(12),".",_A(47),"|",NULL), g_strconcat(_("_Forward"),"|gtk-go-forward|",_("Go to next directory in history"),"|",_A(12),".",_A(46),"|",NULL), #else g_strconcat(_("Mi_rror"),"|mirror_horiz_"E2IP".png|", _("Go to directory shown in other pane"),"|",_A(12),".",_A(55),"|",NULL), g_strconcat(_("_Forward"),"|gtk-go-forward|",_("Go to next directory in history"),"|",_A(12),".",_A(46),"|",NULL), g_strconcat(_("_Up"),"|gtk-go-up|",_("Go up to parent directory"),"|",_A(12),".",_A(47),"|",NULL), g_strconcat(_("_Back"),"|gtk-go-back|",_("Go to previous directory in history"),"|",_A(12),".",_A(45),"|",NULL), #endif g_strdup(">"), NULL); } /** @brief setup tree option for a pane bar @return */ void e2_toolbar_panebar_register (gint num) { gchar *internal_name = g_strdup_printf ("panebar%d", num); gchar *group_name = g_strconcat ((num == 1) ? _C(28) : _C(30), ".", _("bar"), NULL); gchar *public_name = (num == 1) ? _C(29) : _C(31); E2_OptionSet *set = e2_option_tree_register (internal_name, group_name, public_name, NULL, NULL, NULL, E2_OPTION_TREE_UP_DOWN | E2_OPTION_TREE_ADD_DEL, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREENAME | E2_OPTION_FLAG_FREEGROUP | E2_OPTION_FLAG_BUILDBARS); e2_option_tree_add_column (set, _("Label"), E2_OPTION_TREE_TYPE_STR, 0, "", 0, NULL, NULL); e2_option_tree_add_column (set, _("Icon"), E2_OPTION_TREE_TYPE_ICON, 0, "", 0, NULL, NULL); e2_option_tree_add_column (set, _("Tooltip"), E2_OPTION_TREE_TYPE_STR, 0, "", 0, NULL, NULL); e2_option_tree_add_column (set, _("Action"), E2_OPTION_TREE_TYPE_SEL, 0, _A(18), 0, NULL, GINT_TO_POINTER (E2_ACTION_EXCLUDE_GENERAL | E2_ACTION_EXCLUDE_TOOLBAR)); e2_option_tree_add_column (set, _("Argument"), E2_OPTION_TREE_TYPE_SEL, 0, "", 0, NULL, GINT_TO_POINTER (E2_ACTION_EXCLUDE_GENERAL | E2_ACTION_EXCLUDE_TOOLBAR | E2_ACTION_EXCLUDE_TOGGLE)); e2_option_tree_create_store (set); void (*func) = (num == 1) ? _e2_toolbar_bar1_tree_defaults : _e2_toolbar_bar2_tree_defaults ; e2_option_tree_prepare_defaults (set, func); } emelfm2-0.4.1/src/dialogs/0000700000175000017500000000000011015120161014202 5ustar cairocairoemelfm2-0.4.1/src/dialogs/e2_tree_dialog.c0000600000175000017500000013342511007760641017242 0ustar cairocairo/* $Id: e2_tree_dialog.c 868 2008-05-06 04:42:09Z tpgww $ Copyright (C) 2007-2008 tooar Portions copyright (C) 1996-2007 Steve Baker This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "e2_tree_dialog.h" #ifdef E2_TREEDIALOG //enable code to produce a full-tree in one step //still BUGGY (circular links not detected) and SLOW !!! //#define ENABLE_FULL_TREE /* NOTE limitation when ENABLE_FULL_TREE not defined: due to the scan-depth limit = 2, combinations like hidden-dir(s)-only/hidden-dir(s)-only/visible-dirs will result in those visible-dirs not being displayed in the dialog unless the show-hidden option has been activated by the user */ //STRICT_HIDE if defined will prevent all display of hidden dirs, i.e. no //showing of hidden ancestors of non-hidden dirs #ifdef ENABLE_FULL_TREE #define STRICT_HIDE #else //CHOOSEME #define STRICT_HIDE #endif //enable extra messages (for debugging) //#define WARNBADS #include "e2_dialog.h" #include "e2_fs.h" #include enum { DIRNAME, DIRKEY, TXTSET, TXTCOLOR, DIRFLAGS, DIRCOLCOUNT }; //treestore column enumerator enum { E2_TRDLG_NONE = 0, E2_TRDLG_STORED = 1, //don't re-store this item E2_TRDLG_SCANNED = 1 << 1, //children of this item have been read and stored E2_TRDLG_GRANDSCANNED = 1 << 2, //grand-children of this item have been read and stored E2_TRDLG_HIDDENITEMS = 1 << 3 //UNUSED this dir has hidden children (whether or not actually shown) }; #ifdef ENABLE_FULL_TREE /*typedef struct _inodata { ino_t inode; dev_t device; struct _inodata *next; } inodata; */ #endif typedef struct _E2_TreeDialogRuntime { GtkWidget *dialog; //the displayed dialog GtkWidget *hiddenbtn; //action-area hide-button GtkTreeStore *store; //dirnames store GtkWidget *treeview; //dirnames treeview gboolean show_hidden; //TRUE to show hidden dirs in the treeview #ifndef STRICT_HIDE gboolean strict_hide; //TRUE to prevent display of hidden ancestors #endif GHashTable *dirhash; //for contructing dependcies when store is filled ViewInfo *view; //data for displayed file pane // inodata *itable[256]; //NULL-terminated circular array of data structs, for preventing symlink circularity } E2_TreeDialogRuntime; static gboolean show_hidden = FALSE; //session-static value to use in dialogs #ifndef STRICT_HIDE static gboolean strict_hide = TRUE; #endif static GdkColor *bad_color; //static assuming config data won't change during a liststore fill static GdkColor *hidden_color; //static assuming last-closed window sets size for next one in this session only static gint window_width = -1; static gint window_height = -1; #ifdef ENABLE_FULL_TREE #define INOHASH(x) ((x)&255) #endif #ifndef ENABLE_FULL_TREE static void _e2_treedlg_row_expanded_cb (GtkTreeView *treeview, GtkTreeIter *iter, GtkTreePath *path, E2_TreeDialogRuntime *rt); #endif gchar *_e2_treedlg_make_path (GtkTreeModel *model, GtkTreePath *path); /*********************/ /***** utilities *****/ /*********************/ #ifdef ENABLE_FULL_TREE //the following two functions are derived from the 'tree' utility by Steve Baker /* * @brief log inode number of followed symlink to avoid re-following it @param inode inode no. to be logged if not already in @a itable matched with @a device @param device devide no. to be logged if not already in @a itable matched with @a inode @param itable array of inode data structs to be updated @return TRUE if inode already logged or was successfully added, FALSE after error */ /*static gboolean _e2_treedlg_saveino (ino_t inode, dev_t device, inodata *itable[]) { inodata *it, *ip, *pp; gint hp = INOHASH (inode); for (pp = ip = itable[hp]; ip != NULL; ip = ip->next) //CHECKME test ? { if (ip->inode > inode) //CHECKME test ? break; if (ip->inode == inode && ip->device >= device) //CHECKME test ? break; pp = ip; } if (ip != NULL && ip->inode == inode && ip->device == device) return TRUE; it = MALLOCATE (inodata); CHECKALLOCATEDWARN (it, return FALSE;) it->inode = inode; it->device = device; it->next = ip; //CHECKME if (ip == itable[hp]) itable[hp] = it; else pp->next = it; return TRUE; } */ /* * @brief determine whether @a inode & @a device pair has been logged before in @a itable @param inode inode no. to be checked @param device devide no. to be checked @param itable array of inode data structs to be interrogated @return */ /*static gboolean _e2_treedlg_findino (ino_t inode, dev_t device, inodata *itable[]) { inodata *it; for (it = itable[INOHASH(inode)]; it != NULL; it = it->next) { if (it->inode > inode) break; if (it->inode == inode && it->device >= device) //CHECKME test ? break; } return (it != NULL && it->inode == inode && it->device == device); } */ #endif //def ENABLE_FULL_TREE /** @brief directory-tree sort-order comparison function This compares 'sort-key' row data, which is utf-8 compatible NB utf8 collate keys for ascii text are case insensitive !! @param model the data model to be interrogated @param a pointer to model iter for the first row to be compared @param b pointer to model iter for the second row to be compared @param data UNUSED data specified when function was setup @return integer <0, 0 or >0 according to whether a belongs before, = or after b */ static gint _e2_treedlg_name_sort (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer data) { //comppare path depths, > depth sorts after GtkTreePath *pa, *pb; pa = gtk_tree_model_get_path (model, a); pb = gtk_tree_model_get_path (model, b); gint da, db; da = gtk_tree_path_get_depth (pa); db = gtk_tree_path_get_depth (pb); gtk_tree_path_free (pa); gtk_tree_path_free (pb); if (da != db) return (da < db) ? -1 : 1; //same depth ... gchar *keya; gchar *keyb; gtk_tree_model_get (model, a, DIRKEY, &keya, -1); gtk_tree_model_get (model, b, DIRKEY, &keyb, -1); gint result = strcmp (keya, keyb); g_free (keya); g_free (keyb); return result; } #ifndef STRICT_HIDE /** @brief clean all hidden "leaf" items from the treestore to which @a model applies Before first call, @a iter needs to be initialized externally, to the assumed-single root iter @param model treemodel for the filesystem-directories treestore @param iter pointer to iter for store row to start the scan @param table pointer to hash table with iter row references @return */ static void _e2_treedlg_clean_hidden (GtkTreeModel *model, GtkTreeIter *iter, GHashTable *table) { GtkTreeIter iter2, backup; gchar *value; do { nextiter: if (gtk_tree_model_iter_children (model, &iter2, iter)) { backup = iter2; _e2_treedlg_clean_hidden (model, &iter2, table); //in any path like .hidden1/.hidden2, .hidden2 will have been deleted //but .hidden1 will be left, inapppropriately //FIXME make this better if (gtk_tree_model_iter_children (model, &iter2, &backup)) _e2_treedlg_clean_hidden (model, &backup, table); } else { gtk_tree_model_get (model, iter, DIRNAME, &value, -1); if (value == NULL) continue; if (!ITEM_ISHIDDEN (value)) { g_free (value); continue; } g_free (value); GtkTreePath *tp = gtk_tree_model_get_path (model, iter); gchar *pathstr = _e2_treedlg_make_path (model, tp); if (pathstr != NULL) { g_hash_table_remove (table, pathstr); g_free (pathstr); } gtk_tree_path_free (tp); if (gtk_tree_store_remove (GTK_TREE_STORE (model), iter)) goto nextiter; //no need to increment iter break; //finished all iters at this level } } while (gtk_tree_model_iter_next (model, iter)); } #endif //ndef STRICT_HIDE /** @brief check whether some or all of @a path is represented in @a model This finds the treepath, if any, for deepest existing segment of @a path. Any empty segment of @a path is ignored. @a iter does not need to be initialized externally @param model treemodel for the filesystem-directories treestore @param iter pointer to iter to store row which corresponds to @a path @param pathstr utf-8 string, absolute path of the directory whose row is wanted @return TRUE if any match found (and then, *iter is valid) */ static gboolean _e2_treedlg_match_pathstring (GtkTreeModel *model, GtkTreeIter *iter, const gchar *pathstr) { printd (DEBUG, "_e2_treedlg_match_pathstring: path: %s", pathstr); if (!gtk_tree_model_get_iter_first (model, iter)) return FALSE; GtkTreeIter iter2; GtkTreeIter *current, *parent, *temp; parent = iter; current = &iter2; gchar **split = g_strsplit (pathstr, G_DIR_SEPARATOR_S, -1); gchar *iterator; guint i = 0; while ((iterator = split[i]) != NULL) { if (*iterator == '\0') { if (i == 0) //special-case one-only empty string which 'precedes' the path start #ifdef E2_VFSTMP //FIXME other roots #endif iterator = G_DIR_SEPARATOR_S; else { //other empty segments before the first non-empty one are just skipped here i++; continue; } } if (e2_tree_find_iter_from_str_same (model, DIRNAME, iterator, parent)) { //skip intervening empty-segments here, so we can decide whether to //check for children do { i++; } while (split[i] != NULL && *split[i] == '\0'); if (split[i] != NULL) { //something else to process if (gtk_tree_model_iter_children (model, current, parent)) { temp = current; current = parent; parent = temp; } else break; } else break; } else break; } g_strfreev (split); if (//retval && (parent != iter)) *iter = *parent; //ensure correct return data return TRUE; //retval; } /** @brief expand, and scroll to, the treepath of @a pathsstr, or as much of it as is found Any trailing separator in @a pathstr is ignored. Any problem in the expansion causes the displayed tree to be collapsed to level 1, and unscrolled. The treestore model and treeview must be connected. @param pathstr utf-8 string, absolute path of the directory whose row is to be shown @param expanded TRUE if the path to be shown is also to be expanded @param rt pointer to dialog data struct @return */ static void _e2_treedlg_show_path (const gchar *pathstr, gboolean expanded, E2_TreeDialogRuntime *rt) { GtkTreeIter iter; GtkTreeView *tvw = GTK_TREE_VIEW (rt->treeview); GtkTreeModel *model = GTK_TREE_MODEL (rt->store); GtkTreePath *tp = NULL; GtkTreeRowReference *ref; if ((ref = g_hash_table_lookup (rt->dirhash, pathstr)) != NULL) //may fail e.g. due to trailing / tp = gtk_tree_row_reference_get_path (ref); if (tp == NULL && _e2_treedlg_match_pathstring (model, &iter, pathstr)) //find the closest available ancestor-path tp = gtk_tree_model_get_path (model, &iter); if (tp != NULL) { if (expanded) gtk_tree_view_expand_to_path (tvw, tp); else { GtkTreePath *parent = gtk_tree_path_copy (tp); gtk_tree_path_up (parent); gtk_tree_view_expand_to_path (tvw, parent); gtk_tree_path_free (parent); } //maybe gtk bug here ?? any vertical scroll < about 0.5 fails //?WAIT_FOR_EVENTS gtk_tree_view_scroll_to_cell (tvw, tp, NULL, TRUE, 0.5, 0.1); GtkTreeSelection *sel = gtk_tree_view_get_selection (tvw); gtk_tree_selection_select_path (sel, tp); } else //(can be an empty treestore when first called ??) { tp = gtk_tree_path_new_first (); gtk_tree_view_collapse_all (tvw); gtk_tree_view_expand_row (tvw, tp, FALSE); } gtk_tree_path_free (tp); } /** @brief construct string form of path represented by @a path in @a model @param model treemodel for the filesystem-directories treestore @param path the tree path of the directory whose path is to be constructed @return newly-allocated utf-8 absolute path string (no trailer), or NULL after error */ gchar *_e2_treedlg_make_path (GtkTreeModel *model, GtkTreePath *path) { gint i, depth; gint *indices; gchar *freeme, *segment, *madepath; GtkTreeIter *current, *parent, *temp; GtkTreeIter iter1, iter2; depth = gtk_tree_path_get_depth (path); if (depth == 1) return (g_strdup (G_DIR_SEPARATOR_S)); //start at root of model if (!gtk_tree_model_get_iter_first (model, &iter1)) return NULL; parent = &iter1; current = &iter2; //starts undefined indices = gtk_tree_path_get_indices (path); indices++; //we start interating after the root node madepath = g_strdup (""); for (i = 1; i < depth; i++) { if (gtk_tree_model_iter_nth_child (model, current, parent, *indices)) { gtk_tree_model_get (model, current, DIRNAME, &segment, -1); freeme = madepath; madepath = g_strconcat (madepath, G_DIR_SEPARATOR_S, segment, NULL); g_free (freeme); g_free (segment); temp = parent; parent = current; current = temp; } else { printd (WARN, "bad index in directories treemodel"); g_free (madepath); madepath = NULL; break; } indices++; } return madepath; } /** @brief add a node for @a parentpath to @a model, and likewise for any missing ancestor node(s) This is called when an iter for @a parentpath is missing from the directories treestore, because @a store is being populated for the first time, or @a parentpath is hidden and previously, hidden dirs were ignored. Empty path segments are ignored. @param store GtkTreeStore for the directories data @param parentpath utf-8 string, absolute path of directory to be added, no trailer @param dirhash hash table to store path references @return GtkTreePath of the added node */ static GtkTreePath *_e2_treedlg_infill (GtkTreeStore *store, const gchar *parentpath, GHashTable *dirhash) { gchar *name, *key; GtkTreeIter iter1, iter2; GtkTreeIter *current, *parent, *temp; parent = &iter1; current = &iter2; GtkTreePath *tp; GtkTreeModel *model = GTK_TREE_MODEL (store); gchar **split = g_strsplit (parentpath, G_DIR_SEPARATOR_S, -1); guint i, count = g_strv_length (split); if (gtk_tree_model_get_iter_first (model, parent)) { //find node, if any, for deepest existing segment of the parent path for (i = 0; i < count; i++) { name = split[i]; if (*name == '\0') { if (i == 0) #ifdef E2_VFSTMP //FIXME other virtual root ok ? #endif name = G_DIR_SEPARATOR_S; else continue; //ignore empty segments other than the first one } if (e2_tree_find_iter_from_str_same (model, DIRNAME, name, parent)) { if (i < count-1) //not doing last segment now { do { i++; } while (split[i] != NULL && *split[i] == '\0'); if (i <= count-1) { if (gtk_tree_model_iter_children (model, current, parent)) { //re-use the iters temp = current; current = parent; parent = temp; i-- ; //allow the loop counter to work as expected } else break; } } } else break; } } else i = 0; //nothing in store, so start afresh //from deepest existing node, add missing children for (; i < count; i++) { if (i == 0) { //not even a root node found #ifdef E2_VFSTMP //FIXME other virtual root ok ? #endif name = G_DIR_SEPARATOR_S; gtk_tree_store_insert_before (store, current, NULL, NULL); } else { name = split[i]; if (*name == '\0') continue; //ignore empty strings other than tree root //append new segment to parent's children gtk_tree_store_insert_before (store, current, parent, NULL); } #ifdef USE_GTK2_8 key = g_utf8_collate_key_for_filename (name, -1); #else key = g_utf8_collate_key (name, -1); #endif //populate it gtk_tree_store_set (store, current, DIRNAME, name, DIRKEY, key, TXTSET, FALSE, //since we've found some descendant, each missing dir must be readable TXTCOLOR, NULL, #ifndef ENABLE_FULL_TREE DIRFLAGS, E2_TRDLG_STORED, #endif -1); g_free (key); //remember this new addition tp = gtk_tree_model_get_path (model, current); gchar *pathnow = _e2_treedlg_make_path (model, tp); //FIXME handle possible NULL after error GtkTreeRowReference *ref = gtk_tree_row_reference_new (model, tp); g_hash_table_insert (dirhash, pathnow, ref); if (i == 0 && count == 2 && *split[1] == '\0') { //just adding a root entry g_strfreev (split); return tp; } gtk_tree_path_free (tp); //re-use the iters temp = current; current = parent; parent = temp; } g_strfreev (split); tp = gtk_tree_model_get_path (model, parent); return tp; } /** @brief callback function for recursive directory scanning This is called for each directory and non-hanging link to a directory in the filesystem tree. Any hidden dir will be shown despite the flag for showing hidden items, if such dir has non-hidden descendant(s). The treewalk has been setup for breadth-first, dirs only operation @param dirpath localised string, absolute path of discovered directory, no trailer @param statptr pointer to struct stat with info about @a dirpath @param status one of the treewalker-function status codes @param rt pointer to data struct for the dialog @return E2TW_CONTINUE on success, others as appropriate */ static E2_TwResult _e2_treedlg_twcb_scan (VPATH *dirpath, const struct stat *statptr, E2_TwStatus status, E2_TreeDialogRuntime *rt) { E2_TwResult retval; gboolean colored; GdkColor *foreground; E2_ERR_DECLARE retval = E2TW_CONTINUE; //default to ok result switch (status) { case E2TW_DRR: case E2TW_D: //dir #ifndef ENABLE_FULL_TREE case E2TW_DL: //directory, not opened due to tree-depth limit (=2) #endif if (!e2_fs_access (dirpath, R_OK E2_ERR_PTR())) //want X_OK too ? { //add normal item to treestore, if needed colored = FALSE; foreground = NULL; //default_color; break; } #ifdef E2_VFS else { #ifdef E2_VFSTMP //FIXME handle error #endif E2_ERR_CLEAR } #endif #ifdef ENABLE_FULL_TREE case E2TW_DL: //directory, not opened due to tree-depth limit #endif case E2TW_DNR: //unreadable directory has been handled when it's 1st reported, so color is correct case E2TW_DM: //directory, not opened due to different file system case E2TW_NS: //un-statable item (for which, error is reported upstream) //Can usefully return only CONTINUE or STOP to the treewalker in this case //NOTE hanging dirlinks are identified as non-dirs, and not reported here //add error item, if needed colored = TRUE; foreground = bad_color; break; case E2TW_DP: //directory, all subdirs have been visited #ifndef ENABLE_FULL_TREE { GtkTreeRowReference *ref = g_hash_table_lookup (rt->dirhash, dirpath); if (ref != NULL) //maybe dir hidden and not stored { GtkTreeIter iter; GtkTreePath *tp = gtk_tree_row_reference_get_path (ref); if (tp != NULL) { gtk_tree_model_get_iter (GTK_TREE_MODEL (rt->store), &iter, tp); guint iterflags; gtk_tree_model_get (GTK_TREE_MODEL (rt->store), &iter, DIRFLAGS, &iterflags, -1); iterflags |= E2_TRDLG_SCANNED; //CHECKME used ?? gtk_tree_store_set (rt->store, &iter, DIRFLAGS, iterflags, -1); } else { printd (DEBUG, "oops, at 1 failed to get iter for %s", dirpath); g_hash_table_remove (rt->dirhash, dirpath); } } retval = E2TW_FIXME; //just a flag to prevent adding to treestore } break; #else break; #endif // case E2TW_DNR: //unreadable directory has been handled when it's 1st reported, so color is correct // retval = E2TW_FIXME; // break; /*never happen in this context case E2TW_F: //file case E2TW_SL: //link to non-dir case E2TW_SLN: //bad link to non-dir retval = ??; break; */ default: retval = E2TW_STOP; //should never happen break; } //FIXME use filter-model and just re-filter for hidden-items [un]display ? if (retval == E2TW_CONTINUE) { //at dialog startup, the root path is not yet scanned FIXME more elegant way to handle this if (g_str_equal (dirpath, G_DIR_SEPARATOR_S)) return retval; gchar *lastseg = strrchr (VPSTR (dirpath), G_DIR_SEPARATOR); if (lastseg != NULL) //should always succeed { #ifdef STRICT_HIDE if (rt->show_hidden || !ITEM_ISHIDDEN ((lastseg + sizeof (gchar)))) #else //unless hiding is strict, always add dirs and cleanup later if (!rt->strict_hide || !ITEM_ISHIDDEN ((lastseg + sizeof (gchar)))) #endif { //don't add an item if it's already there #ifndef ENABLE_FULL_TREE guint iterflags = E2_TRDLG_NONE; #endif GtkTreeIter iter, parent; GtkTreePath *tp; #ifndef ENABLE_FULL_TREE GtkTreeRowReference *ref = g_hash_table_lookup (rt->dirhash, dirpath); if (ref != NULL) { tp = gtk_tree_row_reference_get_path (ref); if (tp != NULL) { gtk_tree_model_get_iter (GTK_TREE_MODEL (rt->store), &iter, tp); gtk_tree_model_get (GTK_TREE_MODEL (rt->store), &iter, DIRFLAGS, &iterflags, -1); if (iterflags & E2_TRDLG_STORED) return retval; } else { printd (DEBUG, "oops, at 2 failed to get iter for %s", dirpath); g_hash_table_remove (rt->dirhash, dirpath); } } #endif const gchar *parentpath; if (lastseg == VPSTR (dirpath)) { #ifdef E2_VFSTMP //FIXME alternate tree roots ? #endif parentpath = G_DIR_SEPARATOR_S; //we're handling a 1st-level dir } else { *lastseg = '\0'; parentpath = VPCSTR (dirpath); } //confirm that parent path is already in treestore if ((ref = g_hash_table_lookup (rt->dirhash, parentpath)) != NULL) tp = gtk_tree_row_reference_get_path (ref); else //initial entry, or probably hidden and not sofar displayed //due to FALSE show-hidden flag //add a node corresponding to parentpath tp = _e2_treedlg_infill (rt->store, parentpath, rt->dirhash); *lastseg = G_DIR_SEPARATOR; //reinstate possibly-clobbered separator GtkTreeModel *model = GTK_TREE_MODEL (rt->store); if (gtk_tree_model_get_iter (model, &parent, tp)) { gchar *utf, *key; //store data for the item utf = F_FILENAME_FROM_LOCALE (lastseg+sizeof(gchar)); #ifdef USE_GTK2_8 key = g_utf8_collate_key_for_filename (utf, -1); #else key = g_utf8_collate_key (utf, -1); #endif //append new segment to parent's children #ifdef USE_GTK2_10 gtk_tree_store_insert_with_values (rt->store, &iter, &parent, -1, #else gtk_tree_store_insert_before (rt->store, &iter, &parent, NULL); //populate it gtk_tree_store_set (rt->store, &iter, #endif DIRNAME, utf, DIRKEY, key, TXTSET, colored, TXTCOLOR, foreground, #ifndef ENABLE_FULL_TREE DIRFLAGS, E2_TRDLG_STORED, #endif -1); F_FREE (utf); g_free (key); //remember this new addition tp = gtk_tree_model_get_path (model, &iter); ref = gtk_tree_row_reference_new (model, tp); g_hash_table_insert (rt->dirhash, g_strdup (VPSTR(dirpath)), ref); gtk_tree_path_free (tp); } else printd (DEBUG, "directories treestore - missing iterator error"); } #ifndef ENABLE_FULL_TREE else { GdkColor *current; //get parent iter GtkTreeIter parent; const gchar *parentpath; if (lastseg == VPSTR (dirpath)) { #ifdef E2_VFSTMP //FIXME alternate tree roots ? #endif parentpath = G_DIR_SEPARATOR_S; //we're handling a 1st-level dir } else { *lastseg = '\0'; parentpath = VPCSTR (dirpath); } //confirm that parent path is already in treestore GtkTreePath *tp; GtkTreeRowReference *ref; if ((ref = g_hash_table_lookup (rt->dirhash, parentpath)) != NULL) tp = gtk_tree_row_reference_get_path (ref); else //initial entry, or probably hidden and not sofar displayed //due to FALSE show-hidden flag //add a node corresponding to parentpath tp = _e2_treedlg_infill (rt->store, parentpath, rt->dirhash); *lastseg = G_DIR_SEPARATOR; //reinstate possibly-clobbered separator GtkTreeModel *model = GTK_TREE_MODEL (rt->store); if (gtk_tree_model_get_iter (model, &parent, tp)) { gtk_tree_model_get (GTK_TREE_MODEL(rt->store), &parent, TXTCOLOR, ¤t, -1); if (current == NULL) //no assigned color yet gtk_tree_store_set (rt->store, &parent, TXTSET, TRUE, TXTCOLOR, hidden_color, -1); } retval = E2TW_SKIPSUB; } #endif } // printd (DEBUG, "Processed directory %s", dirpath); } else if (retval == E2TW_FIXME) { // printd (DEBUG, "Ignored callback for %s", dirpath); retval = E2TW_CONTINUE; } else printd (DEBUG, "Error code for %s is %d", dirpath, errno); return retval; //must report (some/all?) errors, else it keeps trying & hangs } /** @brief populate empty treestore for directories-tree dialog Directories data are obtained and stored for all dirs in @a startpath This expects BGL to be closed/on @param startpath absolute utf-8 path-string, or NULL to use displayed dir in the pane @param rt pointer to data struct for the dialog @return */ static void _e2_treedlg_fill_store (gchar *startpath, E2_TreeDialogRuntime *rt) { //once-off setup of text color bad_color = e2_option_color_get ("color-negative"); hidden_color = e2_option_color_get ("color-positive"); //setup hash for speedy path relations rt->dirhash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) gtk_tree_row_reference_free); //#ifdef ENABLE_FULL_TREE //FIXME should be done in _e2_treedlg_row_expanded_cb () //disconnect view from store g_object_ref (G_OBJECT (rt->store)); gtk_tree_view_set_model (GTK_TREE_VIEW (rt->treeview), NULL); //#ifdef ENABLE_FULL_TREE GtkTreeSortable *sortable = GTK_TREE_SORTABLE (rt->store); gtk_tree_sortable_set_sort_column_id (sortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING); //#endif #ifdef ENABLE_FULL_TREE # ifdef E2_VFSTMP //FIXME vfs, and other namespaces # endif gdk_threads_leave (); //if (! e2_fs_tw (G_DIR_SEPARATOR_S, _e2_treedlg_twcb_scan, rt, -1, #ifdef WARNBADS E2TW_QT | #else E2TW_XQT | #endif E2TW_ONLYDIR E2_ERR_NONE()) //) //{ //FIXME handle error //} //} gdk_threads_enter (); #else # ifndef STRICT_HIDE //when doing this expansion, we don't want to cleanup each level individually gboolean wasnot_hidden = (rt->strict_hide) ? FALSE : !rt->show_hidden; if (wasnot_hidden) rt->show_hidden = TRUE; # endif //ndef STRICT_HIDE //process each root-dir child and mark its iter 'scanned' GtkTreeIter iter; //a new treestore has no root iter, and that isn't covered in the startpath //loop, so ... GtkTreePath *tp = _e2_treedlg_infill (rt->store, # ifdef E2_VFSTMP //FIXME other roots too ? # endif G_DIR_SEPARATOR_S, rt->dirhash); gtk_tree_model_get_iter (GTK_TREE_MODEL (rt->store), &iter, tp); _e2_treedlg_row_expanded_cb (GTK_TREE_VIEW (rt->treeview), &iter, tp, rt); gchar *showpath = (startpath == NULL) ? rt->view->dir : startpath; gchar **split = g_strsplit (showpath, G_DIR_SEPARATOR_S, -1); guint i, count = g_strv_length (split); if (count > 1) //should always pass for a valid startpath string { gchar *freeme, *madepath = g_strdup (""); for (i = 1; i < count; i++) { if (*split[i] != '\0') { freeme = madepath; madepath = g_strconcat (madepath, G_DIR_SEPARATOR_S, split[i], NULL); GtkTreeRowReference *ref = g_hash_table_lookup (rt->dirhash, madepath); tp = (ref == NULL) ? //should pass this test only in first loop _e2_treedlg_infill (rt->store, madepath, rt->dirhash): gtk_tree_row_reference_get_path (ref); if (tp != NULL) { gtk_tree_model_get_iter (GTK_TREE_MODEL (rt->store), &iter, tp); _e2_treedlg_row_expanded_cb (GTK_TREE_VIEW (rt->treeview), &iter, tp, rt); g_free (freeme); } else { printd (DEBUG, "oops, at 3 failed to get iter for %s", madepath); if (ref != NULL) g_hash_table_remove (rt->dirhash, madepath); } } } g_free (madepath); } g_strfreev (split); # ifndef STRICT_HIDE if (wasnot_hidden) { //cleanup erroneous hidden leaves gtk_tree_model_get_iter_first (GTK_TREE_MODEL (rt->store), &iter); //no error check needed _e2_treedlg_clean_hidden (GTK_TREE_MODEL (rt->store), &iter, rt->dirhash); rt->show_hidden = FALSE; } # endif #endif //#ifdef ENABLE_FULL_TREE gtk_tree_sortable_set_sort_column_id (sortable, DIRNAME, GTK_SORT_ASCENDING); //#endif //#ifdef ENABLE_FULL_TREE //FIXME should be done in _e2_treedlg_row_expanded_cb () gtk_tree_view_set_model (GTK_TREE_VIEW (rt->treeview), GTK_TREE_MODEL (rt->store)); g_object_unref (G_OBJECT (rt->store)); #ifdef ENABLE_FULL_TREE g_hash_table_destroy (rt->dirhash); #endif } /********************/ /*** context menu ***/ /********************/ /** @brief set popup-menu position This function is supplied when calling gtk_menu_popup(), to position the displayed menu, after a menu-key press. set @a push_in to TRUE for menu completely inside the screen, FALSE for menu clamped to screen size @param menu UNUSED the GtkMenu to be positioned @param x place to store gint representing the menu left @param y place to store gint representing the menu top @param push_in place to store pushin flag @param rt pointer to data struct for the dialog to which the menu applies @return */ static void _e2_treedlg_set_menu_position (GtkMenu *menu, gint *x, gint *y, gboolean *push_in, E2_TreeDialogRuntime *rt) { gint left, top; gtk_window_get_position (GTK_WINDOW (rt->dialog), &left, &top); GtkAllocation alloc = rt->treeview->allocation; *x = left + alloc.x + alloc.width/2; *y = top + alloc.y +alloc.height/2 - 30; *push_in = FALSE; } /** @brief tree path copy callback @param widget UNUSED the menu item widget which activated the callback @param treeview treeview to which the menu belongs @return */ static void _e2_treedlg_copy_cb (GtkWidget *widget, GtkTreeView *treeview) { GtkTreeIter iter; GtkTreeModel *model; GtkTreeSelection *sel = gtk_tree_view_get_selection (treeview); gtk_tree_selection_get_selected (sel, &model, &iter); GtkTreePath *path = gtk_tree_model_get_path (model, &iter); gchar *str = _e2_treedlg_make_path (model, path); if (str != NULL) { GtkClipboard *clip = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); gtk_clipboard_set_text (clip, str, strlen (str)); g_free (str); } gtk_tree_path_free (path); } /** @brief tree collapse-all callback @param widget UNUSED the menmu-item widget which was selected @param treeview treeview to which the menu belongs @return */ static void _e2_treedlg_collapse_all_cb (GtkWidget *widget, GtkTreeView *treeview) { gtk_tree_view_collapse_all (treeview); GtkTreePath *tp = gtk_tree_path_new_first (); gtk_tree_view_expand_row (treeview, tp, FALSE); gtk_tree_path_free (tp); } /** @brief treeview refresh callback This can also be called directly, in which case BGL must be closed @param widget UNUSED the menu item widget which activated the callback @param rt pointer to data struct for the dialog @return */ static void _e2_treedlg_refresh_cb (GtkWidget *widget, E2_TreeDialogRuntime *rt) { //FIXME use filter-model and just re-filter ? GtkTreeModel *model; GtkTreeIter iter; GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (rt->treeview)); if (gtk_tree_selection_get_selected (sel, &model, &iter)) { GtkTreePath *tp = gtk_tree_model_get_path (model, &iter); gchar *pathstr = _e2_treedlg_make_path (model, tp); //pathstr = utf-8 gboolean exp = gtk_tree_view_row_expanded (GTK_TREE_VIEW (rt->treeview), tp); #ifndef ENABLE_FULL_TREE g_hash_table_destroy (rt->dirhash); #endif //disconnect view from model to minimise visual disruption and speedup g_object_ref (G_OBJECT (rt->store)); gtk_tree_view_set_model (GTK_TREE_VIEW (rt->treeview), NULL); gtk_tree_store_clear (rt->store); _e2_treedlg_fill_store (pathstr, rt); //re-attaches model //expand to former position _e2_treedlg_show_path (pathstr, exp, rt); g_object_unref (G_OBJECT (rt->store)); gtk_tree_path_free (tp); g_free (pathstr); } } #ifndef STRICT_HIDE /** @brief toggle display of hidden ancestors of visible dirs This is performed when the context-menu item 'strict hiding' is selected @param menuitem the clicked menu widget @param rt ptr to data struct for the dialog @return */ static void _e2_treedlg_toggle_strict_cb (GtkWidget *menuitem, E2_TreeDialogRuntime *rt) { rt->strict_hide = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (menuitem)); _e2_treedlg_refresh_cb (NULL, rt); } #endif /** @brief construct and show dialog context menu @param treeview the widget where the click happened @param event_button which mouse button was clicked (0 for a menu key) @param event_time time that the event happened (0 for a menu key) @param rt pointer to data struct for the dialog @return */ static void _e2_treedlg_show_context_menu (GtkWidget *treeview, guint event_button, gint event_time, E2_TreeDialogRuntime *rt) { GtkTreeView *tvw = GTK_TREE_VIEW (treeview); GtkTreeSelection *sel = gtk_tree_view_get_selection (tvw); GtkWidget *menu = gtk_menu_new (); GtkWidget *item = e2_menu_add (menu, _("_Copy"), GTK_STOCK_COPY, _("Copy selected path"), _e2_treedlg_copy_cb, tvw); if (!gtk_tree_selection_get_selected (sel, NULL, NULL)) gtk_widget_set_sensitive (item, FALSE); e2_menu_add (menu, _("C_ollapse"), GTK_STOCK_ZOOM_OUT, _("Collapse all paths"), _e2_treedlg_collapse_all_cb, tvw); e2_menu_add (menu, _("_Refresh"), GTK_STOCK_REFRESH, NULL, _e2_treedlg_refresh_cb, rt); #ifndef STRICT_HIDE item = e2_menu_add_check (menu, _("_Strict hiding"), rt->strict_hide, _e2_treedlg_toggle_strict_cb, rt); if (!rt->show_hidden) #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif item, _("If active, hidden ancestors of visible directories will not be displayed")); gtk_widget_set_sensitive (item, !rt->show_hidden); #endif g_signal_connect (G_OBJECT (menu), "selection-done", G_CALLBACK (e2_menu_destroy_cb), NULL); if (event_button == 0) gtk_menu_popup (GTK_MENU (menu), NULL, NULL, (GtkMenuPositionFunc) _e2_treedlg_set_menu_position, rt, event_button, event_time); else //this was a button-3 click gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, event_button, event_time); } /*********************/ /***** callbacks *****/ /*********************/ /** @brief mouse button press callback @param treeview the widget where the button was pressed @param event gdk event data @param rt pointer to data struct for the dialog @return TRUE (stop other handlers) for btn 3 press, else FALSE */ static gboolean _e2_treedlg_button_press_cb (GtkWidget *treeview, GdkEventButton *event, E2_TreeDialogRuntime *rt) { printd (DEBUG, "callback: _e2_treedlg mouse button press"); if (event->button == 3) { _e2_treedlg_show_context_menu (treeview, 3, event->time, rt); return TRUE; } return FALSE; } /** @brief treeview key-press callback @param widget UNUSED the focused treeview widget when the key was pressed @param event pointer to event data struct @param rt pointer to data struct for the dialog @return TRUE (stop other handlers) for menu key has, else FALSE */ /*static gboolean _e2_treedlg_key_press_cb (GtkWidget *treeview, GdkEventKey *event, E2_TreeDialogRuntime *rt) { printd (DEBUG, "callback: _e2_treedlg key press"); return FALSE; } */ /** @brief menu-button press callback @param treeview the widget where the press happened @param rt dialog runtime data struct @return TRUE always */ static gboolean _e2_treedlg_popup_menu_cb (GtkWidget *treeview, E2_TreeDialogRuntime *rt) { gint event_time = gtk_get_current_event_time (); _e2_treedlg_show_context_menu (treeview, 0, event_time, rt); return TRUE; } /** @brief treeview row-activated callback Activation is triggered when is pressed or when a double-click happens This causes the activated item to be opened in the corresponding filelist @param treeview the widget where the button was pressed @param path model path to the clicked row @param col UNUSED clicked view column @param view data struct for the view to be worked on @return */ static void _e2_treedlg_row_activated_cb ( GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *col, ViewInfo *view) { printd (DEBUG, "callback: _e2_treedlg_row_activated"); GtkTreeIter iter; GtkTreeModel *model = gtk_tree_view_get_model (treeview); if (gtk_tree_model_get_iter (model, &iter, path)) { //get the activated path gchar *newpath = _e2_treedlg_make_path (model, path); if (newpath != NULL) { //show that in associated filepane E2_PaneRuntime *rt = (view == &app.pane1_view) ? &app.pane1 : &app.pane2 ; e2_pane_change_dir (rt, newpath); g_free (newpath); } } } #ifndef ENABLE_FULL_TREE /** @brief callback for treeview "row-expanded" signal If the expanded row is not 'ready', check each child dir there. Any child not 'scanned', will be scanned and markes as such. Downstream code expects @a treeview to be connected to a treemodel This expects BGL to be closed, and the dialog window must exist @param treeview the object on which the signal was emitted @param iter the tree iter of the expanded row @param path tree path that points to the row @param rt pointer to data struct for the dialog @return */ static void _e2_treedlg_row_expanded_cb (GtkTreeView *treeview, GtkTreeIter *iter, GtkTreePath *path, E2_TreeDialogRuntime *rt) { guint iterflags; GtkTreeModel *model = GTK_TREE_MODEL (rt->store); //maybe disconnected from treeview gtk_tree_model_get (model, iter, DIRFLAGS, &iterflags, -1); if (!(iterflags & E2_TRDLG_GRANDSCANNED)) { //expanded row is not 'ready' to be expanded // if (GDK_IS_WINDOW (rt->dialog->window)) if window created after view, at opening e2_dialog_set_cursor (rt->dialog, GDK_WATCH); //almost certainly ok without reference, as any addtions are later in the store, but ... GtkTreeRowReference *ref = gtk_tree_row_reference_new (model, path); //check each child dir there gchar *thisdir = _e2_treedlg_make_path (model, path); //should never be NULL //FIXME disconnect view from store without collapsing view // g_object_ref (G_OBJECT (rt->store)); //?lots of refs here no clobber-risk ? // gtk_tree_view_set_model (GTK_TREE_VIEW (rt->treeview), NULL); GtkTreeSortable *sortable = GTK_TREE_SORTABLE (rt->store); gint sortcol; GtkSortType sortorder; gtk_tree_sortable_get_sort_column_id (sortable, &sortcol, &sortorder); gtk_tree_sortable_set_sort_column_id (sortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING); #ifndef STRICT_HIDE //hack: when doing this expansion, must process all hidden ancestor items //but we really only want hidden ancestors, not hidden "leaves", so delete //leaves in function-call below gboolean wasnot_hidden = (rt->strict_hide) ? FALSE : !rt->show_hidden; if (wasnot_hidden) rt->show_hidden = TRUE; #endif #ifdef E2_VFS VPATH ddata = { thisdir, rt->view->spacedata }; #endif gdk_threads_leave (); //if (! #ifdef E2_VFS e2_fs_tw (&ddata, _e2_treedlg_twcb_scan, rt, 2, #else e2_fs_tw (thisdir, _e2_treedlg_twcb_scan, rt, 2, #endif #ifdef WARNBADS E2TW_QT | #else E2TW_XQT | #endif E2TW_ONLYDIR E2_ERR_NONE()); //) //{ //FIXME handle error //} gdk_threads_enter (); GtkTreePath *tp = gtk_tree_row_reference_get_path (ref); if (tp != NULL) { gtk_tree_model_get_iter (model, iter, tp); //mark expanded iter as 'ready' gtk_tree_model_get (model, iter, DIRFLAGS, &iterflags, -1); iterflags |= E2_TRDLG_GRANDSCANNED; gtk_tree_store_set (rt->store, iter, DIRFLAGS, iterflags, -1); #ifndef STRICT_HIDE if (wasnot_hidden) { //cleanup erroneous hidden leaves //FIXME handle .hidden1/.hidden2/displayed, .hidden2 is removed but then .hidden1 is kept _e2_treedlg_clean_hidden (GTK_TREE_MODEL (rt->store), iter, rt->dirhash); rt->show_hidden = FALSE; } #endif } else { printd (DEBUG, "oops, at 4 failed to get iter for %s", thisdir); g_hash_table_remove (rt->dirhash, thisdir); } g_free (thisdir); gtk_tree_row_reference_free (ref); //when filling whole store this will leave the sort column unsorted ?? gtk_tree_sortable_set_sort_column_id (sortable, sortcol, GTK_SORT_ASCENDING); // gtk_tree_view_set_model (GTK_TREE_VIEW (rt->treeview), model); // g_object_unref (G_OBJECT (rt->store)); //see above // if (GDK_IS_WINDOW (rt->dialog->window)) e2_dialog_set_cursor (rt->dialog, GDK_LEFT_PTR); } } #endif //ndef ENABLE_FULL_TREE /** @brief handle button click, window-close etc for directory-tree dialog This is the callback for response signals emitted from @a dialog @param dialog UNUSED the dialog where the response was generated @param response the response returned from the dialog @param rt pointer to data struct for the dialog @return */ static void _e2_treedlg_response_cb (GtkDialog *dialog, gint response, E2_TreeDialogRuntime *rt) { switch (response) { case E2_RESPONSE_USER1: //toggle display of hidden items rt->show_hidden = !rt->show_hidden; _e2_treedlg_refresh_cb (NULL, rt); //do the content before changing button e2_button_set_image (rt->hiddenbtn, (rt->show_hidden) ? "hidden_noshow_"E2IP".png" : "hidden_show_"E2IP".png"); break; default: gtk_widget_hide (rt->dialog); #ifndef ENABLE_FULL_TREE g_hash_table_destroy (rt->dirhash); #endif show_hidden = rt->show_hidden; //backups for later used this session #ifndef STRICT_HIDE strict_hide = rt->strict_hide; #endif window_width = rt->dialog->allocation.width; window_height = rt->dialog->allocation.height; gtk_widget_destroy (rt->dialog); DEALLOCATE (E2_TreeDialogRuntime, rt); break; } } /** @brief create and show filesystem tree with focus on dir associated with @a view This is a thread function @param view data struct for file pane with which the treeview is to be associated @return NULL */ static gpointer _e2_tree_dialog_run (ViewInfo *view) { printd (DEBUG, "create tree dialog"); E2_TreeDialogRuntime *rt = ALLOCATE (E2_TreeDialogRuntime); CHECKALLOCATEDWARN (rt, return NULL;); rt->show_hidden = show_hidden; //before dialog is filled CHECKME use view->show_hidden ? #ifndef STRICT_HIDE rt->strict_hide = strict_hide; #endif // memset (rt->itable, 0, sizeof(rt->itable)); gchar *title = (view == &app.pane1_view) ? _("pane 1 navigator") : _("pane 2 navigator") ; rt->dialog = e2_dialog_create (NULL, NULL, title, _e2_treedlg_response_cb, rt); //scrolled window for the treeview GtkWidget *sw = e2_widget_get_sw (GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, GTK_SHADOW_ETCHED_IN); gtk_scrolled_window_set_placement (GTK_SCROLLED_WINDOW (sw), e2_option_int_get ("scrollbar-position")); gtk_container_add ((GTK_CONTAINER (GTK_DIALOG (rt->dialog)->vbox)), sw); //dirnames treestore has single node, 5 columns (4 hidden) //see enumerator at start of this file //2nd is for sortable name key, 3rd & 4th for re-coloring "bad" entries //5th for flags rt->store = gtk_tree_store_new (DIRCOLCOUNT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, GDK_TYPE_COLOR, G_TYPE_UINT); rt->view = view; //dirnames treeview rt->treeview = gtk_tree_view_new_with_model (GTK_TREE_MODEL (rt->store)); g_object_unref (rt->store); gtk_container_add (GTK_CONTAINER (sw), rt->treeview); GtkCellRenderer *renderer = gtk_cell_renderer_text_new (); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (rt->treeview), DIRNAME, NULL, renderer, "text", DIRNAME, "foreground-set", TXTSET, "foreground-gdk", TXTCOLOR, NULL); //by default, type-ahead searching is enabled on column 0 // gtk_tree_view_set_search_equal_func (rt->treeview, // (GtkTreeViewSearchEqualFunc)_e2_fileview_match_filename, view, NULL); #ifdef USE_GTK2_10 gtk_tree_view_set_enable_tree_lines (GTK_TREE_VIEW (rt->treeview), TRUE); #endif // gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (rt->treeview), TRUE); GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (rt->treeview)); gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); GtkTreeSortable *sortable = GTK_TREE_SORTABLE (rt->store); gtk_tree_sortable_set_sort_func (sortable, DIRNAME, //gint sort_column_id _e2_treedlg_name_sort, //GtkTreeIterCompareFunc sort_func, NULL, //gpointer user_data, NULL); //GtkDestroyNotify destroy //relate initial size to last-used, or if first, to filepanes size if (window_width == -1) window_width = MIN(300, app.window.panes_paned->allocation.width/2); if (window_height == -1) window_height = app.window.panes_paned->allocation.height; gtk_window_resize (GTK_WINDOW(rt->dialog), window_width, window_height); g_signal_connect (G_OBJECT (rt->treeview), "popup-menu", G_CALLBACK (_e2_treedlg_popup_menu_cb), rt); // g_signal_connect_after (G_OBJECT (rt->treeview), "key-press-event", // G_CALLBACK (_e2_treedlg_key_press_cb), rt); g_signal_connect (G_OBJECT (rt->treeview), "button-press-event", G_CALLBACK (_e2_treedlg_button_press_cb), rt); g_signal_connect (G_OBJECT (rt->treeview), "row-activated", G_CALLBACK (_e2_treedlg_row_activated_cb), view); #ifndef ENABLE_FULL_TREE g_signal_connect (G_OBJECT (rt->treeview), "row-expanded", G_CALLBACK (_e2_treedlg_row_expanded_cb), rt); #endif rt->hiddenbtn = e2_dialog_add_undefined_button_custom (rt->dialog, FALSE, E2_RESPONSE_USER1, _("_Hidden"), (rt->show_hidden) ? "hidden_noshow_"E2IP".png" : "hidden_show_"E2IP".png", _("Toggle display of hidden directories"), NULL, NULL); E2_BUTTON_CLOSE.showflags |= E2_BTN_DEFAULT; //set default button e2_dialog_show (rt->dialog, app.main_window, E2_DIALOG_THREADED, &E2_BUTTON_CLOSE, NULL); gdk_threads_enter (); //get the dir names _e2_treedlg_fill_store (NULL, rt); //show and select the startup row corresponding to displayed dir _e2_treedlg_show_path (rt->view->dir, TRUE, rt); gdk_threads_leave (); return NULL; } /****************/ /**** public ****/ /****************/ /** @brief show tree dialog action This creates a thread to produce the dialog, because the directories scan can be slow @param from UNUSED the button, menu item etc which was activated @param art UNUSED action runtime data @return TRUE */ gboolean e2_tree_dialog_show_action (gpointer from, E2_ActionRuntime *art) { g_thread_create ((GThreadFunc) _e2_tree_dialog_run, curr_view, FALSE, NULL); return TRUE; } #endif //def E2_TREEDIALOG emelfm2-0.4.1/src/dialogs/e2_view_dialog.h0000600000175000017500000001340611010340377017250 0ustar cairocairo/* $Id: e2_view_dialog.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_VIEW_DIALOG_H__ #define __E2_VIEW_DIALOG_H__ #ifdef E2_SPELLCHECK //NOTE to access internals for the dialog's context-menu-creation, we must //use a patched gtk-spell #include #endif //include redo capability #define E2_REDO_ENABLED //include highlight capability #define E2_MARK_FINDS enum { INS = 0, BS, DEL }; typedef struct _E2_UndoInfo { gchar command; //one of INS, BS, DEL gchar *str; //buffer text that has been changed in accord with command //guint offsets allow for reasonably large files ... guint start; //text-buffer offset to start of str guint end; //text-buffer offset to end of str gboolean seq; // sequency flag ?? } E2_UndoInfo; //this struct is used for viewing and editing, to enable shared functions typedef struct _E2_ViewDialogRuntime { GtkWidget *dialog; GtkWidget *textview; GtkTextBuffer *textbuffer; gchar *localpath; //localised absolute path string #ifdef E2_VFS PlaceInfo *spacedata; #endif gint window_width; gint window_height; // guint idle_id; //all search dialogs in session use same values for these, hence static gboolean case_sensitive; gboolean search_backward; gboolean whole_words; gboolean search_wrap; gboolean textwrap; #ifdef E2_TRANSIENTKEYS gchar *key_binding; //name of key-binding category #endif //search things GtkWidget *panel; GtkWidget *combo; GtkWidget *info_label; GtkWidget *hidebtn; GtkWidget *findbtn; GtkSizeGroup *sgroup; GList *history; gint history_cur; gboolean is_hidden; //reflects hidden property of panel widget; gboolean release_blocked;//flag to manage key-release events which are not for the expected widget //edit things GtkWidget *replacebtn; GtkWidget *savebtn; GtkWidget *combo2; //replacements GtkWidget *replacebar; //for hiding gint linebreak; //type of line-separator in loaded file, 10, 13 or 23 (=CR+lF) const gchar *charset; //name of file's character encoding (for internal conversion) GList *rephistory; gint rephistory_cur; #ifdef E2_MARK_FINDS gboolean research; //TRUE when whole_words or case_sensitive has been changed, until next highlight update gboolean is_lit; #endif gboolean is_dirty; gboolean saved_ok; gboolean saveas; //TRUE when saving to a different name const gchar *newlocalpath; //used for saving as .. gboolean ow_mode; //T = overwrite, F = insert guint blink_id; guint blink_init_id; GList *undo_list; //init to NULL; //list of UndoInfo's for changes made to buffer gboolean undo_enabled; //widget sensitivity controller #ifdef E2_REDO_ENABLED GList *redo_list; //init to NULL; //list of UndoInfo's for buffer changes undone gboolean redo_enabled; #endif GString *undo_gstr; //buffer for accumulating sequentially-entered characters E2_UndoInfo *ui_tmp; //template undo-data struct gboolean seq_reserve; //init to FALSE; //default value for ui->seq ?? guint changes_count; //no. of changes recorded in the undo list guint keyval; //keycode from the keypress callback guint prev_keyval; //remembered previous key, for sequencing #ifdef E2_SPELLCHECK GtkSpell *spelldata; #endif } E2_ViewDialogRuntime; typedef struct _E2_ViewHistory { gchar *localpath; //localised string, absolute path of a viewed item guint topline; //buffer y coordinate of top visible row when internal viewer was closed } E2_ViewHistory; //functions shared between view & edit dialogs #ifdef E2_MARK_FINDS void e2_view_dialog_init_hilites (E2_ViewDialogRuntime *rt); void e2_view_dialog_clear_hilites (E2_ViewDialogRuntime *rt); #endif gboolean e2_view_dialog_read_text (VPATH *localfile, E2_ViewDialogRuntime *rt); void e2_view_dialog_set_font (gint *char_width, gint *char_height, E2_ViewDialogRuntime *rt); void e2_view_dialog_set_menu_position (GtkWidget *menu, gint *x, gint *y, gboolean *push_in, E2_ViewDialogRuntime *rt); void e2_view_dialog_update_combo (GtkWidget *combo); gboolean e2_view_dialog_combokey_cb (GtkWidget *entry, GdkEventKey *event, E2_ViewDialogRuntime *rt); #ifdef USE_GTK2_10 void e2_view_dialog_print_cb (GtkWidget *menuitem, E2_ViewDialogRuntime *rt); #endif void e2_view_dialog_toggled (GtkWidget *button, gboolean *store); GtkWidget *e2_view_dialog_create_searchbar (E2_ViewDialogRuntime *rt); gboolean e2_view_dialog_search (gboolean first, gboolean incremental, E2_ViewDialogRuntime *rt); void e2_view_dialog_destroy (E2_ViewDialogRuntime *rt); void e2_view_dialog_show_atlast (VPATH *localpath, E2_ViewDialogRuntime *rt); gboolean e2_view_dialog_create (VPATH *localpath); gboolean e2_view_dialog_create_immediate (VPATH *view_this); void e2_view_dialog_actions_register (void); void e2_view_dialog_options_register (void); //FIXME location of these gboolean e2_edit_dialog_create (VPATH *localpath, GtkTextBuffer *buf); gboolean e2_edit_dialog_key_press_cb (GtkWidget *textview, GdkEventKey *event, E2_ViewDialogRuntime *rt); void e2_edit_dialog_save_selected (GtkTextBuffer *buffer, #ifdef E2_VFS PlaceInfo *spacedata, #endif GtkWidget *parent); void e2_edit_dialog_actions_register (void); void e2_edit_dialog_options_register (void); #endif //ndef __E2_VIEW_DIALOG_H__ emelfm2-0.4.1/src/dialogs/e2_size_filter_dialog.c0000600000175000017500000001537310721376361020627 0ustar cairocairo/* $Id: e2_size_filter_dialog.c 748 2007-11-22 22:04:33Z tpgww $ Copyright (C) 2003-2007 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "emelfm2.h" #include #include "e2_dialog.h" #include "e2_filelist.h" typedef struct _E2_SzFltDlgData { GtkWidget *dialog; //the created dialog GtkWidget *operation_combo; GtkWidget *size_entry; GtkWidget *units_combo; // GtkWidget *menu_item; //copy of pointer to clicked filters menu item // gboolean itemstate; //initial state of menu_item // gboolean blocked = FALSE; //flag to prevent toggles when not wanted ViewInfo *view; //data for filelist being processed } E2_SzFltDlgData; /*static void _e2_size_filter_dialog_item_toggle_cb (void) { gulong handler = g_signal_handler_find (menu_item, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, e2_size_filter_dialog_create_cb, NULL); g_signal_handler_block (menu_item, handler); gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item), !itemstate); g_signal_handler_unblock (menu_item, handler); } */ static void _e2_size_filter_dialog_ok (E2_SzFltDlgData *rt) { gint index = gtk_combo_box_get_active (GTK_COMBO_BOX (rt->operation_combo)); rt->view->size_filter.op = index; const gchar *s = gtk_entry_get_text (GTK_ENTRY (rt->size_entry)); gdouble dsize = atof (s); index = gtk_combo_box_get_active (GTK_COMBO_BOX (rt->units_combo)); if (index == 1 || index == 2) //kb or Mb respectively dsize *= pow (1024, index); rt->view->size_filter.size = (size_t)dsize; rt->view->size_filter.active = TRUE; //show the results e2_toolbar_toggle_filter_button (rt->view); e2_fileview_refilter_list (rt->view); #ifdef E2_STATUS_DEMAND if (rt->view == curr_view) e2_window_update_status_bar (NULL); #endif } /** @brief dialog response callback @param dialog the permissions-dialog @param response the response for the clicked button @param rt pointer to dialog data struct @return */ static void _e2_size_filter_dialog_response_cb (GtkDialog *dialog, gint response, E2_SzFltDlgData *rt) { switch (response) { case GTK_RESPONSE_OK: gtk_widget_hide (rt->dialog); _e2_size_filter_dialog_ok (rt); //prevent toggle when dialog is destroyed // blocked = TRUE; break; case E2_RESPONSE_REMOVE: //update the cached copy of the state rt->view->size_filter.active = FALSE; gtk_widget_hide (rt->dialog); //show the results e2_toolbar_toggle_filter_button (rt->view); e2_fileview_refilter_list (rt->view); #ifdef E2_STATUS_DEMAND if (rt->view == curr_view) e2_window_update_status_bar (NULL); #endif //prevent toggle when dialog is destroyed // blocked = TRUE; break; // case GTK_RESPONSE_CANCEL: default: //the menu item will be re-toggled when dialog is destroyed break; } gtk_widget_destroy (rt->dialog); DEALLOCATE (E2_SzFltDlgData, rt); // gtk_main_quit (); } /** @brief handle Return keypresses in the size entry @param entry UNUSED the entry widget for the combo box @param rt pointer to dialog data struct @return */ static void _e2_size_filter_dialog_activated_cb (GtkEntry *entry, E2_SzFltDlgData *rt) { _e2_size_filter_dialog_response_cb (GTK_DIALOG (rt->dialog), GTK_RESPONSE_OK, rt); } /** @brief create file size filter dialog The state of @a item when it arrives here is opposite to that shown in the menu, when clicked @param item the clicked checkbox widget in the fillters menu @param view data structure for the view to which the file list belongs @return */ void e2_size_filter_dialog_create_cb (GtkWidget *item, ViewInfo *view) { GtkWidget *hbox; gchar size_string[32]; //save local copies, for later use in other functions // menu_item = item; // itemstate = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM (item)); E2_SzFltDlgData *rt = ALLOCATE (E2_SzFltDlgData); CHECKALLOCATEDWARN (rt, return;); rt->view = view; rt->dialog = e2_dialog_create (NULL, _("Display only the items which are:"), _("size filter"), _e2_size_filter_dialog_response_cb, rt); hbox = e2_widget_add_box (GTK_DIALOG (rt->dialog)->vbox, TRUE, E2_PADDING, FALSE, FALSE, E2_PADDING); rt->operation_combo = e2_combobox_add (hbox, FALSE, 0, NULL, NULL, NULL, E2_COMBOBOX_MENU_STYLE); //the order of these matches the Operator enum in e2_fileview.h gchar *size_choices[3] = { _("smaller than"), _("equal to"), _("bigger than") }; e2_combobox_append_history_counted (rt->operation_combo, 3, size_choices); gint index = view->size_filter.op; gtk_combo_box_set_active (GTK_COMBO_BOX (rt->operation_combo), index) ; //0=1st entry index // gtk_widget_set_size_request (operation_combo, 150, 30); if (view->size_filter.size < (1 << 10)) { g_snprintf (size_string, sizeof (size_string), "%ld", (gulong) view->size_filter.size); index = 0; //0=1st combo entry index } else if (view->size_filter.size < (1 << 20)) { g_snprintf (size_string, sizeof (size_string), "%.2f", (gdouble)((gdouble)view->size_filter.size / (gdouble)(1 << 10))); index = 1; } else { g_snprintf (size_string, sizeof (size_string), "%.2f", (gdouble)((gdouble)view->size_filter.size / (gdouble)(1 << 20))); index = 2; } rt->size_entry = e2_widget_add_entry (hbox, size_string, FALSE, FALSE); gtk_widget_set_size_request (rt->size_entry, 100, 30); #ifdef E2_ASSISTED GtkWidget *label = (GtkWidget *) g_object_get_data (G_OBJECT (rt->dialog), "e2-dialog-label"); e2_widget_set_label_relations (GTK_LABEL (label), rt->size_entry); #endif //handle key-presses when the entry is focused g_signal_connect (G_OBJECT (rt->size_entry), "activate", G_CALLBACK (_e2_size_filter_dialog_activated_cb), rt); rt->units_combo = e2_combobox_add (hbox, FALSE, 0, NULL, NULL, NULL, E2_COMBOBOX_MENU_STYLE); gchar *units_names [3] = { _("bytes"), _("kbytes"), _("Mbytes") }; e2_combobox_append_history_counted (rt->units_combo, 3, units_names); gtk_combo_box_set_active (GTK_COMBO_BOX (rt->units_combo), index) ; //now the buttons // if (!itemstate) if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item))) //user has just toggled off e2_dialog_add_defined_button (rt->dialog, &E2_BUTTON_REMOVE); e2_dialog_show (rt->dialog, app.main_window, 0, &E2_BUTTON_CANCEL, &E2_BUTTON_OK, NULL); // gtk_main (); } emelfm2-0.4.1/src/dialogs/e2_password_dialog.c0000600000175000017500000002076610714030042020135 0ustar cairocairo/* $Id: e2_password_dialog.c 706 2007-11-06 09:13:06Z tpgww $ Copyright (C) 2007 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/dialogs/e2_password_dialog.c @brief functions to setup and cleanup password-related widgets for a dialog */ #include "e2_password_dialog.h" //#include #define E2_HINT_MSEC 600 //static assuming last-closed window sets size for next one in this session only //static gint window_width = -1; #ifdef USE_GTK2_10 static guint pwrefcount = 0; static guint hinttime; static gboolean hinted = FALSE; //TRUE when the last char in either used entry is displayed as plaintext #endif static gboolean plaintext = FALSE; //TRUE when all entered chars are echoed as is static gboolean hidden = FALSE; //TRUE when no entered char is to be echoed /** @brief toggle temporary display of last-entered character in the password This is a key-press callback for both password-entry entry widgets Toggles parameters in response to p, h, and for gtk >= 2.10, t @param entry pointer to the widget that received the keypress @param event event data struct @param rt pointer to data struct for the dialog @return TRUE if the key was one of the recognised ones */ static gboolean _e2_pwdlg_key_press_cb (GtkWidget *entry, GdkEventKey *event, E2_PWDataRuntime *rt) { if ((event->state & GDK_CONTROL_MASK) != 0) { if (event->keyval == GDK_p || event->keyval == GDK_P) { rt->plain = !rt->plain; gtk_entry_set_visibility (GTK_ENTRY (rt->pwentry1), rt->plain); gtk_entry_set_visibility (GTK_ENTRY (rt->pwentry2), rt->plain); return TRUE; } else if (event->keyval == GDK_h || event->keyval == GDK_H) { if (rt->plain) { rt->plain = FALSE; gtk_entry_set_visibility (GTK_ENTRY (rt->pwentry1), FALSE); gtk_entry_set_visibility (GTK_ENTRY (rt->pwentry2), FALSE); } rt->hide = !rt->hide; gunichar ch = (rt->hide) ? (gunichar) 0 : (gunichar) '*'; gtk_entry_set_invisible_char (GTK_ENTRY (rt->pwentry1), ch); gtk_entry_set_invisible_char (GTK_ENTRY (rt->pwentry2), ch); return TRUE; } #ifdef USE_GTK2_10 else if (event->keyval == GDK_t || event->keyval == GDK_T) { if (rt->plain) { rt->plain = FALSE; gtk_entry_set_visibility (GTK_ENTRY (rt->pwentry1), FALSE); gtk_entry_set_visibility (GTK_ENTRY (rt->pwentry2), FALSE); } rt->hint = !rt->hint; guint msec = (rt->hint) ? E2_HINT_MSEC : 0; GtkSettings *s = gtk_settings_get_default (); g_object_set (G_OBJECT (s), "gtk-entry-password-hint-timeout", msec, NULL); return TRUE; } #endif } return FALSE; } /** @brief setup password-related widgets to get user input with obfuscated text display One or two sets of centred labels and entries consecutively added to the end of @a box or placed side-by-side in a table added to @a box @a pw may hold a pointer to a suggested value. @param box the widget into which things will be packed @param confirm TRUE to get 2 matched copies of entered password @param mainprompt custom prompt string, or NULL for default @param pw store for password pointer @return pointer to data struct, NULL upon error */ E2_PWDataRuntime *e2_password_dialog_setup (GtkWidget *box, gboolean confirm, //gboolean tabular, gchar *mainprompt, gchar **pw) { E2_PWDataRuntime *rt = ALLOCATE0 (E2_PWDataRuntime); CHECKALLOCATEDWARN (rt, return NULL;) gchar *realprompt = (mainprompt != NULL) ? mainprompt : _("Enter password:"); gchar *realpw = (*pw != NULL && **pw != '\0') ? *pw : NULL; /* if (tabular) { rt->table = e2_widget_add_table (box, (confirm)? 1:2, 2, FALSE, TRUE, E2_PADDING_SMALL); #ifdef E2_ASSISTED GtkWidget *label = #endif e2_widget_add_mid_label_to_table (rt->table, realprompt, 0.0, 0, 1, 0, 1); rt->pwentry1 = e2_widget_add_entry_to_table (rt->table, realpw, 1, 2, 0, 1); #ifdef E2_ASSISTED e2_widget_set_label_relations (GTK_LABEL (label), rt->pwentry1); #endif } else { rt->table = NULL; */ #ifdef E2_ASSISTED GtkWidget *label = #endif e2_widget_add_label (box, realprompt, 0.5, 0.0, FALSE, E2_PADDING); rt->pwentry1 = e2_widget_add_entry (box, realpw, TRUE, (realpw != NULL)); #ifdef E2_ASSISTED e2_widget_set_label_relations (GTK_LABEL (label), rt->pwentry1); #endif // } rt->plain = plaintext; #ifdef USE_GTK2_10 rt->hint = hinted; #endif rt->hide = hidden; if (!rt->plain) { gtk_entry_set_visibility (GTK_ENTRY (rt->pwentry1), FALSE); if (rt->hide) gtk_entry_set_invisible_char (GTK_ENTRY (rt->pwentry1), 0); #ifdef USE_GTK2_10 //setup to handle password hinting GtkSettings *defs = gtk_settings_get_default (); //log original value guint msec; g_object_get (G_OBJECT (defs), "gtk-entry-password-hint-timeout", &msec, NULL); if (++pwrefcount == 1) hinttime = msec; msec = (rt->hint) ? E2_HINT_MSEC : 0; g_object_set (G_OBJECT (defs), "gtk-entry-password-hint-timeout", msec, NULL); #endif } //handle Return-key presses when the entry is focused // g_signal_connect (G_OBJECT (rt->pwentry1), "activate", // G_CALLBACK (_e2_pwdlg_activated_cb), rt); //handle hint-key or hide-key presses g_signal_connect (G_OBJECT (rt->pwentry1), "key-press-event", G_CALLBACK (_e2_pwdlg_key_press_cb), rt); rt->confirm = confirm; if (confirm) { /* if (tabular) { #ifdef E2_ASSISTED label = #endif e2_widget_add_mid_label_to_table (rt->table, _("Confirm password:"), 0.0, 0, 1, 1, 2); rt->pwentry2 = e2_widget_add_entry_to_table (rt->table, NULL, 1, 2, 1, 2); #ifdef E2_ASSISTED e2_widget_set_label_relations (GTK_LABEL (label), rt->pwentry2); #endif } else { */ #ifdef E2_ASSISTED label = #endif e2_widget_add_label (box, _("Confirm password:"), 0.5, 0.0, FALSE, E2_PADDING); rt->pwentry2 = e2_widget_add_entry (box, NULL, TRUE, FALSE); #ifdef E2_ASSISTED e2_widget_set_label_relations (GTK_LABEL (label), rt->pwentry2); #endif // } if (!rt->plain) { gtk_entry_set_visibility (GTK_ENTRY (rt->pwentry2), FALSE); if (rt->hide) gtk_entry_set_invisible_char (GTK_ENTRY (rt->pwentry2), 0); } //handle Return-key presses when the entry is focused // g_signal_connect (G_OBJECT (rt->pwentry2), "activate", // G_CALLBACK (_e2_pwdlg_activated_cb), rt); //handle hint-key or hide-key presses g_signal_connect (G_OBJECT (rt->pwentry2), "key-press-event", G_CALLBACK (_e2_pwdlg_key_press_cb), rt); } rt->passwd = pw; return rt; } /** @brief check whether entered password(s) are ok to use, and log it if so There is no "editorial" about the merit of the password(s) - anything non-empty will be accepted @param rt pointer to password(s) data @return TRUE if the password(s) are ok */ gboolean e2_password_dialog_confirm (E2_PWDataRuntime *rt) { const gchar *pw1; pw1 = gtk_entry_get_text (GTK_ENTRY (rt->pwentry1)); if (*pw1 == '\0' && !rt->confirm) { gtk_widget_grab_focus (rt->pwentry1); return FALSE; } else //initial pw not empty or is empty but confirm needed if (rt->confirm) { const gchar *pw2; pw2 = gtk_entry_get_text (GTK_ENTRY (rt->pwentry2)); if (*pw1 != '\0' && *pw2 == '\0') { gtk_widget_grab_focus (rt->pwentry2); return FALSE; } else if (*pw2 != '\0' && *pw1 == '\0') { gtk_widget_grab_focus (rt->pwentry1); return FALSE; } else if (!g_str_equal (pw1, pw2)) { e2_dialog_warning (_("Entered passwords are different")); gtk_widget_grab_focus (rt->pwentry1); return FALSE; } else if (*pw1 == '\0') //pw1 and pw2 both empty { gtk_widget_grab_focus (rt->pwentry1); return FALSE; } } if (*rt->passwd != NULL) g_free (*rt->passwd); *rt->passwd = g_strdup (pw1); return TRUE; } /** @brief backup session-static settings @param rt pointer to password data struct @return */ void e2_password_dialog_backup (E2_PWDataRuntime *rt) { plaintext = rt->plain; hidden = rt->hide; #ifdef USE_GTK2_10 if (--pwrefcount == 0) { GtkSettings *defs = gtk_settings_get_default (); g_object_set (G_OBJECT (defs), "gtk-entry-password-hint-timeout", hinttime, NULL); } hinted = rt->hint; #endif } emelfm2-0.4.1/src/dialogs/e2_filetype_dialog.c0000600000175000017500000012350711002355535020121 0ustar cairocairo/* $Id: e2_filetype_dialog.c 854 2008-04-19 11:45:33Z tpgww $ Copyright (C) 2003-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "emelfm2.h" #include #include "e2_dialog.h" #include "e2_task.h" #include "e2_filetype.h" #ifndef OLDFTDLG //no translation of this #define E2_FTDLG_SIG "PANE%d-FTDLG" /*****************************/ /*** edit filetypes dialog ***/ /*****************************/ /* this dialog uses the config treestore for the filetypes option, and also 2 special-purpose treestores and 3 treeviews, respectively for categories, extensions and commands. The latter 2 have no children, but cannot be liststores coz the cut/copy/paste functions work only on treestores. Contents of the latter 2 are moved back and forth from the main config treestore as needed, notably when a different category is selected */ //FIXME doesn't like config dialog filetypes being edited when this is open //FIXME saving to invalid path in config treestore, create new cat & use its path typedef enum { E2_CATPANE, E2_EXTPANE, E2_CMDPANE, E2_PANECOUNT } E2_FileTypeDlg_PaneID; typedef struct _E2_FileTypeDlgRuntime { E2_OptionSet *typeset; E2_FileTypeDlg_PaneID currentID; void *stores[E2_PANECOUNT]; //GtkTreeStores GtkWidget *views[E2_PANECOUNT]; GtkTreePath *catpath; GHashTable *buffer_hash; //for cut|copy data } E2_FileTypeDlgRuntime; static gboolean _e2_edftdlg_cancel_cb (GtkWidget *widget, E2_FileTypeDlgRuntime *rt); static GtkWidget *filetypes_dialog = NULL; /** @brief save current-category data back into the config option treestore Current category is already logged at rt->catpath @param rt pointer to data struct for the dialog @return */ static void _e2_edftdlg_save_current_category (E2_FileTypeDlgRuntime *rt) { GtkTreeModel *cfgmdl = rt->typeset->ex.tree.model; GtkTreeIter cfg_iter; GtkTreePath *path = rt->catpath; if (path == NULL //there was no category || !gtk_tree_model_get_iter (cfgmdl, &cfg_iter, path)) return; GtkTreeModel *mdl; gchar *extension, *cmd_label, *cmd_string; GtkTreeIter iter, cfg_iter2, cfg_iter3; //iters for local liststore & 3 levels of filetypes treestore gint cfgi, newi, cfgchildren, newchildren; //there should never be a case where the extensions and commands child-nodes //are missing, but ... if (gtk_tree_model_iter_children (cfgmdl, &cfg_iter2, &cfg_iter)) { do { // extension or command loop = level 2 cfgchildren = gtk_tree_model_iter_n_children (cfgmdl, &cfg_iter2); gtk_tree_model_get (cfgmdl, &cfg_iter2, 1, &extension, -1); if (g_str_equal (extension, _C(13))) //extensions node found { g_free (extension); mdl = GTK_TREE_MODEL (rt->stores[E2_EXTPANE]); newchildren = gtk_tree_model_iter_n_children (mdl, NULL); //replace as many of old values as possible cfgi = 0; for (newi = 0; newi < newchildren; newi++) { gtk_tree_model_iter_nth_child (mdl, &iter, NULL, newi); gtk_tree_model_get (mdl, &iter, 0, &extension, -1); if (extension != NULL) { if (*extension != '\0') { if (cfgi == cfgchildren) { g_free (extension); break; } gtk_tree_model_iter_nth_child (cfgmdl, &cfg_iter3, &cfg_iter2, cfgi); gtk_tree_store_set (rt->stores[E2_CATPANE], &cfg_iter3, 1, extension, -1); cfgi++; } g_free (extension); } } //if we ran out of old children, need to add more for (;newi < newchildren; newi++) { gtk_tree_model_iter_nth_child (mdl, &iter, NULL, newi); gtk_tree_model_get (mdl, &iter, 0, &extension, -1); if (extension != NULL) { if (*extension != '\0') { #ifdef USE_GTK2_10 gtk_tree_store_insert_with_values ( rt->stores[E2_CATPANE], &cfg_iter3, &cfg_iter2, -1, #else //insert empty row into the config treestore, after other children gtk_tree_store_insert (rt->stores[E2_CATPANE], &cfg_iter3, &cfg_iter2, -1); //populate it gtk_tree_store_set (rt->stores[E2_CATPANE], &cfg_iter3, #endif 1, extension, -1); } g_free (extension); } } } else if (g_str_equal (extension, _C(6))) //commands node found { g_free (extension); mdl = GTK_TREE_MODEL (rt->stores[E2_CMDPANE]); newchildren = gtk_tree_model_iter_n_children (mdl, NULL); //replace as many of old values as possible cfgi = 0; for (newi = 0; newi < newchildren; newi++) { gtk_tree_model_iter_nth_child (mdl, &iter, NULL, newi); gtk_tree_model_get (mdl, &iter, 0, &cmd_label, 1, &cmd_string, -1); if (cmd_string != NULL) { if (*cmd_string != '\0') { if (cfgi == cfgchildren) { if (cmd_label != NULL) g_free (cmd_label); g_free (cmd_string); break; } if (cmd_label == NULL) cmd_label = g_strdup ("?"); //tolerate blank labels else if (*cmd_label == '\0') { g_free (cmd_label); cmd_label = g_strdup ("?"); } gtk_tree_model_iter_nth_child (cfgmdl, &cfg_iter3, &cfg_iter2, cfgi); gtk_tree_store_set (rt->stores[E2_CATPANE], &cfg_iter3, 1, cmd_label, 2, cmd_string, -1); cfgi++; } g_free (cmd_string); } g_free (cmd_label); } //if we ran out of old children, need to add more for (;newi < newchildren; newi++) { gtk_tree_model_iter_nth_child (mdl, &iter, NULL, newi); gtk_tree_model_get (mdl, &iter, 0, &cmd_label, 1, &cmd_string, -1); if (cmd_string != NULL && *cmd_string != '\0') { if (cmd_label == NULL) cmd_label = g_strdup ("?"); //tolerate blank labels else if (*cmd_label == '\0') { g_free (cmd_label); cmd_label = g_strdup ("?"); } #ifdef USE_GTK2_10 gtk_tree_store_insert_with_values ( rt->stores[E2_CATPANE], &cfg_iter3, &cfg_iter2, -1, #else //insert empty row into the config treestore, after other children gtk_tree_store_insert (rt->stores[E2_CATPANE], &cfg_iter3, &cfg_iter2, -1); //populate it gtk_tree_store_set (rt->stores[E2_CATPANE], &cfg_iter3, #endif 1, cmd_label, 2, cmd_string, -1); } if (cmd_label != NULL) g_free (cmd_label); if (cmd_string != NULL) g_free (cmd_string); } } else continue; //OOPS - what else is there ? //delete any redundant old children //(by repeatedly deleting the next child) newi = cfgi; //re-use this variable for (; cfgi < cfgchildren; cfgi++) { gtk_tree_model_iter_nth_child (cfgmdl, &cfg_iter3, &cfg_iter2, newi); gtk_tree_store_remove (rt->stores[E2_CATPANE], &cfg_iter3); } } while (gtk_tree_model_iter_next (cfgmdl, &cfg_iter2)); } } /** @brief (re)populate extensions- and commands-view for the category at @a path in the categories-view @a path is logged, so we can always find the currently-displayed category item Extensions and commands stores are cleared, so anything there that we want to keep must already be saved @param path ptr to treeview path of the category to be used @param rt pointer to data struct for the dialog @return */ static void _e2_edftdlg_update_category (GtkTreePath *path, E2_FileTypeDlgRuntime *rt) { gtk_tree_store_clear (rt->stores[E2_EXTPANE]); gtk_tree_store_clear (rt->stores[E2_CMDPANE]); GtkTreeIter iter; GtkTreeModel *cfgmdl = rt->typeset->ex.tree.model; if (gtk_tree_model_get_iter (cfgmdl, &iter, path)) { GtkTreeIter newiter; //for dialog stores GtkTreeIter iter2, iter3; //for cfg levels gchar *extension, *cmd_label, *cmd_string; //should always be level-2 child nodes, but test anyway ... if (gtk_tree_model_iter_children (cfgmdl, &iter2, &iter)) { do { // extension or command loop = level 2 gtk_tree_model_get (cfgmdl, &iter2, 1, &extension, -1); if (gtk_tree_model_iter_children (cfgmdl, &iter3, &iter2)) { if (g_str_equal (extension, _C(13))) //extensions node found { g_free (extension); do { //extension loop = level 3, fill the extensions store gtk_tree_model_get (cfgmdl, &iter3, 1, &extension, -1); #ifdef USE_GTK2_10 gtk_tree_store_insert_with_values ( rt->stores[E2_EXTPANE], &newiter, NULL, -1, #else //append empty row to the store gtk_tree_store_append (rt->stores[E2_EXTPANE], &newiter, NULL); //populate it gtk_tree_store_set (rt->stores[E2_EXTPANE], &newiter, #endif 0, extension, -1); g_free (extension); } while (gtk_tree_model_iter_next (cfgmdl, &iter3)); } else if (g_str_equal (extension, _C(6))) //commands node found { g_free (extension); do { //commands loop also = level 3, build the commands array gtk_tree_model_get (cfgmdl, &iter3, 1, &cmd_label, 2, &cmd_string, -1); #ifdef USE_GTK2_10 gtk_tree_store_insert_with_values ( rt->stores[E2_CMDPANE], &newiter, NULL, -1, #else //append empty row to the store gtk_tree_store_append (rt->stores[E2_CMDPANE], &newiter, NULL); //populate it gtk_tree_store_set (rt->stores[E2_CMDPANE], &newiter, #endif 0, cmd_label, 1, cmd_string, -1); g_free (cmd_label); g_free (cmd_string); } while (gtk_tree_model_iter_next (cfgmdl, &iter3)); } else { //OOPS printd (WARN, "un-recognised node in filetypes config"); } } else g_free (extension); //no children, just cleanup } while (gtk_tree_model_iter_next (cfgmdl, &iter2)); } } //remember where to find the category now displayed if (rt->catpath != NULL) gtk_tree_path_free (rt->catpath); rt->catpath = gtk_tree_path_copy (path); } /** @brief treeview cell edited callback @param renderer the renderer that was used @param path_string string form of path of edited cell @param new_text the replacement content for the edited cell @param rt pointer to data struct for the dialog @return */ static void _e2_edftdlg_cell_edited_cb (GtkCellRendererText *renderer, gchar *path_string, gchar *new_text, E2_FileTypeDlgRuntime *rt) { if (new_text == NULL) return; //the user did not finish editing a new cell GtkTreeIter iter; gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (rt->stores[rt->currentID]), &iter, path_string); gpointer colptr = g_object_get_data (G_OBJECT (renderer), "col_num"); gint colnum = GPOINTER_TO_INT (colptr); if (colnum == -1) //this is the categories renderer colnum = 0; gtk_tree_store_set (rt->stores[rt->currentID], &iter, colnum, new_text, -1); } /** @brief category treeview selection-changed callback @param treeselection selection object for the clicked treeview widget @param rt pointer to data struct for the dialog @return */ static void _e2_edftdlg_cat_sel_change_cb (GtkTreeSelection *treeselection, E2_FileTypeDlgRuntime *rt) { GtkTreePath *path; gtk_tree_view_get_cursor (GTK_TREE_VIEW (rt->views[E2_CATPANE]), &path, NULL); if (path != NULL) { _e2_edftdlg_save_current_category (rt); _e2_edftdlg_update_category (path, rt); gtk_tree_path_free (path); } } /** @brief determine which pane's store is current, and the first selected iter @param store pointer to store for current-store pointer @param iter pointer to iter to set to the first selected item in store @return TRUE if iter is set */ static gboolean _e2_edftdlg_find_selected (E2_FileTypeDlgRuntime *rt, GtkTreeIter *iter) { GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (rt->views[rt->currentID])); GtkTreeModel *mdl; if (rt->currentID == E2_CATPANE) //categories treeview is single-selection only return (gtk_tree_selection_get_selected (sel, &mdl, iter)); gboolean retval; GList *items = gtk_tree_selection_get_selected_rows (sel, &mdl); if (items != NULL) { retval = gtk_tree_model_get_iter (mdl, iter, (GtkTreePath *)items->data); g_list_foreach (items, (GFunc) gtk_tree_path_free, NULL); g_list_free (items); } else retval = FALSE; return retval; } /** @brief process dialog pane change This is a "focus-in-event" callback, applied to each treeview @param treeview the newly-focused treeview widget @param event ptr to event data struct @param rt pointer to data struct for the dialog @return FALSE always - allow other handlers */ static gboolean _e2_edftdlg_focus_in_cb (GtkWidget *treeview, GdkEventFocus *event, E2_FileTypeDlgRuntime *rt) { if (treeview == rt->views[E2_CATPANE]) rt->currentID = E2_CATPANE; else if (treeview == rt->views[E2_EXTPANE]) rt->currentID = E2_EXTPANE; else rt->currentID = E2_CMDPANE; GtkTreeIter iter; if (!_e2_edftdlg_find_selected (rt, &iter)) { GtkTreePath*path = gtk_tree_path_new_first (); //NOTE this is supposed to, but doesn't, make the cell editable //without a mouse-click ! gtk_tree_view_set_cursor (GTK_TREE_VIEW (treeview), path, NULL, TRUE); gtk_tree_path_free (path); } return FALSE; } /** @brief insert a row into store and view @param rt pointer to data struct for the dialog @return */ static void _e2_edftdlg_insert_row (E2_FileTypeDlgRuntime *rt) { GtkTreeIter iter, newiter; if (_e2_edftdlg_find_selected (rt, &iter)) gtk_tree_store_insert_after (rt->stores[rt->currentID], &newiter, NULL, &iter); else gtk_tree_store_append (rt->stores[rt->currentID], &newiter, NULL); GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (rt->views[rt->currentID])); if (rt->currentID == E2_CATPANE) { //provide a token category name gtk_tree_store_set (rt->stores[E2_CATPANE], &newiter, 0, _("new"), -1); //create empty framework for the new category #ifdef USE_GTK2_10 gtk_tree_store_insert_with_values (rt->stores[E2_CATPANE], &iter, &newiter, -1, 1, _C(13), -1); gtk_tree_store_insert_with_values (rt->stores[E2_CATPANE], &iter, &newiter, -1, 1, _C(6), -1); #else //add extensions node gtk_tree_store_append (rt->stores[E2_CATPANE], &iter, &newiter); gtk_tree_store_set (rt->stores[E2_CATPANE], &iter, 1, _C(13), -1); //add commands node gtk_tree_store_append (rt->stores[E2_CATPANE], &iter, &newiter); gtk_tree_store_set (rt->stores[E2_CATPANE], &iter, 1, _C(6), -1); #endif GtkTreePath *path = gtk_tree_model_get_path (rt->typeset->ex.tree.model, &newiter); gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (rt->views[E2_CATPANE]), path, NULL, FALSE, 0, 0); gtk_tree_selection_select_path (sel, path); _e2_edftdlg_save_current_category (rt); _e2_edftdlg_update_category (path, rt); //update rt->catpath & clear the views gtk_tree_path_free (path); } else //not a new category { // gtk_list_store_set (rt->stores[[rt->currentID]], &newiter, 0, _("new"), -1); gtk_tree_selection_unselect_all (sel); gtk_tree_selection_select_iter (sel, &newiter); } } /** @brief dialog response callback @param dialog the dialog where the response was generated @param response the response assigned to @a button @param rt pointer to data struct for the dialog @return */ static void _e2_edftdlg_response_cb (GtkDialog *dialog, gint response, E2_FileTypeDlgRuntime *rt) { GtkTreeIter iter; switch (response) { case GTK_RESPONSE_APPLY: _e2_edftdlg_save_current_category (rt); //this might be dirty e2_option_tree_unbackup (rt->typeset, FALSE); //get rid of old backup data e2_filetype_apply_allnew (); //convert config data to runtime format e2_option_tree_backup (rt->typeset); //get fresh backup data break; case GTK_RESPONSE_OK: _e2_edftdlg_save_current_category (rt); //this might be dirty e2_option_tree_unbackup (rt->typeset, FALSE); //get rid of old backup data g_hash_table_destroy (rt->buffer_hash); g_free (rt); gtk_widget_destroy (filetypes_dialog); filetypes_dialog = NULL; e2_filetype_apply_allnew (); gtk_widget_grab_focus (curr_view->treeview); break; case E2_RESPONSE_USER1: //move-up button pressed if (_e2_edftdlg_find_selected (rt, &iter)) { GtkTreePath *path = gtk_tree_model_get_path (GTK_TREE_MODEL (rt->stores[rt->currentID]), &iter); if (gtk_tree_path_prev (path)) { GtkTreeIter previter; gtk_tree_model_get_iter (GTK_TREE_MODEL (rt->stores[rt->currentID]), &previter, path); gtk_tree_store_swap (rt->stores[rt->currentID], &iter, &previter); } gtk_tree_path_free (path); } break; case E2_RESPONSE_USER2: //move-down button pressed if (_e2_edftdlg_find_selected (rt, &iter)) { GtkTreeIter nextiter = iter; if (gtk_tree_model_iter_next (GTK_TREE_MODEL (rt->stores[rt->currentID]), &nextiter)) gtk_tree_store_swap (rt->stores[rt->currentID], &iter, &nextiter); } break; case E2_RESPONSE_USER3: //insert-after button pressed _e2_edftdlg_insert_row (rt); break; case E2_RESPONSE_REMOVE: if (rt->currentID == E2_CATPANE) { if (_e2_edftdlg_find_selected (rt, &iter)) { GtkTreePath *newpath = gtk_tree_model_get_path (GTK_TREE_MODEL (rt->stores[E2_CATPANE]), &iter); GtkTreeIter newiter = iter; //this is where we goto after the removal //(if there's a next iter, when the current is deleted, next becomes current) gboolean allgone = ! (gtk_tree_model_iter_next (rt->typeset->ex.tree.model, &newiter) || gtk_tree_path_prev (newpath)); //this deletes all children too gtk_tree_store_remove (rt->stores[E2_CATPANE], &iter); if (allgone) { //nowhere else to show if (rt->catpath != NULL) gtk_tree_path_free (rt->catpath); rt->catpath = NULL; //clear the related views gtk_tree_store_clear (rt->stores[E2_EXTPANE]); gtk_tree_store_clear (rt->stores[E2_CMDPANE]); } else { gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (rt->views[E2_CATPANE]), newpath, NULL, FALSE, 0, 0); GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (rt->views[E2_CATPANE])); gtk_tree_selection_select_path (sel, newpath); _e2_edftdlg_update_category (newpath, rt); } gtk_tree_path_free (newpath); } } else e2_tree_delete (GTK_TREE_VIEW (rt->views[rt->currentID])); break; default: _e2_edftdlg_cancel_cb (NULL, rt); break; } return; } /** series of context-menu callback functions @param item the selected menu item widget @param rt pointer to dialog data struct @return */ static void _e2_edftdlg_menu_copy_cb (GtkWidget *item, E2_FileTypeDlgRuntime *rt) { GtkTreeView *treeview = GTK_TREE_VIEW (rt->views[rt->currentID]); GtkTreeSelection *sel = gtk_tree_view_get_selection (treeview); if (gtk_tree_selection_count_selected_rows (sel) > 0) { GList *copied = e2_tree_copy (treeview); gchar *id_string = g_strdup_printf (E2_FTDLG_SIG, rt->currentID); printd (DEBUG, "adding to buffer: key %s and %d rows", id_string, g_list_length (copied)); g_hash_table_replace (rt->buffer_hash, id_string, copied); } } static void _e2_edftdlg_menu_cut_cb (GtkWidget *item, E2_FileTypeDlgRuntime *rt) { GtkTreeView *treeview = GTK_TREE_VIEW (rt->views[rt->currentID]); GtkTreeSelection *sel = gtk_tree_view_get_selection (treeview); if (gtk_tree_selection_count_selected_rows (sel) > 0) { _e2_edftdlg_menu_copy_cb (item, rt); e2_tree_delete (treeview); } } static void _e2_edftdlg_menu_paste_cb (GtkWidget *item, E2_FileTypeDlgRuntime *rt) { //as a special case, if the store is empty, setup to add the data gboolean empty = (gtk_tree_model_iter_n_children (GTK_TREE_MODEL (rt->stores[rt->currentID]), NULL) == 0); if (empty) _e2_edftdlg_insert_row (rt); GtkTreeView *treeview = GTK_TREE_VIEW (rt->views[rt->currentID]); //we paste after the 1st selected item GtkTreeSelection *sel = gtk_tree_view_get_selection (treeview); if (gtk_tree_selection_count_selected_rows (sel) > 0) { gchar *id_string = g_strdup_printf (E2_FTDLG_SIG, rt->currentID); //get the buffered data, if any, for the current page GList *copied = g_hash_table_lookup (rt->buffer_hash, id_string); if (copied != NULL) e2_tree_paste (copied, treeview); g_free (id_string); } if (empty) e2_tree_delete (treeview); } static void _e2_edftdlg_menu_add_below_cb (GtkWidget *item, E2_FileTypeDlgRuntime *rt) { _e2_edftdlg_insert_row (rt); } static void _e2_edftdlg_menu_move_up_cb (GtkWidget *item, E2_FileTypeDlgRuntime *rt) { _e2_edftdlg_response_cb (GTK_DIALOG (filetypes_dialog), E2_RESPONSE_USER1, rt); } static void _e2_edftdlg_menu_move_down_cb (GtkWidget *item, E2_FileTypeDlgRuntime *rt) { _e2_edftdlg_response_cb (GTK_DIALOG (filetypes_dialog), E2_RESPONSE_USER2, rt); } /** @brief set popup menu position This function is supplied when calling gtk_menu_popup(), to position the displayed menu. set @a push_in to TRUE for menu completely inside the screen, FALSE for menu clamped to screen size @param menu UNUSED the GtkMenu to be positioned @param x place to store gint representing the menu left @param y place to store gint representing the menu top @param push_in place to store pushin flag @param treeview the focused widget when the button was pressed @return */ static void _e2_edftdlg_set_menu_position (GtkMenu *menu, gint *x, gint *y, gboolean *push_in, GtkWidget *treeview) { gint left, top; gtk_window_get_position (GTK_WINDOW (filetypes_dialog), &left, &top); GtkAllocation alloc = treeview->allocation; *x = left+ alloc.x + alloc.width/2; *y = top + alloc.y +alloc.height/2 - 30; *push_in = FALSE; } /** @brief create treeview context menu @param treeview widget where the action is to occur @param button the clicked mouse button, or 0 for menu key @param time event time @param rt pointer to dialog data struct @return */ static void _e2_edftdlg_menu_create (GtkWidget *treeview, guint button, guint32 time, E2_FileTypeDlgRuntime *rt) { GtkWidget *menu = gtk_menu_new (); e2_menu_add (menu, _("Cu_t"), GTK_STOCK_CUT, _("Cut selected row(s)"), _e2_edftdlg_menu_cut_cb, rt); e2_menu_add (menu, _("_Copy"), GTK_STOCK_COPY, _("Copy selected row(s)"), _e2_edftdlg_menu_copy_cb, rt); //check for valid stored content gchar *id_string = g_strdup_printf (E2_FTDLG_SIG, rt->currentID); gboolean state = (g_hash_table_lookup (rt->buffer_hash, id_string) != NULL); g_free (id_string); GtkWidget *item = e2_menu_add (menu, _("_Paste"), GTK_STOCK_PASTE, _("Paste previously copied or cut row(s) after current row"), _e2_edftdlg_menu_paste_cb, rt); gtk_widget_set_sensitive (item, state); e2_menu_add_separator (menu); e2_menu_add (menu, _("_Add"), GTK_STOCK_ADD, _("Add a row after the current one"), _e2_edftdlg_menu_add_below_cb, rt); e2_menu_add_separator (menu); e2_menu_add (menu, _("_Up"), GTK_STOCK_GO_UP, _("Move selected row up"), _e2_edftdlg_menu_move_up_cb, rt); e2_menu_add (menu, _("_Down"), GTK_STOCK_GO_DOWN, _("Move selected row down"), _e2_edftdlg_menu_move_down_cb, rt); if (button == 0) //this was a menu-key press gtk_menu_popup (GTK_MENU (menu), NULL, NULL, (GtkMenuPositionFunc) _e2_edftdlg_set_menu_position, treeview, 0, time); else //this was a button-3 click gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, button, time); } /** @brief treeview menu-button press callback @param treeview the focused widget when the menu button was pressed @param rt pointer to data struct for the dialog @return TRUE */ static gboolean _e2_edftdlg_popup_menu_cb (GtkWidget *treeview, E2_FileTypeDlgRuntime *rt) { guint32 event_time = gtk_get_current_event_time (); _e2_edftdlg_menu_create (treeview, 0, event_time, rt); return TRUE; } /** @brief treeview button press callback @param treeview the clicked treeview widget @param event ptr to event data struct @param rt pointer to data struct for the dialog @return FALSE */ //FIXME find a way to cancel editing of a renderer that is clicked again static gboolean _e2_edftdlg_button_press_cb (GtkWidget *treeview, GdkEventButton *event, E2_FileTypeDlgRuntime *rt) { if (event->button == 1 && event->type == GDK_BUTTON_PRESS) { GtkTreePath *path; if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (treeview), event->x, event->y, &path, NULL, NULL, NULL)) { if (treeview == rt->views[E2_CATPANE]) { _e2_edftdlg_save_current_category (rt); _e2_edftdlg_update_category (path, rt); } gtk_tree_path_free (path); } //clicked a bare part of the view if (treeview == rt->views[E2_CATPANE]) rt->currentID = E2_CATPANE; else if (treeview == rt->views[E2_EXTPANE]) rt->currentID = E2_EXTPANE; else rt->currentID = E2_CMDPANE; } else if (event->button == 3) { _e2_edftdlg_menu_create (treeview, 3, event->time, rt); return TRUE; } return FALSE; } /** @brief abandon the dialog without making any change @param widget the widget which was activated to trigger the cancel @param rt pointer to data struct for the dialog @return TRUE always - prevent other handlers */ static gboolean _e2_edftdlg_cancel_cb (GtkWidget *widget, E2_FileTypeDlgRuntime *rt) { gtk_widget_destroy (filetypes_dialog); filetypes_dialog = NULL; e2_option_tree_unbackup (rt->typeset, TRUE); //undo everything g_free (rt); gtk_widget_grab_focus (curr_view->treeview); return TRUE; } /** @brief if Escape key is pressed, abandon the dialog without making any change @param widget the widget which was activated to trigger the cancel @parm event pointer to event data struct @param rt pointer to data struct for the dialog @return TRUE if Esc key processed, else FALSE */ static gboolean _e2_edftdlg_key_press_cb (GtkWidget *widget, GdkEventKey *event, E2_FileTypeDlgRuntime *rt) { if (event->keyval == GDK_Escape) //restore tree data and clean up return (_e2_edftdlg_cancel_cb (NULL, rt)); return FALSE; } /** @brief create and show edit filetypes dialog @param open_category name of category to focus when dialog opens, or NULL @return */ void e2_filetype_dialog_edit_create (gchar *open_category) { //check if there is already one of these dialogs opened if (filetypes_dialog != NULL) { gtk_window_present (GTK_WINDOW (filetypes_dialog)); return; } E2_OptionSet *set = e2_option_get_simple ("filetypes"); if (set == NULL) return; //can't do anything E2_FileTypeDlgRuntime *rt = ALLOCATE (E2_FileTypeDlgRuntime); CHECKALLOCATEDWARN (rt, return); rt->typeset = set; rt->catpath = NULL; //receptacle for cut/copied row(s) data generated from context menu rt->buffer_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) e2_option_tree_menu_hash_clean); filetypes_dialog = e2_dialog_create (NULL, NULL, _("edit filetypes"), _e2_edftdlg_response_cb, rt); e2_option_connect (filetypes_dialog, FALSE); //main scrolled window GtkWidget *sw = e2_widget_get_sw (GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, GTK_SHADOW_IN); // gtk_scrolled_window_set_placement (GTK_SCROLLED_WINDOW (sw), // e2_option_int_get ("scrollbar-position")); gtk_container_add (GTK_CONTAINER (GTK_DIALOG (filetypes_dialog)->vbox), sw); gtk_widget_set_size_request (sw, 0, 400); gtk_widget_show (sw); GtkWidget *hbox = gtk_hbox_new (FALSE, 0); gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), hbox); gtk_viewport_set_shadow_type (GTK_VIEWPORT (GTK_BIN (sw)->child), GTK_SHADOW_NONE); gtk_widget_show (hbox); //categories scrolled window GtkWidget *cat_sw = e2_widget_get_sw (GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, GTK_SHADOW_IN); gtk_box_pack_start (GTK_BOX (hbox), cat_sw, TRUE, TRUE, 0); //categories store and treeview rt->stores[E2_CATPANE] = GTK_TREE_STORE (set->ex.tree.model); rt->views[E2_CATPANE] = gtk_tree_view_new_with_model (set->ex.tree.model); gtk_tree_view_set_reorderable (GTK_TREE_VIEW (rt->views[E2_CATPANE]), TRUE); #ifdef USE_GTK2_10 gtk_tree_view_set_enable_tree_lines (GTK_TREE_VIEW (rt->views[E2_CATPANE]), TRUE); #endif g_signal_connect (rt->views[E2_CATPANE], "popup-menu", G_CALLBACK (_e2_edftdlg_popup_menu_cb), rt); GtkCellRenderer *renderer = gtk_cell_renderer_text_new (); g_object_set (G_OBJECT (renderer), "editable", TRUE, NULL); g_object_set_data (G_OBJECT (renderer), "col_num", GINT_TO_POINTER (-1)); //flag for categories renderer g_signal_connect (G_OBJECT (renderer), "edited", G_CALLBACK (_e2_edftdlg_cell_edited_cb), rt); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (rt->views[E2_CATPANE]), -1, _("Categories"), renderer, "text", 0, NULL); //hide the tree expanders #ifdef USE_GTK2_12 gtk_tree_view_set_show_expanders (GTK_TREE_VIEW (rt->views[E2_CATPANE]), FALSE); #else GtkTreeViewColumn *expander_col = gtk_tree_view_column_new (); gtk_tree_view_insert_column (GTK_TREE_VIEW (rt->views[E2_CATPANE]), expander_col, 1); gtk_tree_view_set_expander_column (GTK_TREE_VIEW (rt->views[E2_CATPANE]), expander_col); gtk_tree_view_column_set_visible (expander_col, FALSE); #endif gtk_container_add (GTK_CONTAINER (cat_sw), rt->views[E2_CATPANE]); gtk_widget_set_size_request (cat_sw, 150, 0); gtk_widget_show_all (cat_sw); //extensions scrolled window GtkWidget *ext_sw = e2_widget_get_sw (GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, GTK_SHADOW_IN); gtk_box_pack_start (GTK_BOX (hbox), ext_sw, TRUE, TRUE, 0); //extensions liststore and treeview //store has 1 column, for displayed extensions rt->stores[E2_EXTPANE] = gtk_tree_store_new (1, G_TYPE_STRING); rt->views[E2_EXTPANE] = gtk_tree_view_new_with_model (GTK_TREE_MODEL (rt->stores[E2_EXTPANE])); g_object_unref (rt->stores[E2_EXTPANE]); //destroy store with view gtk_tree_view_set_reorderable (GTK_TREE_VIEW (rt->views[E2_EXTPANE]), TRUE); g_signal_connect (rt->views[E2_EXTPANE], "popup-menu", G_CALLBACK (_e2_edftdlg_popup_menu_cb), rt); renderer = gtk_cell_renderer_text_new (); g_object_set (G_OBJECT (renderer), "editable", TRUE, NULL); g_object_set_data (G_OBJECT (renderer), "col_num", GINT_TO_POINTER (0)); g_signal_connect (G_OBJECT (renderer), "edited", G_CALLBACK (_e2_edftdlg_cell_edited_cb), rt); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (rt->views[E2_EXTPANE]), -1, _("Extensions"), renderer, "text", 0, NULL); gtk_container_add (GTK_CONTAINER (ext_sw), rt->views[E2_EXTPANE]); gtk_widget_set_size_request (ext_sw, 100, 0); gtk_widget_show_all (ext_sw); GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (rt->views[E2_EXTPANE])); gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE); //commands scrolled window GtkWidget *cmd_sw = e2_widget_get_sw (GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, GTK_SHADOW_IN); gtk_box_pack_start (GTK_BOX (hbox), cmd_sw, TRUE, TRUE, 0); //commands liststore and treeview //store has columns for displayed label, command rt->stores[E2_CMDPANE] = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_STRING); rt->views[E2_CMDPANE] = gtk_tree_view_new_with_model (GTK_TREE_MODEL (rt->stores[E2_CMDPANE])); g_object_unref (rt->stores[E2_CMDPANE]); //destroy store with view gtk_tree_view_set_reorderable (GTK_TREE_VIEW (rt->views[E2_CMDPANE]), TRUE); g_signal_connect (rt->views[E2_CMDPANE], "popup-menu", G_CALLBACK (_e2_edftdlg_popup_menu_cb), rt); renderer = gtk_cell_renderer_text_new (); g_object_set (G_OBJECT (renderer), "editable", TRUE, NULL); g_object_set_data (G_OBJECT (renderer), "col_num", GINT_TO_POINTER (0)); g_signal_connect (G_OBJECT (renderer), "edited", G_CALLBACK (_e2_edftdlg_cell_edited_cb), rt); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (rt->views[E2_CMDPANE]), 0, _("Labels"), renderer, "text", 0, NULL); GtkTreeViewColumn *column = gtk_tree_view_get_column (GTK_TREE_VIEW (rt->views[E2_CMDPANE]), 0); g_object_set (G_OBJECT (column), "resizable", TRUE, "min-width", 120, NULL); renderer = gtk_cell_renderer_text_new (); g_object_set (G_OBJECT (renderer), "editable", TRUE, NULL); g_object_set_data (G_OBJECT (renderer), "col_num", GINT_TO_POINTER (1)); g_signal_connect (G_OBJECT (renderer), "edited", G_CALLBACK (_e2_edftdlg_cell_edited_cb), rt); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (rt->views[E2_CMDPANE]), 1, _("Commands"), renderer, "text", 1, NULL); column = gtk_tree_view_get_column (GTK_TREE_VIEW (rt->views[E2_CMDPANE]), 1); g_object_set (G_OBJECT (column), "resizable", TRUE, NULL); gtk_container_add (GTK_CONTAINER (cmd_sw), rt->views[E2_CMDPANE]); gtk_widget_set_size_request (cmd_sw, 400, 0); gtk_widget_show_all (cmd_sw); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (rt->views[E2_CMDPANE])); gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE); e2_option_tree_backup (set); //keep a backup in case user cancels //fill the stores GtkTreePath *path; GtkTreeIter iter; if (open_category != NULL && e2_tree_find_iter_from_str (set->ex.tree.model, 0, open_category, &iter, FALSE)) { path = gtk_tree_model_get_path (set->ex.tree.model, &iter); gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (rt->views[E2_CATPANE]), path, NULL, FALSE, 0, 0); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (rt->views[E2_CATPANE])); gtk_tree_selection_select_path (selection, path); _e2_edftdlg_update_category (path, rt); if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (rt->stores[E2_CMDPANE]), &iter)) { selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (rt->views[E2_CMDPANE])); gtk_tree_selection_select_iter (selection, &iter); } gtk_widget_grab_focus (rt->views[E2_CMDPANE]); } else if (gtk_tree_model_get_iter_first (set->ex.tree.model, &iter)) { path = gtk_tree_model_get_path (set->ex.tree.model, &iter); _e2_edftdlg_update_category (path, rt); } //handle things g_signal_connect (G_OBJECT (filetypes_dialog), "key-press-event", G_CALLBACK (_e2_edftdlg_key_press_cb), rt); // g_signal_connect (G_OBJECT (filetypes_dialog), "delete-event", // G_CALLBACK (_e2_edftdlg_cancel_cb), rt); g_signal_connect (G_OBJECT (rt->views[E2_CATPANE]), "button-press-event", G_CALLBACK (_e2_edftdlg_button_press_cb), rt); g_signal_connect (G_OBJECT (rt->views[E2_EXTPANE]), "button-press-event", G_CALLBACK (_e2_edftdlg_button_press_cb), rt); g_signal_connect (G_OBJECT (rt->views[E2_CMDPANE]), "button-press-event", G_CALLBACK (_e2_edftdlg_button_press_cb), rt); // g_signal_connect (G_OBJECT (rt->views[E2_CATPANE]), "cursor-changed", // G_CALLBACK (_e2_edftdlg_cat_cursor_move_cb), rt); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (rt->views[E2_CATPANE])); g_signal_connect (G_OBJECT (selection), "changed", G_CALLBACK (_e2_edftdlg_cat_sel_change_cb), rt); //traps for pane-changes by tab key or mouse click g_signal_connect (G_OBJECT (rt->views[E2_CATPANE]), "focus-in-event", G_CALLBACK (_e2_edftdlg_focus_in_cb), rt); g_signal_connect (G_OBJECT (rt->views[E2_EXTPANE]), "focus-in-event", G_CALLBACK (_e2_edftdlg_focus_in_cb), rt); g_signal_connect (G_OBJECT (rt->views[E2_CMDPANE]), "focus-in-event", G_CALLBACK (_e2_edftdlg_focus_in_cb), rt); //add buttons, custom and standard E2_Button buttontype; buttontype.label = _("_Up"); buttontype.stock = GTK_STOCK_GO_UP; buttontype.response = E2_RESPONSE_USER1; e2_dialog_add_button_custom (filetypes_dialog, FALSE, &buttontype, _("Move the selected row one place up"), NULL, NULL); buttontype.label = _("_Down"); buttontype.stock = GTK_STOCK_GO_DOWN; buttontype.response = E2_RESPONSE_USER2; e2_dialog_add_button_custom (filetypes_dialog, FALSE, &buttontype, _("Move the selected row one place down"), NULL, NULL); buttontype.label = _("Add a_fter"); buttontype.stock = GTK_STOCK_ADD; buttontype.response = E2_RESPONSE_USER3; e2_dialog_add_button_custom (filetypes_dialog, FALSE, &buttontype, _("Add a row after the currently selected one"), NULL, NULL); E2_BUTTON_CANCEL.showflags |= E2_BTN_DEFAULT; E2_BUTTON_OK.showflags &= ~E2_BTN_DEFAULT; e2_dialog_show (filetypes_dialog, app.main_window, 0, &E2_BUTTON_REMOVE, &E2_BUTTON_CANCEL, &E2_BUTTON_APPLY, &E2_BUTTON_OK, NULL); } #endif //ndef OLDFTDLG /***************************/ /**** what-to-do dialog ****/ /***************************/ typedef struct _E2_FileTypeDlg2Runtime { GtkWidget *view_btn; GtkWidget *open_btn; gchar *utfpath; //utf8 absolute path of item gint processed; //store for return-code } E2_FileTypeDlg2Runtime; /** @brief unknown filetype dialog response-signal callback @param dialog the dialog where the response was triggered @param response the number assigned the activated widget @param view rt data for the dialog @return */ static void _e2_filetype_dialog_response_cb (GtkDialog *dialog, gint response, E2_FileTypeDlg2Runtime *rt) { printd (DEBUG, "response_cb (dialog:_,response:%d,rt:_)", response); gtk_widget_destroy (GTK_WIDGET (dialog)); gchar *action; //remember this when rt is cleared at end of dialog func, and handle any whitespace gchar *item = g_strconcat ("\"", rt->utfpath, "\"", NULL); //send back "handled" type flag to caller switch (response) { case E2_RESPONSE_USER1: //edit case E2_RESPONSE_USER2: //view case E2_RESPONSE_USER3: //open with case 124: //exec rt->processed = E2_TYPE_HANDLED; break; case GTK_RESPONSE_ACCEPT: //add case 125: //open with default (back in the calling code) rt->processed = E2_TYPE_NOTHANDLED; break; default: //this includes GTK_RESPONSE_DELETE_EVENT rt->processed = E2_TYPE_CANCELLED; break; } gtk_main_quit (); //allow the dialog creation function to return switch (response) { /* these callbacks handle a specific filename, so are usable for output pane selections as well as filelist selections item is a quouted absolute utf-8 path string */ case GTK_RESPONSE_ACCEPT: //add //open the filetypes config dialog with no extension selected e2_filetype_config_show (NULL); break; case E2_RESPONSE_USER1: //edit action = g_strconcat (_A(5),".",_A(39), NULL); gdk_threads_leave (); //open BGL e2_action_run_simple_from (action, item, NULL); gdk_threads_enter (); g_free (action); break; case E2_RESPONSE_USER2: //view action = g_strconcat (_A(5),".",_A(101), NULL); gdk_threads_leave (); //open BGL e2_action_run_simple_from (action, item, NULL); gdk_threads_enter (); g_free (action); break; case E2_RESPONSE_USER3: //open with action = g_strconcat (_A(5),".",_A(62), NULL); gdk_threads_leave (); //open BGL e2_action_run_simple_from (action, item, NULL); gdk_threads_enter (); g_free (action); break; case 124: //execute #ifdef E2_COMMANDQ e2_command_run (item, E2_COMMAND_RANGE_DEFAULT, FALSE); #else e2_command_run (item, E2_COMMAND_RANGE_DEFAULT); #endif break; default: break; } gtk_widget_grab_focus (curr_view->treeview); g_free (item); } /** @brief create and show dialog for handling unknown or ambiguous filetype @a newtype should be TRUE when @a filename does have an extension @a newtype and @a ambig should not both be TRUE @param localpath absolute or relative path of item to be processed, localised string @param text TRUE to include view or edit option in the dialog @param ambig TRUE to enable choice to execute or open item @param newtype TRUE to include a button to add a new filetype @return enumerator signalling how @a filename was "actioned" via the dialog */ gint e2_filetype_dialog_create (VPATH *localpath, gboolean text, gboolean ambig, gboolean newtype) { gint retval; gboolean addpath; gchar *usepath, *dir, *base, *title, *public, *message; struct stat sb; E2_FileTypeDlg2Runtime *rt = ALLOCATE (E2_FileTypeDlg2Runtime); CHECKALLOCATEDWARN (rt, return); usepath = VPSTR (localpath); // printd (DEBUG, "e2_filetype_dialog_create: %s", usepath); addpath = !g_path_is_absolute (usepath); if (addpath) { //FIXME race (unlikely) if dir goes and cd happens, worse if past virtual root #ifdef E2_VFS if (localpath->spacedata == other_view->spacedata) usepath = e2_utils_dircat (other_view, usepath, TRUE); else #endif usepath = e2_utils_dircat (curr_view, usepath, TRUE); } rt->utfpath = D_FILENAME_FROM_LOCALE (usepath); title = (ambig) ? _("ambiguous filetype") : _("unrecognised filetype"); public = g_markup_escape_text (rt->utfpath, -1); dir = g_path_get_dirname (public); base = g_path_get_basename (public); message = g_strdup_printf ( _("What would you like to do with\n%s\nin %s ?"), base, dir); GtkWidget *dialog = e2_dialog_create (GTK_STOCK_DIALOG_QUESTION, message, title, _e2_filetype_dialog_response_cb, rt); g_free (public); g_free (dir); g_free (base); g_free (message); //can always add to category, even if item does not presently exist if (newtype && !ambig) e2_dialog_add_undefined_button_custom (dialog, FALSE, GTK_RESPONSE_ACCEPT, _("_Add.."), "gtk-preferences", _("Create a new filetype for this extension, or add it to an existing filetype"), NULL, NULL); #ifdef E2_VFS VPATH ddata = { usepath, localpath->spacedata }; if (!e2_fs_stat (&ddata, &sb E2_ERR_NONE())) #else if (!e2_fs_stat (usepath, &sb E2_ERR_NONE())) #endif { if (S_ISREG (sb.st_mode)) { if (sb.st_size == 0) rt->view_btn = e2_dialog_add_undefined_button_custom (dialog, FALSE, E2_RESPONSE_USER1, _("_Edit"), "edit_"E2IP".png", _("Edit the file"), NULL, NULL); else if (text) rt->view_btn = e2_dialog_add_undefined_button_custom (dialog, FALSE, E2_RESPONSE_USER2, _("_View"), "view_"E2IP".png", _("Read the file"), NULL, NULL); } if (ambig) { e2_dialog_add_undefined_button_custom (dialog, FALSE, 124, _("_Run"), GTK_STOCK_EXECUTE, _("Execute the item"), NULL, NULL); rt->open_btn = e2_dialog_add_undefined_button_custom (dialog, TRUE, 125, _("_Open"), GTK_STOCK_OPEN, //FIXME better icon _("Open with the default application"), NULL, NULL); } else rt->open_btn = e2_dialog_add_undefined_button_custom (dialog, TRUE, E2_RESPONSE_USER3, _("_Open.."), "open_with_"E2IP".png", _("Enter a command with which to open the file"), NULL, NULL); gtk_widget_grab_focus (rt->open_btn); } e2_dialog_show (dialog, NULL, 0, &E2_BUTTON_CANCEL, NULL); gtk_main (); //block until user selects something if (addpath) g_free (usepath); retval = rt->processed; //get code for what was selected DEALLOCATE (E2_FileTypeDlg2Runtime, rt); return retval; } emelfm2-0.4.1/src/dialogs/e2_mkdir_dialog.c0000600000175000017500000007707211014513365017413 0ustar cairocairo/* $Id: e2_mkdir_dialog.c 892 2008-05-20 09:16:37Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "emelfm2.h" //#include #include #include "e2_dialog.h" #include "e2_mkdir_dialog.h" #include "e2_task.h" #include "e2_filelist.h" //option-pointers shared by all mkdir dialogs in the session //FIXME make sure these are NULL'd if optionsets are ever renewed static E2_OptionSet *follow_pane = NULL; static E2_OptionSet *suggest_dir = NULL; static E2_OptionSet *show_last = NULL; static E2_OptionSet *connected = NULL; static GList *mkdir_history = NULL; static gboolean _e2_mkdirdlg_change_dir_hook (gchar *path, E2_MkdirDialogRuntime *rt); static gboolean _e2_mkdirdlg_change_focus_hook (E2_PaneRuntime *pane_rt, E2_MkdirDialogRuntime *rt); static gboolean _e2_task_mkdirQ (E2_ActionTaskData *qed); /*****************/ /***** utils *****/ /*****************/ /** @brief determine a suggested directory name, by appending or incrementing a number @param dir last combo box entry, ie utf8, maybe NULL if no entry @param parent_dir is rt->path, utf8 @return newly-allocated utf8 string */ static gchar *_e2_mkdirdlg_find_dir (const gchar *dir, const gchar *parent_dir) { //we have to suggest something, but if there is nothing //in the history yet, we use a default string if (dir == NULL) dir = _("new directory"); #ifdef E2_VFSTMP //FIXME path for non-mounted dirs #endif //the absolute directory name consists of 4 parts: //parent directory if the combobox string is not an absolute path gchar *part1 = (g_path_is_absolute (dir)) ? "" : (gchar *)parent_dir; //part of the combobox string that's a relative path like "bar/" from "bar/foo" gchar *part2 = g_path_get_dirname (dir); if (g_str_equal (part2, ".")) { //no path entered g_free (part2); part2 = g_strdup (""); } //#warning ignore compiler warning about unitialized usage of digit_end //#warning ignore compiler warning about unitialized usage of part4 //the base name of the dir gchar *part3 = g_path_get_basename (dir); //last part to hold the rest of part 3 after we find a number, if any //ignore warning about uninitialized usage gchar *part4 = NULL; //assignment for complier-warning prevention only //index (chars, not bytes) of the start of a number in part3 glong digit_start = -1; glong digit_end = 0; //assignment for complier-warning prevention only glong digit_len = 1; //digit to increment or add to the directory name guint i = 0; //complete path (all utf8) gchar *path = g_build_filename (part1, part2, part3, NULL); gchar *local = F_FILENAME_TO_LOCALE (path); #ifdef E2_VFS VPATH ddata = { local, curr_view->spacedata }; if (!e2_fs_access (&ddata, F_OK E2_ERR_NONE())) //through links #else if (!e2_fs_access (local, F_OK E2_ERR_NONE())) //through links #endif { //the directory in the combo entry already exists, //check (backwards through the name) whether the name includes a number glong j = g_utf8_strlen (part3, -1) - 1; gboolean last_was_digit = FALSE; gchar *p; gunichar c; while (j >= 0) { p = g_utf8_offset_to_pointer (part3, j); c = g_utf8_get_char (p); if (g_unichar_isdigit (c)) { if (!last_was_digit) { last_was_digit = TRUE; digit_end = j; } // digit_start = j; if (j == 0) //no chance of a preceeding non-digit digit_start = 0; } else if (last_was_digit) { digit_start = j + 1; break; } j--; } if (digit_start != -1) { //there is a number, grab it and otherwise carve up the entered name digit_len = digit_end - digit_start + 1; p = g_utf8_offset_to_pointer (part3, digit_end + 1); part4 = g_strdup (p); *p ='\0'; p = g_utf8_offset_to_pointer (part3, digit_start); i = (guint) atoi (p); *p = '\0'; } gchar numfmt[20]; numfmt[0] = '%'; if (digit_len > 1) g_snprintf (numfmt+1, sizeof(numfmt)-1, "0%luu", digit_len); else g_strlcpy (numfmt+1, "u", sizeof(numfmt)-1); //if there is no number in the directory string, we simply add it //to the end gchar *freeme; gchar *format = g_strconcat ( "%s", //(*part1 == '\0') ? "" : G_DIR_SEPARATOR_S, "%s", (*part2 == '\0') ? "" : G_DIR_SEPARATOR_S, "%s", //part3 always has something useful numfmt, NULL); //otherwise, add a trailing token for part4 if (digit_start != -1) { freeme = format; format = g_strconcat (freeme, "%s", NULL); g_free (freeme); } //now check for a non-existent item with an incremented number #ifdef E2_VFS while (!e2_fs_access (&ddata, F_OK E2_ERR_NONE())) #else while (!e2_fs_access (local, F_OK E2_ERR_NONE())) #endif { i++; g_free (path); if (digit_start != -1) path = g_strdup_printf (format, part1, part2, part3, i, part4); else path = g_strdup_printf (format, part1, part2, part3, i); F_FREE (local); local = F_FILENAME_TO_LOCALE (path); } g_free (format); g_free (path); F_FREE (local); //now build the string for the entry //from the the string the user really entered if (digit_start != -1) { format = g_strconcat ("%s%s", numfmt, "%s", NULL); path = g_strdup_printf (format, part2, part3, i, part4); } else { format = g_strconcat ("%s%s", numfmt, NULL); path = g_strdup_printf (format, part2, part3, i); } g_free (format); } else { g_free (path); path = g_strdup (dir); } //cleanup g_free (part2); g_free (part3); return path; } /** @brief walk up the path of @a parent until an existing directory is found @param parent path of dir to check, freeable utf8 string @param exists store for flag indicating that directory @a parent was not found @param i store for no. of path segments scanned @return utf8 string, @a parent or newly-allocated substitute */ static gchar *_e2_mkdirdlg_real_parent (gchar *parent, gboolean *exists, gint *i) { #ifdef E2_VFSTMP //FIXME handle vfs parent #endif gchar *local = F_FILENAME_TO_LOCALE (parent); #ifdef E2_VFS VPATH ddata = { local, curr_view->spacedata }; while (e2_fs_access (&ddata, F_OK E2_ERR_NONE()) || ! e2_fs_is_dir3 (&ddata E2_ERR_NONE())) #else while (e2_fs_access (local, F_OK E2_ERR_NONE()) || ! e2_fs_is_dir3 (local E2_ERR_NONE())) #endif { (*i)++; *exists = FALSE; if ((parent[0] == '/') && (parent[1] == '\0')) break; gchar *freeme = parent; parent = g_path_get_dirname (parent); F_FREE (local); local = F_FILENAME_TO_LOCALE (parent); g_free (freeme); } F_FREE (local); return parent; } /** @brief clean up after a mkdir dialog This is called only from within the dialog response callback (so BGL applies) @param rt pointer to dialog data struct @return */ static void _e2_mkdirdlg_dialog_destroy (E2_MkdirDialogRuntime *rt) { //#ifdef RACE_CHECK printd (DEBUG, "mkdir dialog destroy"); //#endif gtk_widget_destroy (rt->dialog); e2_list_free_with_data (&rt->history); e2_hook_unregister (&app.pane1.hook_change_dir, _e2_mkdirdlg_change_dir_hook, rt, TRUE); e2_hook_unregister (&app.pane2.hook_change_dir, _e2_mkdirdlg_change_dir_hook, rt, TRUE); e2_hook_unregister (&app.hook_pane_focus_changed, _e2_mkdirdlg_change_focus_hook, rt, TRUE); //#ifdef RACE_CHECK // printd (DEBUG, "mkdir dialog unhooks completed"); //#endif g_free (rt->path); if (rt->idle_id != 0) #ifdef RACE_CHECK { printd (DEBUG, "mkdir dialog"); gboolean debug = #endif g_source_remove (rt->idle_id); #ifdef RACE_CHECK if (debug) printd (DEBUG, " idle removal completed"); else printd (DEBUG, " idle removal failed"); } // else // printd (DEBUG, "mkdir dialog NO idle source to remove"); #endif DEALLOCATE (E2_MkdirDialogRuntime, rt); //#ifdef RACE_CHECK // printd (DEBUG, "mkdir dialog destroy COMPLETED"); //#endif gtk_main_quit (); } /** @brief change size of a mkdir dialog window This is called from several placces, all inside BGL @param rt pointer to dialog data struct @return */ static void _e2_mkdirdlg_update_dialog_size (E2_MkdirDialogRuntime *rt) { // printd (DEBUG, "mkdir dialog resize pending"); e2_dialog_resize_with_sw (rt->dialog, rt->scrolled); //this works at idle-time } /** @brief update dialog label which describes the parent path This is called from _e2_mkdirdlg_update_status() (inside BGL) @param path parent directory path, utf8 string @param rt pointer to dialog data struct @return */ static void _e2_mkdirdlg_update_parent_label (gchar *path, E2_MkdirDialogRuntime *rt) { printd (DEBUG, "mkdir dialog update parent label"); #ifdef E2_VFSTMP //FIXME path for non-mounted dirs #else gchar *translated = e2_utils_translate_relative_path (rt->path, path); #endif gchar *public = g_markup_escape_text (translated, -1); gchar *label = g_strconcat ("", public, "", NULL); gtk_label_set_markup (GTK_LABEL (rt->info_label), label); g_free (translated); g_free (public); g_free (label); } /** @brief update dialog label which describes creation, and also buttons' sensitivity This is called from _e2_mkdirdlg_update_status() (inside BGL) @param reason explanation string @param rt pointer to dialog data struct @return */ static void _e2_mkdirdlg_update_creation_widgets (gchar *reason, E2_MkdirDialogRuntime *rt) { printd (DEBUG, "mkdir dialog update creation widgets"); gchar *color; gchar *constant; if (rt->creation_possible) { //FIXME gtk stops repeated clicking of create button without leaving it //if the E2_RESPONSE_CREATE sensitivity or corresponding button //sensitivity is ever changed either way gtk_dialog_set_response_sensitive (GTK_DIALOG (rt->dialog), E2_RESPONSE_CREATE, TRUE); gtk_dialog_set_response_sensitive (GTK_DIALOG (rt->dialog), GTK_RESPONSE_OK, TRUE); color = e2_option_str_get ("color-positive"); constant = _("yes"); } else { gtk_dialog_set_response_sensitive (GTK_DIALOG (rt->dialog), E2_RESPONSE_CREATE, FALSE); gtk_dialog_set_response_sensitive (GTK_DIALOG (rt->dialog), GTK_RESPONSE_OK, FALSE); color = e2_option_str_get ("color-negative"); constant = _("no"); } gchar *label = g_strconcat ("", constant, " ", reason == NULL ? NULL : "(", reason, ")", NULL); gtk_label_set_markup (GTK_LABEL (rt->info_label2), label); g_free (label); } /** @brief update the two info labels in the info frame, and buttons' sensitivity This is done even if the info frame isn't shown, to update the rt->creation_possible state and buttons It must be called only from inside BGL as no thread protection is done @param rt pointer to dialog's data struct @return */ static void _e2_mkdirdlg_update_status (E2_MkdirDialogRuntime *rt) { printd (DEBUG, "mkdir dialog update status"); gboolean absolute; gchar *path, *parent; E2_ERR_DECLARE rt->creation_possible = FALSE; const gchar *entry_text = gtk_entry_get_text (GTK_ENTRY (GTK_BIN (rt->combo)->child)); //if the entry is empty, we'll only check if the parent directory //is writable, so, set path to the currently active directory if (*entry_text == '\0') { path = g_strdup (rt->path); absolute = TRUE; //CHECKME parent = g_strdup (path); } else //otherwise we have to create an absolute path from //the entry text and the active directory { path = (gchar *) entry_text; absolute = g_path_is_absolute (path); if (absolute) path = g_strdup (entry_text); //make sure this is always freeable else //the directory entered is not absolute, prepend ... path = g_strconcat (rt->path, path, NULL); //CHECKME extra separator ? parent = g_path_get_dirname (path); } gboolean parent_exists = TRUE; //number of "ancestor" dirs that need to be created gint num = g_str_has_suffix (entry_text, G_DIR_SEPARATOR_S) ? 0 : 1; parent = _e2_mkdirdlg_real_parent (parent, &parent_exists, &num); _e2_mkdirdlg_update_parent_label (parent, rt); gchar *reason = NULL; if (*entry_text == '\0') { _e2_mkdirdlg_update_creation_widgets (reason, rt); g_free (path); g_free (parent); return; } gchar *local = F_FILENAME_TO_LOCALE (parent); #ifdef E2_VFS VPATH ddata = { local, curr_view->spacedata }; if ((e2_fs_access (&ddata, W_OK | X_OK E2_ERR_PTR())) != 0) #else if ((e2_fs_access (local, W_OK | X_OK E2_ERR_PTR())) != 0) #endif { if (absolute) reason = g_strdup_printf (_("cannot write to '%s' - %s"), parent, #ifdef E2_VFS E2_ERR_NAME->message #else g_strerror (errno) #endif ); else if (parent_exists) reason = g_strdup_printf (_("cannot write to parent directory - %s"), #ifdef E2_VFS E2_ERR_NAME->message #else g_strerror (errno) #endif ); else reason = g_strdup_printf (_("only '%s' exists - %s"), parent, #ifdef E2_VFS E2_ERR_NAME->message #else g_strerror (errno) #endif ); _e2_mkdirdlg_update_creation_widgets (reason, rt); E2_ERR_CLEAR g_free (path); g_free (parent); F_FREE (local); return; } F_FREE (local); local = F_FILENAME_TO_LOCALE (path); #ifdef E2_VFS ddata.localpath = local; if (!e2_fs_access (&ddata, F_OK E2_ERR_NONE())) #else if (!e2_fs_access (local, F_OK E2_ERR_NONE())) #endif { #ifdef E2_VFS if (e2_fs_is_dir3 (&ddata E2_ERR_NONE())) #else if (e2_fs_is_dir3 (local E2_ERR_NONE())) #endif reason = _("the directory already exists."); else reason = _("something is in the way."); _e2_mkdirdlg_update_creation_widgets (reason, rt); g_free (path); g_free (parent); F_FREE (local); return; } F_FREE (local); if (num > 1) reason = g_strdup_printf ("%d %s",num,_("directories will be created")); rt->creation_possible = TRUE; _e2_mkdirdlg_update_creation_widgets (reason, rt); g_free (parent); g_free (path); } /** @brief update suggested name for the next mkdir, and consequent UI changes This is called from several places, all with BGL closed @param rt pointer to dialog data struct @return */ static void _e2_mkdirdlg_update_name_and_status (E2_MkdirDialogRuntime *rt) { printd (DEBUG, "mkdir dialog update name and status"); GtkWidget *entry = GTK_BIN (rt->combo)->child; if (rt->opt_suggest_dir || rt->opt_show_last) { gchar *text = e2_combobox_first_text (GTK_COMBO_BOX (rt->combo)); if (text != NULL) { if (rt->opt_suggest_dir) { gchar *new_text = _e2_mkdirdlg_find_dir (text, rt->path); gtk_entry_set_text (GTK_ENTRY (entry), new_text); g_free (new_text); } g_free (text); gtk_editable_select_region (GTK_EDITABLE (entry), 0, -1); } else gtk_entry_set_text (GTK_ENTRY (entry), ""); } else gtk_entry_set_text (GTK_ENTRY (entry), ""); _e2_mkdirdlg_update_status (rt); #ifdef RACE_CHECK printd (DEBUG, "mkdir update combo completed"); #endif } /** @brief set or clear mechanism for following changes of active directory This is called out of BGL If following is turned on, the default path is updated and status info etc updated accordingly @param rt pointer to dialog data struct @param follow TRUE to turn on following, FALSE to turn it off @return */ static void _e2_mkdirdlg_update_follow_dir (E2_MkdirDialogRuntime *rt, gboolean follow) { printd (DEBUG, "mkdir dialog update dir-following"); if (follow) { g_free (rt->path); rt->path = g_strdup (curr_pane->path); gdk_threads_enter (); _e2_mkdirdlg_update_status (rt); gdk_threads_leave (); #ifdef E2_VFSTMP //CHECKME hooks for non-mounted dirs #endif e2_hook_register (&app.pane1.hook_change_dir, _e2_mkdirdlg_change_dir_hook, rt); e2_hook_register (&app.pane2.hook_change_dir, _e2_mkdirdlg_change_dir_hook, rt); e2_hook_register (&app.hook_pane_focus_changed, _e2_mkdirdlg_change_focus_hook, rt); } else { e2_hook_unregister (&app.pane1.hook_change_dir, _e2_mkdirdlg_change_dir_hook, rt, TRUE); e2_hook_unregister (&app.pane2.hook_change_dir, _e2_mkdirdlg_change_dir_hook, rt, TRUE); e2_hook_unregister (&app.hook_pane_focus_changed, _e2_mkdirdlg_change_focus_hook, rt, TRUE); } } /** @brief hook function for app.paneX.hook_change_dir This is called with BGL off/open @param path path of an opened directory, utf-8 string @param rt pointer to dialog's data struct @return TRUE */ static gboolean _e2_mkdirdlg_change_dir_hook (gchar *path, E2_MkdirDialogRuntime *rt) { g_free (rt->path); rt->path = g_strdup (path); gdk_threads_enter (); _e2_mkdirdlg_update_status (rt); _e2_mkdirdlg_update_dialog_size (rt); gdk_threads_leave (); return TRUE; } /** @brief hook function for app.hook_pane_focus_changed This is called with BGL off/open @param pane_rt data struct for the currenly-focused pane @param rt pointer to dialog's data struct @return TRUE */ static gboolean _e2_mkdirdlg_change_focus_hook (E2_PaneRuntime *pane_rt, E2_MkdirDialogRuntime *rt) { g_free (rt->path); #ifdef E2_VFSTMP //FIXME path for non-mounted dirs #else LISTS_LOCK rt->path = g_strdup (pane_rt->path); LISTS_UNLOCK #endif gdk_threads_enter (); _e2_mkdirdlg_update_status (rt); _e2_mkdirdlg_update_dialog_size (rt); gdk_threads_leave (); return TRUE; } /** @brief create a new directory, along with any missing "ancestor(s)", in the active-pane dir The string provided may be an absolute or relative path, the latter including just a name. If relative, the last-used dir?? will be prepended This is called only from _e2_mkdirdlg_response_cb () with BGL closed @param entry the entry widget for the name-entry combo @param rt pointer to dialog's data struct @param close TRUE if the dialog will be closed when finished here @return */ static void _e2_mkdirdlg_create_dir (GtkWidget *entry, E2_MkdirDialogRuntime *rt, gboolean close) { printd (DEBUG, "mkdir dialog create dir"); if (!rt->creation_possible) return; const gchar *dir = gtk_entry_get_text (GTK_ENTRY (entry)); //quick exit if (*dir == '\0') return; gchar *path, *parent, *local, *freeme = NULL; if (g_path_is_absolute (dir)) path = g_strdup (dir); else path = g_strconcat (rt->path, dir, NULL); //find closest existing "ancestor" of the dir to be created, and any //intervening ancestor dir(s) that need to be created GList *tmp, *missing = NULL; parent = g_path_get_dirname (path); if (g_str_has_suffix (path, G_DIR_SEPARATOR_S)) { freeme = parent; parent = g_path_get_dirname (parent); g_free (freeme); } freeme = local = F_FILENAME_TO_LOCALE (parent); #ifdef E2_VFS VPATH ddata = { local, curr_view->spacedata }; while (e2_fs_access (&ddata, F_OK E2_ERR_NONE()) || !e2_fs_is_dir3 (&ddata E2_ERR_NONE())) #else while (e2_fs_access (local, F_OK E2_ERR_NONE()) || !e2_fs_is_dir3 (local E2_ERR_NONE())) #endif { #ifdef E2_VFSTMP //FIXME parent path for virtual dirs #endif if ((parent[0] == G_DIR_SEPARATOR) && (parent[1] == '\0')) break; missing = g_list_prepend (missing, parent); parent = g_path_get_dirname (parent); F_FREE (local); local = F_FILENAME_TO_LOCALE (parent); } g_free (parent); F_FREE (freeme); E2_ERR_DECLARE //create any missing ancestor(s) for (tmp = missing; tmp != NULL; tmp = g_list_next (tmp)) { local = F_FILENAME_TO_LOCALE ((gchar *)tmp->data); // E2_ERR_NAME = NULL; #ifdef E2_VFS ddata.localpath = local; if (e2_fs_mkdir (&ddata, 0777 E2_ERR_PTR())) //FIXME vfs #else if (e2_fs_mkdir (local, 0777 E2_ERR_PTR())) //FIXME vfs #endif { gdk_threads_leave (); //downstream does local mutex management e2_fs_error_local (_("Cannot create directory %s"), #ifdef E2_VFS &ddata E2_ERR_MSGL()); #else local E2_ERR_MSGL()); #endif gdk_threads_enter (); E2_ERR_CLEAR if (!close) _e2_mkdirdlg_update_status (rt); #ifdef E2_VFSTMP //FIXME path for non-mounted dirs #else e2_filelist_request_refresh (rt->path, TRUE); #endif F_FREE (local); e2_list_free_with_data (&missing); return; } F_FREE (local); } e2_list_free_with_data (&missing); //now create the chosen dir itself local = F_FILENAME_TO_LOCALE (path); #ifdef E2_VFS ddata.localpath = local; if (!e2_fs_mkdir (&ddata, 0777 E2_ERR_PTR())) #else if (!e2_fs_mkdir (local, 0777 E2_ERR_PTR())) #endif { //succeeded if (!close) _e2_mkdirdlg_update_name_and_status (rt); #ifdef E2_VFSTMP //FIXME path for non-mounted dirs #else e2_filelist_request_refresh (rt->path, TRUE); #endif e2_list_update_history ((gchar *) dir, &mkdir_history, NULL, 30, FALSE); } else //mkdir failed { gdk_threads_leave (); //downstream does local mutex management e2_fs_error_local (_("Cannot create directory %s"), #ifdef E2_VFS &ddata E2_ERR_MSGL()); #else local E2_ERR_MSGL()); #endif gdk_threads_enter (); E2_ERR_CLEAR if (!close) _e2_mkdirdlg_update_status (rt); } g_free (path); F_FREE (local); } /*********************/ /***** callbacks *****/ /*********************/ /** @brief dialog response callback @param dialog the dialog where the response was initiated @param response the response assigned to the activated button @param rt pointer to dialog's data struct @return */ static void _e2_mkdirdlg_response_cb (GtkDialog *dialog, gint response, E2_MkdirDialogRuntime *rt) { printd (DEBUG, "mkdir dialog response_cb (dialog:_,response:%d,rt:_)", response); GtkWidget *entry; switch (response) { case E2_RESPONSE_CREATE: case GTK_RESPONSE_OK: entry = GTK_BIN (rt->combo)->child; const gchar *dir = gtk_entry_get_text (GTK_ENTRY (entry)); //quick exit if (*dir == '\0') break; if (strchr (dir, '%') != NULL) { gchar *freeme = e2_utils_expand_macros ((gchar *)dir, NULL); if (freeme != NULL && freeme != GINT_TO_POINTER (1)) { gtk_entry_set_text (GTK_ENTRY (entry), freeme); g_free (freeme); } } e2_combobox_activated_cb (entry, GINT_TO_POINTER (FALSE)); *rt->status = E2_TASK_RUNNING; if (response == E2_RESPONSE_CREATE) { _e2_mkdirdlg_create_dir (entry, rt, FALSE); // e2_filelist_request_focus (entry); *rt->status = E2_TASK_PAUSED; gtk_widget_grab_focus (entry); break; } _e2_mkdirdlg_create_dir (entry, rt, TRUE); default: _e2_mkdirdlg_dialog_destroy (rt); break; } } /** @brief combobox active-item "changed" callback This is also called every time a letter is added to or removed from the dialog's name-entry @param combo UNUSED the affected combobox @param rt pointer to dialog's data struct @return */ static void _e2_mkdirdlg_changed_cb (GtkComboBox *combo, E2_MkdirDialogRuntime *rt) { printd (DEBUG, "mkdir dialog changed cb"); _e2_mkdirdlg_update_status (rt); _e2_mkdirdlg_update_dialog_size (rt); } /** @brief combobox entry "activate" callback This simply triggers an 'ok' type response @param entry UNUSED the entry widget for the name-entry combo @param rt pointer to dialog's data struct @return */ static void _e2_mkdirdlg_activate_cb (GtkWidget *entry, E2_MkdirDialogRuntime *rt) { printd (DEBUG, "mkdir dialog activate cb"); _e2_mkdirdlg_response_cb (GTK_DIALOG (rt->dialog), GTK_RESPONSE_OK, rt); } /** @brief callback for context menu item for toggling "follow active pane" @param menu_item the activated widget @param rt pointer to dialog's data struct @return */ static void _e2_mkdirdlg_toggled1_cb (GtkWidget *menu_item, E2_MkdirDialogRuntime *rt) { printd (DEBUG, "mkdir dialog follow pane toggle"); gboolean state = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (menu_item)); //open BGL to allow downstream mutex management gdk_threads_leave (); _e2_mkdirdlg_update_follow_dir (rt, state); gdk_threads_enter (); gpointer p = g_object_get_data (G_OBJECT (rt->dialog), "e2-controller-blocked"); if (!GPOINTER_TO_INT (p)) e2_option_bool_set_direct (follow_pane, state); } /** @brief callback for context menu item for toggling "show last entry" @param menu_item the activated widget @param rt pointer to dialog's data struct @return */ static void _e2_mkdirdlg_toggled2_cb (GtkWidget *menu_item, E2_MkdirDialogRuntime *rt) { printd (DEBUG, "mkdir dialog show last entry toggle"); rt->opt_show_last = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (menu_item)); _e2_mkdirdlg_update_name_and_status (rt); gpointer p = g_object_get_data (G_OBJECT (rt->dialog), "e2-controller-blocked"); if (!GPOINTER_TO_INT (p)) e2_option_bool_set_direct (show_last, rt->opt_show_last); } /** @brief callback for context menu item for toggling "suggest dir" @param menu_item the activated widget @param rt pointer to dialog's data struct @return */ static void _e2_mkdirdlg_toggled3_cb (GtkWidget *menu_item, E2_MkdirDialogRuntime *rt) { printd (DEBUG, "mkdir dialog suggest dir toggle"); rt->opt_suggest_dir = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (menu_item)); _e2_mkdirdlg_update_name_and_status (rt); gpointer p = g_object_get_data (G_OBJECT (rt->dialog), "e2-controller-blocked"); if (!GPOINTER_TO_INT (p)) e2_option_bool_set_direct (suggest_dir, rt->opt_suggest_dir); } /** @brief callback for info expander opened or closed @param expander the activated widget @param spec @param rt pointer to dialog's data struct @return */ static void _e2_mkdirdlg_expander_toggled_cb (GtkWidget *expander, GParamSpec *spec, E2_MkdirDialogRuntime *rt) { // printd (DEBUG, "mkdir dialog expander toggle"); _e2_mkdirdlg_update_dialog_size (rt); } /** @brief callback for dialog window shown @param dialog the shown widget @param rt pointer to dialog's data struct @return */ static void _e2_mkdirdlg_show_cb (GtkWidget *dialog, E2_MkdirDialogRuntime *rt) { printd (DEBUG, "mkdir dialog show cb"); //convenient to set name here, inside BGL _e2_mkdirdlg_update_name_and_status (rt); gtk_expander_set_expanded (GTK_EXPANDER (rt->info_expander), e2_option_bool_get ("dialog-mkdir-show-info")); } /******************/ /***** action *****/ /******************/ /** @brief create and show a mkdir dialog @return TRUE always */ static gboolean _e2_mkdir_dialog_create (gpointer from, E2_ActionRuntime *art) { return (e2_task_enqueue_task (E2_TASK_MKDIR, art, from, _e2_task_mkdirQ, NULL)); } static gboolean _e2_task_mkdirQ (E2_ActionTaskData *qed) { //init runtime object E2_MkdirDialogRuntime *rt = ALLOCATE (E2_MkdirDialogRuntime); CHECKALLOCATEDWARN (rt, return FALSE;) rt->history = e2_list_copy_with_data (mkdir_history); #ifdef E2_VFSTMP //FIXME path for non-mounted dirs #else rt->path = D_FILENAME_FROM_LOCALE (qed->currdir); #endif rt->status = qed->status; //enable on-the-fly status changes rt->creation_possible = TRUE; rt->idle_id = 0; //create dialog rt->dialog = e2_dialog_create (GTK_STOCK_DIALOG_QUESTION, _("What is the new directory's name?"), _("create directory"), _e2_mkdirdlg_response_cb, rt); GtkWidget *vbox = e2_dialog_add_sw (rt->dialog); rt->scrolled = vbox->parent->parent; //bad !! //CHECKME this is needed to workaround gtk weirdness, //= bad delay when any key is pressed during a cursor-visible interval gdk_threads_enter (); //add combo rt->combo = e2_combobox_add (vbox, FALSE, E2_PADDING, _e2_mkdirdlg_activate_cb, rt, &mkdir_history, E2_COMBOBOX_HAS_ENTRY | E2_COMBOBOX_FOCUS_ON_CHANGE); gdk_threads_leave (); //add expander with info box as its child GtkWidget *info_box = e2_widget_get_box (TRUE, FALSE, 1); rt->info_expander = e2_widget_add_expander (vbox, _("info"), e2_option_get ("dialog-mkdir-show-info"), rt->dialog, info_box); g_signal_connect (G_OBJECT (rt->info_expander), "notify::expanded", G_CALLBACK (_e2_mkdirdlg_expander_toggled_cb), rt); GtkSizeGroup *group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); GtkWidget *hbox = e2_widget_add_box (info_box, FALSE, 0, FALSE, FALSE, 0); GtkWidget *label = e2_widget_add_label (hbox, _("parent directory:"), 0.0, 0.0, FALSE, E2_PADDING_SMALL); gtk_size_group_add_widget (group, label); rt->info_label = e2_widget_add_mid_label (hbox, "", 0.0, TRUE, E2_PADDING_SMALL); hbox = e2_widget_add_box (info_box, FALSE, 0, FALSE, FALSE, 0); label = e2_widget_add_label (hbox, _("creation possible:"), 0.0, 0.0, FALSE, E2_PADDING_SMALL); gtk_size_group_add_widget (group, label); rt->info_label2 = e2_widget_add_label (hbox, "", 0.0, 0.0, TRUE, E2_PADDING_SMALL); g_object_unref (G_OBJECT (group)); if (follow_pane == NULL) follow_pane = e2_option_get ("dialog-mkdir-follow-pane"); if (suggest_dir == NULL) suggest_dir = e2_option_get ("dialog-mkdir-suggest-directory"); if (show_last == NULL) show_last = e2_option_get ("dialog-mkdir-show-last"); if (connected == NULL) connected = e2_option_get ("dialog-mkdir-connected"); rt->menu = e2_menu_create_options_menu (rt->dialog, NULL, follow_pane, _e2_mkdirdlg_toggled1_cb, rt, suggest_dir, _e2_mkdirdlg_toggled3_cb, rt, show_last, _e2_mkdirdlg_toggled2_cb, rt, connected, e2_menu_control_cb, rt->dialog, NULL); e2_dialog_attach_menu (rt->dialog, rt->menu); //do this after create button is created _e2_mkdirdlg_update_follow_dir (rt, e2_option_bool_get_direct (follow_pane)); e2_option_connect (rt->dialog, e2_option_bool_get_direct (connected)); rt->opt_show_last = e2_option_bool_get_direct (show_last); rt->opt_suggest_dir = e2_option_bool_get_direct (suggest_dir); g_signal_connect (G_OBJECT (rt->dialog), "show", G_CALLBACK (_e2_mkdirdlg_show_cb), rt); E2_BUTTON_CANCEL.showflags |= E2_BTN_DEFAULT; E2_BUTTON_OK.showflags &= ~E2_BTN_DEFAULT; // e2_dialog_add_defined_button (rt->dialog, &E2_BUTTON_CANCEL); // rt->create_btn = // e2_dialog_add_defined_button (rt->dialog, &E2_BUTTON_CREATE); // _e2_mkdirdlg_update_follow_dir (rt, e2_option_bool_get_direct (follow_pane)); e2_dialog_show (rt->dialog, app.main_window, E2_DIALOG_THREADED | E2_DIALOG_DONT_SHOW_ALL, &E2_BUTTON_CANCEL, &E2_BUTTON_CREATE, &E2_BUTTON_OK, NULL); //do this after showing, to reduce repitition g_signal_connect (G_OBJECT (rt->combo), "changed", G_CALLBACK (_e2_mkdirdlg_changed_cb), rt); *qed->status = E2_TASK_PAUSED; gdk_threads_enter (); gtk_main (); //block the queue-thread until the user is finished gdk_threads_leave (); return TRUE; } /******************/ /***** public *****/ /******************/ void e2_mkdir_dialog_actions_register () { e2_cache_list_register ("mkdir-history", &mkdir_history); gchar *action_name = g_strconcat(_A(1),".",_A(56),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_mkdir_dialog_create, NULL, FALSE); } void e2_mkdir_dialog_options_register () { gchar* group_name = g_strconcat(_C(11),":",_C(23),NULL); //_("dialogs:mkdir" e2_option_bool_register ("dialog-mkdir-show-info", group_name, _("open info frame"), _("This causes make-directory dialogs to start with extra information displayed"), NULL, TRUE, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP); e2_option_bool_register ("dialog-mkdir-follow-pane", group_name, _("follow active-pane directory"), _("This makes the parent directory for new directories the same as the one in the active pane, even if the latter changes"), NULL, TRUE, E2_OPTION_FLAG_ADVANCED); e2_option_bool_register ("dialog-mkdir-suggest-directory", group_name, _("suggest directory name"), _("This presents a suggested name for each new directory, based on the last-created directory with an increasing number appended"), NULL, TRUE, E2_OPTION_FLAG_ADVANCED); e2_option_bool_register ("dialog-mkdir-show-last", group_name, _("show last directory name"), _("This causes the name of the last-created directory to be shown in the entry field, after opening the dialog or when creating another directory"), "!dialog-mkdir-suggest-directory", FALSE, E2_OPTION_FLAG_ADVANCED); e2_option_bool_register ("dialog-mkdir-connected", group_name, _("replicate changes"), _("This causes option-changes to be replicated in other mkdir dialogs. Otherwise such changes will be confined to the current dialog"), NULL, TRUE, E2_OPTION_FLAG_ADVANCED); } emelfm2-0.4.1/src/dialogs/e2_select_image_dialog.h0000600000175000017500000000264011014260306020712 0ustar cairocairo/* $Id: e2_select_image_dialog.h 885 2008-05-19 11:13:10Z tpgww $ Copyright (C) 2003-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_SELECT_IMAGE_DIALOG_H__ #define __E2_SELECT_IMAGE_DIALOG_H__ #include "emelfm2.h" typedef struct _E2_SID_Runtime { gchar *name; gchar *icon; //gtk-stock-icon name, or path to icon file, taken from tree-option store GtkWidget *dialog; GtkWidget *parent; GtkNotebook *notebook; gint page; GtkIconView *stockview; GtkTreeModel *stockmodel; GtkIconView *customview; GtkTreeModel *custommodel; GtkWidget *dir_chooser; //button } E2_SID_Runtime; GtkWidget *e2_sid_create (GtkWidget *parent, const gchar *name, gchar *icon, gpointer response_func, E2_OptionSet *set); #endif //ndef __E2_SELECT_IMAGE_DIALOG_H__ emelfm2-0.4.1/src/dialogs/e2_permissions_dialog.h0000600000175000017500000000234011010340377020644 0ustar cairocairo/* $Id: e2_permissions_dialog.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_PERMISSIONS_DIALOG_H__ #define __E2_PERMISSIONS_DIALOG_H__ #include "emelfm2.h" #include "e2_dialog.h" typedef enum { E2_RECURSE_NONE = 0, E2_RECURSE_DIRS = 1, E2_RECURSE_OTHER = 1 << 2, E2_RECURSE_ALL = 1 << 3 } E2_RecurseType; DialogButtons e2_permissions_dialog_run (VPATH *localpath, gchar **mode_ret, E2_RecurseType *recurse_ret, gboolean *permission_ret, gboolean multi); #endif //ndef __E2_PERMISSIONS_DIALOG_H__ emelfm2-0.4.1/src/dialogs/e2_config_dialog.h0000600000175000017500000000374611010340377017551 0ustar cairocairo/* $Id: e2_config_dialog.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_CONFIG_DIALOG_H__ #define __E2_CONFIG_DIALOG_H__ enum { E2_CFGDLG_SINGLE=1, E2_CFGDLG_MAIN, E2_CFGDLG_BASIC, E2_CFGDLG_ADVANCED, }; typedef struct _E2_ConfigDialogRuntime { GtkTreeStore *store; //page-names store GtkWidget *treeview; //page-names treeview GtkTreePath *openpath; //path of startup-name in page-names treeview gint openpage; //index of startup notebook page GtkNotebook *notebook; GHashTable *opthash; //sizegroups hash } E2_ConfigDialogRuntime; gboolean e2_confdlg_key_press_cb (GtkWidget *widget, GdkEventKey *event, gpointer data); void e2_config_dialog_create (gchar *page); void e2_config_dialog_setup_labels (void); void e2_config_dialog_actions_register (void); void e2_confdlg_choose_plugin_cb (GtkWidget *widget, E2_OptionSet *set); #ifdef E2_RAINBOW void e2_confdlg_extcolorpick_cb (GtkWidget *widget, E2_OptionSet *set); #endif // things for single-page config dialogs typedef struct _E2_SpecificConfDialogRuntime { GtkWidget *dialog; E2_OptionSet *set; void (*apply_function) (); } E2_SpecificConfDialogRuntime; E2_SpecificConfDialogRuntime *e2_config_dialog_single (gchar *set_name, void *response_cb, gboolean showit); #endif //ndef __E2_CONFIG_DIALOG_H__ emelfm2-0.4.1/src/dialogs/e2_tree_dialog.h0000600000175000017500000000177411010340377017242 0ustar cairocairo/* $Id: e2_tree_dialog.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2007-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_TREE_DIALOG_H__ #define __E2_TREE_DIALOG_H__ #include "emelfm2.h" #ifdef E2_TREEDIALOG gboolean e2_tree_dialog_show_action (gpointer from, E2_ActionRuntime *art); #endif #endif //ndef __E2_TREE_DIALOG_H__ emelfm2-0.4.1/src/dialogs/e2_config_dialog.c0000600000175000017500000016256011010574071017544 0ustar cairocairo/* $Id: e2_config_dialog.c 876 2008-05-08 12:55:21Z tpgww $ Copyright (C) 2003-2008 tooar Portions copyright (C) 1999 Michael Clark This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "emelfm2.h" #include #include "e2_dialog.h" #include "e2_config_dialog.h" #include "e2_option_tree.h" #include "e2_filelist.h" #include "e2_task.h" #include "e2_plugins.h" #include "e2_filetype.h" //define to apply scrolledwindow to each page of the config notebeok, allowing //omission of bars for whole book, which cause nested bars for tree-set data #define NEWSCROLL typedef struct _E2_TreeCfgData { E2_OptionSet *set; GtkTreePath *tpath; } E2_TreeCfgData; //common vars we need in several functions GtkWidget *config_dialog = NULL; //this is accessed by other files static gchar *page_last_name = NULL; //the group name for the notebook page last opened // =========== functions supporting single-page config dialogs =============== /** @brief dummy 'apply' function used when none is supplied by caller */ static void _e2_cd1_no_application (void) {} /** @brief clean up at the end of a one-page config dialog This relates to tree-options only @param rt pointer to data struct for the dialog @param flushtype TRUE to restore backed-up data to the tree, FALSE to cleanup only @return */ static void _e2_cd1_cancel (E2_SpecificConfDialogRuntime *rt, gboolean flushtype) { //restore or abandon the default tree data e2_option_tree_unbackup (rt->set, flushtype); gtk_widget_destroy (rt->dialog); //cleanup any copy/cut buffer data g_hash_table_destroy (tree_view_buffer_hash); DEALLOCATE (E2_SpecificConfDialogRuntime, rt); config_dialog = NULL; } /*********************/ /***** callbacks *****/ /*********************/ /** @brief callback for dialog "delete-event" signal @param widget UNUSED pointer to the widget that was 'deleted' @param rt pointer to data struct for the dialog @return */ /* handled in default response static void _e2_cd1_close_cb (GtkWidget *widget, E2_SpecificConfDialogRuntime *rt) { _e2_cd1_cancel (rt, TRUE); } */ /** @brief close the dialog when esc key is pressed This is the callback for @a widget keypress signal @param widget UNUSED pointer to the widget that received the keypress @param event event data struct @param rt pointer to data struct for the dialog @return TRUE if esc key pressed, otherwise FALSE */ static gboolean _e2_cd1_key_press_cb (GtkWidget *widget, GdkEventKey *event, E2_SpecificConfDialogRuntime *rt) { if (event->keyval == GDK_Escape) { //restore tree data and clean up _e2_cd1_cancel (rt, TRUE); return TRUE; } return FALSE; } /** @brief handle button click in a one-page config dialog This is the callback for a buttonclick or other response signal Note: applies only to tree options @param dialog the dialog where the response was generated @param response the response assigned to @a button @param rt pointer to data struct for the dialog @return */ static void _e2_cd1_response_cb (GtkDialog *dialog, gint response, E2_SpecificConfDialogRuntime *rt) { switch (response) { case GTK_RESPONSE_OK: //perform any post-dialog data processing, via caller-specified 'apply' action (*rt->apply_function) (); //clean up without restoring tree data _e2_cd1_cancel (rt, FALSE); e2_option_disable_config_checks (); //update stored config data e2_option_file_write (NULL); //update stamp, to prevent auto-refresh e2_fs_touch_config_dir (); e2_option_enable_config_checks (); break; case GTK_RESPONSE_APPLY: (*rt->apply_function) (); /* //clear old backup data e2_option_tree_unbackup (rt->set, FALSE); //renew backup data, in case we cancel later e2_option_tree_backup (rt->set); */ break; default: _e2_cd1_cancel (rt, TRUE); break; } } /** @brief create single-page config dialog for a tree-type option set This creates and optionally shows a dialog similar to a page in the overall config dialog. It applies only to tree-type option sets. Clicking 'apply' or 'ok' performs a caller-specified fn (if any) and updates the config file @param set_name string, non-translated 'internal' name of the tree optionset to be worked on @param function ptr to caller's 'apply changes' function, or NULL if nothing to be done @param showit boolean TRUE to show the dialog immediately, FALSE if not @return E2_SpecificConfDialogRuntime created, with dialog data, or NULL if error occurred */ E2_SpecificConfDialogRuntime *e2_config_dialog_single (gchar *set_name, gpointer function, gboolean showit) { E2_SpecificConfDialogRuntime *rt = ALLOCATE (E2_SpecificConfDialogRuntime); CHECKALLOCATEDWARN (rt, return NULL;) rt->set = e2_option_get_simple (set_name); rt->apply_function = (function != NULL) ? function : _e2_cd1_no_application; //set config_dialog too, so that list-cell-renderer can access the dialog config_dialog = rt->dialog = e2_dialog_create (NULL, NULL, _("configuration"), _e2_cd1_response_cb, rt); gtk_widget_set_size_request (rt->dialog, 600, 400); GtkWidget *vbox = GTK_DIALOG (rt->dialog)->vbox; gtk_container_set_border_width (GTK_CONTAINER (vbox), E2_PADDING); GtkWidget *frame = e2_widget_add_frame (vbox, FALSE, 0, NULL, FALSE); gchar *label_text2 = g_strconcat ("", rt->set->group, "", NULL); GtkWidget *label = e2_widget_add_mid_label (NULL, label_text2, 0.5, FALSE, 0); g_free (label_text2); gtk_misc_set_padding (GTK_MISC (label), E2_PADDING_XSMALL, E2_PADDING_XSMALL); gtk_container_add (GTK_CONTAINER (frame), label); e2_option_connect (rt->dialog, FALSE); g_object_set_data (G_OBJECT (rt->dialog), "dialog-form", GINT_TO_POINTER (E2_CFGDLG_SINGLE)); //receptacle for cut/copied row(s) data generated from context menu //the keys are constant strings, not to be replaced or freed tree_view_buffer_hash = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) e2_option_tree_menu_hash_clean); //add tree config data //(this also arranges backup of tree data) e2_option_tree_add_widget (rt->dialog, vbox, rt->set); // g_signal_connect (G_OBJECT (rt->dialog), "delete-event", // G_CALLBACK (_e2_cd1_close_cb), rt); g_signal_connect_after (G_OBJECT (rt->dialog), "key-press-event", G_CALLBACK (_e2_cd1_key_press_cb), rt); //prepare dialog with buttons E2_BUTTON_CANCEL.showflags |= E2_BTN_DEFAULT; E2_BUTTON_OK.showflags &= ~E2_BTN_DEFAULT; e2_dialog_show (rt->dialog, app.main_window, 0, &E2_BUTTON_CANCEL, &E2_BUTTON_APPLY, &E2_BUTTON_OK, NULL); if (showit) gtk_widget_show_all (rt->dialog); return rt; } // ================== end of single-page config things ================== /*********************/ /***** callbacks *****/ /*********************/ /** @brief callback for options hash cleanup Size-group opjects in the hash need to be unreffed so that they will be cleared when all widgets in the group are destroyed @param data pointer to data item to be cleaned @return */ static void _e2_hashfree (gpointer data) { if (GTK_IS_SIZE_GROUP (data)) g_object_unref (data); } #ifndef NEWSCROLL /** @brief callback for show dialog @param dialog UNUSED the dialog widget being shown @param sw scrolled window containing the categories treeview @return */ static void _e2_confdlg_show_cb (GtkWidget *dialog, GtkWidget *sw) { gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); } #endif /** @brief callback for show dialog @param dialog the config dialog widget @param rt pointer to data struct for the dialog @return */ static void _e2_confdlg_show_cb2 (GtkWidget *dialog, E2_ConfigDialogRuntime *rt) { gtk_notebook_set_current_page (rt->notebook, rt->openpage); gtk_widget_grab_focus (rt->treeview); } /** @brief change dependent widget sensitivity, when the dependency is negated @param button the activated option toggle-button @param dependant the dependant option-widget @return TRUE */ static gboolean _e2_confdlg_negdepends_cb (GtkWidget *button, GtkWidget *dependant) { gtk_widget_set_sensitive (dependant, !GTK_TOGGLE_BUTTON (button)->active); return TRUE; } /** @brief change dependent widget sensitivity, when the dependency is not negated @param button the activated option toggle-button @param dependant the dependant option-widget @return TRUE */ static gboolean _e2_confdlg_posdepends_cb_ (GtkWidget *button, GtkWidget *dependant) { gtk_widget_set_sensitive (dependant, GTK_TOGGLE_BUTTON (button)->active); return TRUE; } /** @brief cleanup after config dialog is finished @return */ static void _e2_confdlg_clean (void) { if (config_dialog != NULL) { printd (DEBUG, "config dialog widget destroy"); app.cfgdlg_width = config_dialog->allocation.width; app.cfgdlg_height = config_dialog->allocation.height; gtk_widget_destroy (config_dialog); config_dialog = NULL; } //cleanup any copy/cut buffer data g_hash_table_destroy (tree_view_buffer_hash); } /** @brief process ok or apply button click @param apply TRUE if 'apply' button was pressed, FALSE if 'ok' @return */ static gboolean _e2_confdlg_ok_cb (gboolean apply) { printd (DEBUG, "config dialog callback: %s", (apply) ? "apply" : "ok"); e2_filelist_disable_refresh (); e2_option_disable_config_checks (); gboolean bchoice; gint ichoice; gchar *cchoice, *ccurrent; E2_OptionFlags buildflags = 0; guint i; gpointer *walker; E2_OptionSet *set; //update all options that were included in the dialog for (i = 0, walker = options_array->pdata; i < options_array->len; i++, walker++) { set = *walker; if (set->widget != NULL) //this is the flag for included-in-dialog { switch (set->type) { case E2_OPTION_TYPE_BOOL: bchoice = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (set->widget)); if (e2_option_bool_get_direct (set) != bchoice) { e2_option_bool_set_direct (set, bchoice); buildflags |= set->flags; } break; case E2_OPTION_TYPE_INT: ichoice = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (set->widget)); if (e2_option_int_get_direct (set) != ichoice) { e2_option_int_set_direct (set, ichoice); buildflags |= set->flags; } break; case E2_OPTION_TYPE_FONT: case E2_OPTION_TYPE_STR: case E2_OPTION_TYPE_COLOR: cchoice = gtk_editable_get_chars (GTK_EDITABLE (set->widget), 0, -1); ccurrent = e2_option_str_get_direct (set); if (! g_str_equal (cchoice, ccurrent)) { if (set->type == E2_OPTION_TYPE_COLOR) e2_option_color_set_str_direct (set, cchoice); else e2_option_str_set_direct (set, cchoice); buildflags |= set->flags; } g_free (cchoice); break; case E2_OPTION_TYPE_SEL: ichoice = gtk_combo_box_get_active (GTK_COMBO_BOX (set->widget)); if (e2_option_sel_get_direct (set) != ichoice) { e2_option_sel_set_direct (set, ichoice); buildflags |= set->flags; } break; case E2_OPTION_TYPE_TREE: if (set->ex.tree.flags & E2_OPTION_TREE_SET_EDITED) { buildflags |= set->flags; set->ex.tree.flags &= ~E2_OPTION_TREE_SET_EDITED; } //abandon backed-up tree-option data e2_option_tree_unbackup (set, FALSE); //if the config dialog is not finished, get ready for next ok/apply //FIXME this not needed if backup triggered by dirty tree if (apply) e2_option_tree_backup (set); break; default: break; } } } //if this is an 'ok' button response, close the dialog etc if (!apply) _e2_confdlg_clean (); e2_utils_update_gtk_settings (); //rebuild only what is needed ... //the 'special' (non-window-specific) rebuilds are handled in the //same order as for a session-start if (buildflags & E2_OPTION_FLAG_BUILDKEYS) e2_keybinding_clean (); #ifdef E2_IMAGECACHE if (buildflags & E2_OPTION_FLAG_BUILDICONS) { e2_cache_image_clearall (); e2_cache_icons_init (); } #endif if (buildflags & E2_OPTION_FLAG_BUILDPLUGS) e2_plugins_update (); if (buildflags & E2_OPTION_FLAG_BUILDSAMEBARS) { //user wants toolbar buttons conformed //FIXME distinguish between style & size gint index1 = e2_option_sel_get ("allbars-style"); gint index2 = e2_option_sel_get ("allbars-isize"); E2_ToolbarData **thisbar; for (thisbar = app.bars; *thisbar != NULL; thisbar++) { e2_option_sel_set_direct ((*thisbar)->rt->style, index1); e2_option_sel_set_direct ((*thisbar)->rt->isize, index2); } } #ifdef E2_RAINBOW if (buildflags & E2_OPTION_FLAG_BUILDFILES) e2_option_color_filetypes_sync (); //may be a color-data change #endif if (buildflags & E2_OPTION_FLAG_BUILDPANES) { //check if we want the panes to be the same const gchar *src_basename, *dest_basename; gchar *opt_name; gint srcnum, destnum, intvalue; //decide whether pane 1 or 2 or neither is the driver gboolean boolvalue = e2_option_bool_get ("pane1-uses-other"); if (boolvalue) { srcnum = 2; destnum = 1; } else { boolvalue = e2_option_bool_get ("pane2-uses-other"); if (boolvalue) { srcnum = 1; destnum = 2; } } if (boolvalue) { //the change wasn't merely to cancel the same-panes option //conform columns for (i = 1; i < MAX_COLUMNS; i++) //column 0 never changes { opt_name = g_strdup_printf ("pane%d-show-column%d", srcnum, i); boolvalue = e2_option_bool_get (opt_name); g_free (opt_name); opt_name = g_strdup_printf ("pane%d-show-column%d", destnum, i); e2_option_bool_set (opt_name, boolvalue); g_free (opt_name); } //CHECKME also conform the column order ? if (e2_option_bool_get ("advanced-config")) { //these are also shown in advancee mode //conform most other options set in e2_toolbar_options_register () E2_BarType barnum = (srcnum == 1) ? E2_BAR_PANE1 : E2_BAR_PANE2; src_basename = app.bars[barnum]->name; barnum = (destnum == 1) ? E2_BAR_PANE1 : E2_BAR_PANE2; dest_basename = app.bars[barnum]->name; //booleans gchar *bnames[5] = {"-show", "-tooltips", "-hori", "-relief", "-same"}; //option names, no translation for (i = 0; i < 5; i++) { opt_name = g_strconcat (src_basename, bnames[i], NULL); boolvalue = e2_option_bool_get (opt_name); g_free (opt_name); opt_name = g_strconcat (dest_basename, bnames[i], NULL); e2_option_bool_set (opt_name, boolvalue); g_free (opt_name); } //sels (except "-type" which can't automatically be conformed gchar *snames[3] = {"-space", "-style", "-isize"}; //option names, no translation for (i = 0; i < 3; i++) { opt_name = g_strconcat (src_basename, snames[i], NULL); intvalue = e2_option_sel_get (opt_name); g_free (opt_name); opt_name = g_strconcat (dest_basename, snames[i], NULL); e2_option_sel_set (opt_name, intvalue); g_free (opt_name); } //ints - can't validly set priority either /* opt_name = g_strconcat (src_basename, "-priority", NULL); intvalue = e2_option_int_get (opt_name); g_free (opt_name); opt_name = g_strconcat (dest_basename, "-priority", NULL); e2_option_int_set (opt_name, intvalue); g_free (opt_name); */ } } } if (buildflags & E2_OPTION_FLAG_BUILDALL) e2_window_recreate (&app.window); //also registers all key-bindings else if (buildflags & E2_OPTION_FLAG_BUILDPANES) { //rebuild filepane treeviews, & toolbars (in case they are destroyed with the panes) e2_window_recreate (&app.window); /* //FIXME more intelligent rebuild for this e.g no need to do output pane if (buildflags & E2_OPTION_FLAG_BUILDKEYS) //create all bindings, then register _("general" //(must be done before file-pane contents are created) e2_keybinding_register (_C(17), app.main_window); e2_pane_recreate (&app.pane1); e2_pane_recreate (&app.pane2); // e2_toolbar_create (&app.toolbar); //this might be inside a destroyed pane // e2_toolbar_create (&app.commandbar); e2_pane_flag_active (); WAIT_FOR_EVENTS; //make sure pane parameters are set, before connecting cb's e2_window_pane_cb_setup (&app.window); e2_pane_change_dir (&app.pane1, app.pane1.path); e2_pane_change_dir (&app.pane2, app.pane2.path); */ } else { if (buildflags & E2_OPTION_FLAG_BUILDBARS) e2_toolbar_recreate_all (); if (buildflags & E2_OPTION_FLAG_BUILDKEYS) //CHECKME ok to re-register command-line key bindings, if bars re-created ? //CHECKME maybe fileview bindings should be registered before the general //button-press callback for the respective treeviews e2_keybinding_register_all (); if (buildflags & E2_OPTION_FLAG_BUILDLISTS) { e2_fileview_set_font (); //FIXME do this only if needed //filelists will be refreshed only if dirty, so cd instead e2_pane_change_dir (curr_pane, curr_pane->path); e2_pane_change_dir (other_pane, other_pane->path); } } //aliases if ((buildflags & E2_OPTION_FLAG_BUILDALIAS) && e2_option_bool_get ("command-use-aliases")) e2_alias_sync (&app.aliases); //filetypes if (buildflags & E2_OPTION_FLAG_BUILDFILES) e2_filetype_apply_allnew (); if (!apply) //if this is an 'ok' button response { e2_option_disable_config_checks (); //update the stored config data e2_option_file_write (NULL); //update stamp, to prevent auto-refresh e2_fs_touch_config_dir (); e2_option_enable_config_checks (); //may need to show the revised config file data /* FIXME this may cause crash if (!(buildflags & (E2_OPTION_FLAG_BUILDALL | E2_OPTION_FLAG_BUILDPANES))) { #ifdef E2_FAM e2_filelist_request_refresh (curr_view->dir, FALSE); e2_filelist_request_refresh (other_view->dir, TRUE); #else e2_filelist_check_dirty (GINT_TO_POINTER(1)); #endif } */ } //set filelist monitoring according to current option if (e2_option_bool_get ("auto-refresh")) { if (app.timers[DIRTYCHECK_T] == 0) { e2_filelist_start_refresh_polling (); #ifdef E2_FAM //e2_fs_FAM_connect (); //CHECKME what things to start monitoring ? //e2_fs_FAM_change (gchar *olddir, E2_PaneRuntime *rt); #endif } } else if (app.timers[DIRTYCHECK_T] != 0) { g_source_remove (app.timers[DIRTYCHECK_T]); app.timers[DIRTYCHECK_T] = 0; #ifdef E2_FAM //FIXME don't want FAM reports to pile up due to lack of polling //e2_fs_FAM_cancel_monitor_dir (gchar *path) //e2_fs_FAM_clean_reports (gchar *path) #endif } //after config file update, set config monitoring according to current option e2_option_enable_config_checks (); e2_filelist_enable_refresh (); //clear settings changed at start of func return TRUE; } /** @brief process a cancellation request @return TRUE always */ static void _e2_confdlg_cancel_cb (void) { printd (DEBUG, "config dialog cancel cb"); if (config_dialog != NULL) { //NOTE this triggers an "edited" callback for any //treeview cell that is being edited at this time ! _e2_confdlg_clean (); //revert all option trees to their last-saved state, //after the clean e2_option_tree_restore_all (); // gtk_widget_grab_focus (curr_view->treeview); } } /** @brief determine what action to take after a dialog button click @param widget the dialog where the response was generated @param response the response assigned to @a button @param rt pointer to data struct for the dialog @return */ static void _e2_confdlg_response_cb (GtkDialog *dialog, gint response, E2_ConfigDialogRuntime *rt) { switch (response) { case GTK_RESPONSE_APPLY: _e2_confdlg_ok_cb (TRUE); break; case GTK_RESPONSE_OK: _e2_confdlg_ok_cb (FALSE); DEALLOCATE (E2_ConfigDialogRuntime, rt); break; case E2_RESPONSE_USER1: //revert to default options { DialogButtons choice = e2_dialog_warning ( _("Reverting to default configuration cannot be undone")); if (choice == OK) { //dump old data, don't re-read the config file, recreate screen // _e2_confdlg_cancel_cb (); //must kill dialog before clearing option data // WAIT_FOR_EVENTS //visually better to repaint screen _e2_confdlg_clean (); //MUST kill dialog before clearing option data e2_option_refresh (FALSE, TRUE); DEALLOCATE (E2_ConfigDialogRuntime, rt); WAIT_FOR_EVENTS return e2_config_dialog_create (NULL); } } break; case E2_RESPONSE_USER2: //toggle between advanced & basic //same response for basic & advanced buttons - only 1 is displayed e2_option_bool_toggle ("advanced-config"); _e2_confdlg_clean (); DEALLOCATE (E2_ConfigDialogRuntime, rt); e2_config_dialog_create (NULL); break; default: _e2_confdlg_cancel_cb (); DEALLOCATE (E2_ConfigDialogRuntime, rt); break; } return; } /** @brief close the dialog if the esc key is pressed @param widget UNUSED the focused widget when key was pressed @param event ptr to event data struct @param data UNUSED ptr to data specified when callback was connected @return TRUE if esc key was pressed */ /* key processing is done in the dialog's "negative response" process gboolean e2_confdlg_key_press_cb (GtkWidget *widget, GdkEventKey *event, gpointer data) { // if (event->keyval == GDK_Return) return activates any focused button // return e2_confdlg_ok_cb (FALSE); if (event->keyval == GDK_Escape) { _e2_confdlg_cancel_cb (); return TRUE; } return FALSE; } */ //this flag is ok because only 1 config dialog, and only 1 edit, at any time static gboolean cancel_blocked = FALSE; /** @brief allow press to cancel editing of the entry text without closing the dialog @param widget UNUSED the focused widget when key was pressed @param event ptr to event data struct @param set ptr to option data @return TRUE if key was pressed */ static gboolean _e2_confdlg_key_press_cb (GtkWidget *widget, GdkEventKey *event, E2_OptionSet *set) { if (!cancel_blocked) { g_signal_handlers_block_by_func (G_OBJECT (config_dialog), e2_dialog_key_neg_cb, config_dialog); cancel_blocked = TRUE; } else if (event->keyval == GDK_Escape || event->keyval == GDK_Return) { if (event->keyval == GDK_Escape) { gtk_entry_set_text (GTK_ENTRY (set->widget), set->sval); gtk_editable_set_position (GTK_EDITABLE (set->widget), -1); } //revert key handling when cell editing is finsished g_signal_handlers_unblock_by_func (G_OBJECT (config_dialog), e2_dialog_key_neg_cb, config_dialog); cancel_blocked = FALSE; return (event->keyval == GDK_Escape); } return FALSE; } /** @brief cancel localised blocking This is a callback for entry "focus-out-event", @param widget UNUSED newly-departed widget or NULL @param event UNUSED pointer to event data struct @param data UNUSED data specified when callback was connected @return FALSE to propagate the event to other handlers */ static gboolean _e2_confdlg_focus_out_cb (GtkWidget *widget, GdkEventFocus *event, gpointer data) { if (cancel_blocked) { g_signal_handlers_unblock_by_func (G_OBJECT (config_dialog), e2_dialog_key_neg_cb, config_dialog); cancel_blocked = FALSE; } return FALSE; } /** @brief show categories context menu if right-button pressed @param treeview the categories treeview widget @param event ptr to event data struct @param data UNUSED ptr to data specified when callback was connected @return TRUE for a right-button press */ static gboolean _e2_confdlg_button_press_cb (GtkWidget *treeview, GdkEventButton *event, gpointer data) { if (event->button == 3) { GtkWidget *menu = gtk_menu_new (); e2_menu_add (menu, _("_Expand"), GTK_STOCK_ZOOM_IN, _("Expand all rows"), e2_tree_expand_all_cb, treeview); e2_menu_add (menu, _("C_ollapse"), GTK_STOCK_ZOOM_OUT, _("Collapse all rows"), e2_tree_collapse_all_cb, treeview); e2_menu_popup (menu, 3, event->time); return TRUE; } return FALSE; } /** @brief response callback for plugin selection dialog @param dialog the dialog where the response was triggered @param response the response assigned to the activated button widget @param data ptr to plugins option data struct @return */ static void _e2_confdlg_plugpick_response_cb (GtkDialog *dialog, gint response, E2_TreeCfgData *data) { gchar *plocal, *local, *utf, *utfp; switch (response) { case E2_RESPONSE_USER1: //toggle hidden items display break; case GTK_RESPONSE_OK: case GTK_RESPONSE_APPLY: //returns localized (not utf) string, no trailer unless the user deliberately enters one plocal = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); if (plocal == NULL) { //conversion error //probably should warn the user break; } Plugin *p = e2_plugins_open1 (plocal); if (p != NULL) { //get plugin UI data only p->action = GINT_TO_POINTER (1); //non-NULL prevents full initialisation p->plugin_init (p); const gchar *menu_name = (p->menu_name != NULL) ? p->menu_name : ""; const gchar *icon = (p->icon != NULL) ? p->icon : ""; const gchar *description = (p->description != NULL) ? p->description : NULL; local = strrchr (plocal, G_DIR_SEPARATOR); //should never fail *local = '\0'; //truncate plocal at trailing / if (g_str_equal (plocal, PLUGINS_DIR)) //from Makefile this is localised, no trailer utfp = ""; else utfp = F_FILENAME_FROM_LOCALE (plocal); utf = F_FILENAME_FROM_LOCALE (++local); GtkTreeIter iter; gtk_tree_model_get_iter (data->set->ex.tree.model, &iter, data->tpath); gtk_tree_store_set (GTK_TREE_STORE (data->set->ex.tree.model), &iter, 1, TRUE, 2, menu_name, 3, icon, 4, description, 5, utf, 6, utfp, -1); p->show_in_menu = TRUE; //for child-rows data if (p->child_list != NULL) { //process child UI data into config store and cleanup e2_plugins_store_child_data (data->set->ex.tree.model, &iter, p); gtk_tree_view_expand_to_path (GTK_TREE_VIEW (data->set->widget), data->tpath); } e2_option_tree_flag_change (data->set); F_FREE (utf); if (*utfp != '\0') F_FREE (utfp); DEALLOCATE (Plugin, p); //no leaks provided all embedded strings are constant } g_free (plocal); if (response == GTK_RESPONSE_APPLY) { break; } // case GTK_RESPONSE_CANCEL: default: gtk_widget_destroy (GTK_WIDGET (dialog)); gtk_tree_path_free (data->tpath); gtk_widget_grab_focus (data->set->widget); DEMALLOCATE (E2_TreeCfgData, data); //too small for slice break; } } /** @brief bring up a system find-file window to choose a plugin @param button activated widget, UNUSED @param set ptr to plugins option data struct @return */ void e2_confdlg_choose_plugin_cb (GtkWidget *button, E2_OptionSet *set) { GtkTreeView *tvw = GTK_TREE_VIEW (set->widget); GtkTreePath *tpath; gtk_tree_view_get_cursor (tvw, &tpath, NULL); if (tpath == NULL) return; E2_TreeCfgData *data = MALLOCATE (E2_TreeCfgData); //too small for slice CHECKALLOCATEDWARN (data, return;); data->set = set; data->tpath = tpath; GtkTreeIter iter; gchar *pname, *ppath; gtk_tree_model_get_iter (set->ex.tree.model, &iter, tpath); gtk_tree_model_get (set->ex.tree.model, &iter, 5, &pname, 6, &ppath, -1); if (*pname == '\0') { g_free (ppath); ppath = NULL; //we'll just set the dir in the opened dialog } else { if (*ppath == '\0') { g_free (ppath); ppath = g_strconcat (PLUGINS_DIR G_DIR_SEPARATOR_S, pname, NULL); //mixed ascii & utf } else { gchar *freeme = ppath; ppath = g_build_filename (ppath, pname, NULL); g_free (freeme); } } g_free (pname); //no need for vfs support, local plugins only GtkWidget *dialog = gtk_file_chooser_dialog_new (NULL, GTK_WINDOW (config_dialog), GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_APPLY, GTK_RESPONSE_APPLY, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); e2_dialog_setup_chooser (dialog, _("choose plugin"), ppath, //full path or NULL GTK_FILE_CHOOSER_ACTION_OPEN, TRUE, //show hidden FALSE, //single-selection GTK_RESPONSE_OK); //default response if (ppath == NULL) gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), PLUGINS_DIR);//localised else g_free (ppath); GtkFileFilter *filter = gtk_file_filter_new (); gtk_file_filter_set_name (GTK_FILE_FILTER (filter), _("plugin")); gtk_file_filter_add_pattern (filter, "e2p*.so"); gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter); /* //hide the dialog's standard 'open' button GtkContainer *bbox = GTK_CONTAINER (GTK_DIALOG (dialog)->action_area); GList* children = gtk_container_get_children (bbox); GtkWidget *btn = children->data; gtk_widget_hide (btn); g_list_free (children); //add 2 buttons that we want e2_dialog_add_defined_button (dialog, &E2_BUTTON_APPLY); e2_dialog_add_defined_button (dialog, &E2_BUTTON_OK); */ g_signal_connect (G_OBJECT (dialog), "response", G_CALLBACK (_e2_confdlg_plugpick_response_cb), data); e2_dialog_setup (dialog, config_dialog); gtk_widget_show (dialog); } /**************************/ /***** font callbacks *****/ /**************************/ /** @brief accept font selection @param button @param entry @return */ static void _e2_confdlg_font_select_ok_cb (GtkWidget *button, GtkWidget *entry) { GtkWidget *dialog = g_object_get_data (G_OBJECT (button), "dialog_widget"); const gchar *title = gtk_window_get_title (&(GTK_DIALOG (dialog)->window)); gchar *font_str = gtk_font_selection_dialog_get_font_name (GTK_FONT_SELECTION_DIALOG (dialog)); gtk_entry_set_text (GTK_ENTRY (entry), font_str); g_free (font_str); g_object_set_data (G_OBJECT (config_dialog), title, NULL); } /** @brief cancel font selection @param button UNUSED the activated button widget @param dialog the font-selection dialog widget @return */ static void _e2_confdlg_font_select_cancel_cb (GtkWidget *button, GtkWidget *dialog) { const gchar *title = gtk_window_get_title (&(GTK_DIALOG (dialog)->window)); g_object_set_data (G_OBJECT (config_dialog), title, NULL); } /** @brief create font-selection dialog @param button the activated button widget @param set pointer to data struct for the option to be altered @return */ static void _e2_confdlg_font_select_cb (GtkWidget *button, E2_OptionSet *set) { gchar *title = g_strdup_printf (_("Choose font: %s"), set->desc); GtkFontSelectionDialog *dialog = g_object_get_data (G_OBJECT (config_dialog), title); if (dialog != NULL) gtk_window_present (GTK_WINDOW (dialog)); else { //CHECKME the normal dialog properties like close on Esc press ? dialog = GTK_FONT_SELECTION_DIALOG (gtk_font_selection_dialog_new (title)); gtk_widget_destroy (dialog->apply_button); g_object_set_data (G_OBJECT (dialog->ok_button), "dialog_widget", GTK_WIDGET (dialog)); gtk_font_selection_dialog_set_font_name (dialog, gtk_entry_get_text (GTK_ENTRY (set->widget))); #ifdef E2_COMPOSIT e2_window_set_opacity (GTK_WIDGET (dialog), DIALOG_OPACITY_LEVEL); //constant opacity for dialogs #endif g_signal_connect (G_OBJECT (dialog->ok_button), "clicked", G_CALLBACK (_e2_confdlg_font_select_ok_cb), set->widget); g_signal_connect (G_OBJECT (dialog->cancel_button), "clicked", G_CALLBACK (_e2_confdlg_font_select_cancel_cb), dialog); g_signal_connect (G_OBJECT (dialog), "delete-event", G_CALLBACK (_e2_confdlg_font_select_cancel_cb), NULL); g_object_set_data_full (G_OBJECT (config_dialog), title, dialog, (void *) gtk_widget_destroy); e2_dialog_show (GTK_WIDGET (dialog), config_dialog, 0, NULL); } g_free (title); } /** @brief update font example @param entry widget containing name of font @param label label widget to be updated with example of the named font @return */ static void _e2_confdlg_font_entry_cb (GtkEditable *entry, GtkWidget *label) { gchar *font_str = gtk_editable_get_chars (entry, 0, -1); if ((font_str != NULL) && (*font_str != '\0') && (strlen (font_str) > 2)) { gchar *label_text = g_strdup_printf ("\t%s %s", _("example:"), font_str, _("abcd efgh ABCD EFGH")); gtk_label_set_markup (GTK_LABEL (label), label_text); g_free (label_text); } g_free (font_str); } /*************************/ /**** color callbacks ****/ /*************************/ #ifdef E2_RAINBOW /** @brief response callback for extension-color selection dialog @param dialog the dialog where the response was triggered @param response the response assigned to the activated button widget @param set ptr to plugins option data struct @return */ static void _e2_confdlg_colorpick_response_cb (GtkDialog *dialog, gint response, E2_TreeCfgData *data) { GdkColor color; GtkTreeIter iter; switch (response) { case GTK_RESPONSE_OK: case GTK_RESPONSE_APPLY: gtk_color_selection_get_current_color (GTK_COLOR_SELECTION (GTK_COLOR_SELECTION_DIALOG (dialog)->colorsel), &color); gchar *color_str = e2_utils_color2str (&color); gtk_tree_model_get_iter (data->set->ex.tree.model, &iter, data->tpath); gtk_tree_store_set (GTK_TREE_STORE (data->set->ex.tree.model), &iter, 2, color_str, -1); e2_option_tree_flag_change (data->set); g_free (color_str); // case GTK_RESPONSE_CANCEL: default: gtk_widget_destroy (GTK_WIDGET (dialog)); gtk_tree_path_free (data->tpath); g_free (data); break; } } /** @brief callback for color-selection button in filetypes page Color strings are stored in col 3 of rows with extensions title (path depth 2) or a row with a specific extension (depth 3) @param button UNUSED the color button widget @param set pointer to filetypes option data @return */ void e2_confdlg_extcolorpick_cb (GtkWidget *button, E2_OptionSet *set) { GtkTreeView *tvw = GTK_TREE_VIEW (set->widget); GtkTreePath *path; gtk_tree_view_get_cursor (tvw, &path, NULL); if (path == NULL) return; GtkTreeIter iter; gchar *current; gboolean valid = FALSE; gint depth = gtk_tree_path_get_depth (path); switch (depth) { case 2: //we're at an extensions/commands heading line gtk_tree_model_get_iter (set->ex.tree.model, &iter, path); gtk_tree_model_get (set->ex.tree.model, &iter, 1, ¤t, -1); //extensions node is ok if (g_str_equal (current, _C(13))) valid = TRUE; g_free (current); break; case 3: //we're at an extension or command line { GtkTreePath *nodepath = gtk_tree_path_copy (path); gtk_tree_path_up (nodepath); gtk_tree_model_get_iter (set->ex.tree.model, &iter, nodepath); gtk_tree_path_free (nodepath); gtk_tree_model_get (set->ex.tree.model, &iter, 1, ¤t, -1); if (g_str_equal (current, _C(13))) valid = TRUE; g_free (current); } break; default: break; } if (!valid) { e2_output_print_error (_("Color data are not stored there"), FALSE); gtk_tree_path_free (path); return; } //FIXME use ref in case user edits model // GtkTreeRowReference *ref = gtk_tree_row_reference_new (set->ex.tree.model, path); E2_TreeCfgData *data = MALLOCATE (E2_TreeCfgData); //too small for slice CHECKALLOCATEDWARN (data, return;) data->set = set; data->tpath = path; gtk_tree_model_get_iter (set->ex.tree.model, &iter, path); gtk_tree_model_get (set->ex.tree.model, &iter, 2, ¤t, -1); GdkColor color; if (*current == '\0') gdk_color_parse ("black", &color); else if (!gdk_color_parse (current, &color)) { e2_output_print_error (_("The current color descriptor is not valid"), FALSE); gdk_color_parse ("black", &color); } g_free (current); //CHECKME the normal dialog properties like close on Esc press ? GtkWidget *dialog = gtk_color_selection_dialog_new (_("Set filetype color")); gtk_color_selection_set_current_color (GTK_COLOR_SELECTION (GTK_COLOR_SELECTION_DIALOG (dialog)->colorsel), &color); #ifdef E2_COMPOSIT e2_window_set_opacity (dialog, DIALOG_OPACITY_LEVEL); //constant opacity for dialogs #endif e2_dialog_add_defined_button (dialog, &E2_BUTTON_APPLY); g_signal_connect (G_OBJECT (dialog), "response", G_CALLBACK (_e2_confdlg_colorpick_response_cb), data); e2_dialog_setup (dialog, config_dialog); gtk_widget_show (dialog); } #endif /** @brief @param button the clicked button widget @param entry @return */ static void _e2_confdlg_color_select_ok_cb (GtkWidget *button, GtkWidget *entry) { GtkWidget *dialog = button->parent->parent->parent; //FIXME bad const gchar *title = gtk_window_get_title (&(GTK_DIALOG (dialog)->window)); GdkColor color; gtk_color_selection_get_current_color (GTK_COLOR_SELECTION (GTK_COLOR_SELECTION_DIALOG (dialog)->colorsel), &color); gchar *color_str = e2_utils_color2str (&color); gtk_entry_set_text (GTK_ENTRY (entry), color_str); g_free (color_str); g_object_set_data (G_OBJECT (config_dialog), title, NULL); } /** @brief @param button the clicked button widget @param dialog @return */ static void _e2_confdlg_color_select_cancel_cb (GtkWidget *button, GtkWidget *dialog) { const gchar *title = gtk_window_get_title (&(GTK_DIALOG (dialog)->window)); g_object_set_data (G_OBJECT (config_dialog), title, NULL); } /** @brief callback for color-selection button in non-filetypes page @param button the clicked color-button widget @param set pointer to data for the relevant config option @return */ static void _e2_confdlg_color_select_cb (GtkWidget *button, E2_OptionSet *set) { gchar *title = g_strdup_printf (_("Choose color: %s"), set->desc); GtkColorSelectionDialog *dialog = g_object_get_data (G_OBJECT (config_dialog), title); if (dialog != NULL) gtk_window_present (GTK_WINDOW (dialog)); else { //CHECKME close on Esc press ? dialog = GTK_COLOR_SELECTION_DIALOG (gtk_color_selection_dialog_new (title)); gtk_widget_destroy (dialog->help_button); GdkColor color; gchar *color_str = gtk_editable_get_chars (GTK_EDITABLE (set->widget), 0, -1); gdk_color_parse (color_str, &color); gtk_color_selection_set_current_color (GTK_COLOR_SELECTION (dialog->colorsel), &color); g_free (color_str); g_signal_connect (G_OBJECT (dialog->ok_button), "clicked", G_CALLBACK (_e2_confdlg_color_select_ok_cb), set->widget); g_signal_connect (G_OBJECT (dialog->cancel_button), "clicked", G_CALLBACK (_e2_confdlg_color_select_cancel_cb), dialog); g_signal_connect (G_OBJECT (dialog), "delete-event", G_CALLBACK (_e2_confdlg_color_select_cancel_cb), dialog); g_object_set_data_full (G_OBJECT (config_dialog), title, dialog, (void *) gtk_widget_destroy); gtk_window_set_resizable (GTK_WINDOW (dialog), TRUE); e2_dialog_show (GTK_WIDGET (dialog), config_dialog, 0, NULL); } g_free (title); } /** @brief @param entry @param label @return */ static void _e2_confdlg_color_entry_cb (GtkEditable *entry, GtkWidget *label) { gchar *color_str = gtk_editable_get_chars (entry, 0, -1); GdkColor color; if (gdk_color_parse (color_str, &color)) { gchar *label_text = g_strdup_printf ("\t%s " "%s " "%s %s", _("currently:"), color_str, color_str, _("abCD"), color_str, _("abCD"), color_str); gtk_label_set_markup (GTK_LABEL (label), label_text); g_free (label_text); } g_free (color_str); } /** @brief process a click on an item in the categories treeview @param selection selection for the categories treeview @param rt pointer to data struct for the dialog @return TRUE if there is a selected category */ static gboolean _e2_confdlg_category_selected_cb (GtkTreeSelection *selection, E2_ConfigDialogRuntime *rt) { printd (DEBUG, "callback: category selected"); GtkTreeIter iter; GtkTreeModel *model; if (gtk_tree_selection_get_selected (selection, &model, &iter)) { g_free (page_last_name); gint page; gtk_tree_model_get (model, &iter, 0, &page_last_name, 1, &page, -1); gtk_notebook_set_current_page (rt->notebook, page); gtk_widget_grab_focus (rt->treeview); return TRUE; } return FALSE; } /** @brief setup an entry widget for a set on a dialog page @param box box widget to hold the entry @param set pointer to set data @return */ static void _e2_confdlg_set_entry (GtkWidget *box, E2_OptionSet *set) { set->widget = e2_widget_add_entry (box, set->sval, TRUE, FALSE); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif set->widget, set->tip); //arrange localised handling of key presses g_signal_connect (G_OBJECT (GTK_ENTRY (set->widget)), "key-press-event", G_CALLBACK (_e2_confdlg_key_press_cb), set); g_signal_connect (G_OBJECT (GTK_ENTRY (set->widget)), "focus-out-event", G_CALLBACK (_e2_confdlg_focus_out_cb), NULL); } /** @brief add a line to the categories treeview, and a page to the options notebook Expects page_last_name to be non-NULL (even if wrong, when changing to basic config) @param parent_iter pointer to iter in categories treeview, can be NULL for a new category @param label_text name of the category @param with_sw TRUE to put the data into a scrolled window @param rt pointer to data struct for the dialog @return a vbox widget */ static GtkWidget *_e2_confdlg_add_page (GtkTreeIter *parent_iter, gchar *label_text, #ifdef NEWSCROLL gboolean with_sw, #endif E2_ConfigDialogRuntime *rt) { //add page to the treeview model GtkTreeIter child_iter; gint page_num = gtk_notebook_get_n_pages (rt->notebook); #ifdef USE_GTK2_10 gtk_tree_store_insert_with_values (rt->store, &child_iter, parent_iter, -1, #else gtk_tree_store_append (rt->store, &child_iter, parent_iter); gtk_tree_store_set (rt->store, &child_iter, #endif 0, label_text, 1, page_num, -1); //is this one the one nominated to open at startup? if (g_str_equal (page_last_name, label_text)) { rt->openpath = gtk_tree_model_get_path (GTK_TREE_MODEL (rt->store), &child_iter); rt->openpage = page_num; } GtkWidget *vbox; #ifdef NEWSCROLL if (with_sw) { GtkWidget *sw = e2_widget_get_sw (GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, GTK_SHADOW_NONE); vbox = gtk_vbox_new (FALSE, E2_PADDING); e2_widget_sw_add_with_viewport (sw, vbox); gtk_widget_show_all (sw); gtk_notebook_append_page (rt->notebook, sw, NULL); } else { #endif //page vbox containing the headline and config widgets vbox = gtk_vbox_new (FALSE, E2_PADDING); gtk_widget_show (vbox); gtk_notebook_append_page (rt->notebook, vbox, NULL); #ifdef NEWSCROLL } #endif g_object_set_data_full (G_OBJECT (vbox), "iter", gtk_tree_iter_copy (&child_iter), (void *) gtk_tree_iter_free); gtk_container_set_border_width (GTK_CONTAINER (vbox), E2_PADDING); //page headline GtkWidget *frame = e2_widget_add_frame (vbox, FALSE, 0, NULL, FALSE); gchar *label_text2 = g_strconcat ("", label_text, "", NULL); GtkWidget *label = e2_widget_add_mid_label (NULL, label_text2, 0.5, FALSE, 0); g_free (label_text2); gtk_misc_set_padding (GTK_MISC (label), E2_PADDING_XSMALL, E2_PADDING_XSMALL); gtk_container_add (GTK_CONTAINER (frame), label); return vbox; } /** @brief get notebook page for option group @a group, after creating it if necessary Recursive if @a group is a child (name includes '.') or is a grandchild (name includes ':') (and in those cases, it alters the set->group temporarily) This also sets page_last_name, if it was NULL @param set pointer to set data struct @param rt pointer to data struct for the dialog @return a vbox widget into which option data can be packed */ static GtkWidget *_e2_confdlg_get_page (E2_OptionSet *set, E2_ConfigDialogRuntime *rt) { GtkWidget *box; if ((box = (GtkWidget *) g_hash_table_lookup (rt->opthash, set->group)) == NULL) { gchar *frame = strrchr (set->group, ':'); //always ascii :, don't need g_utf8_strrchr() if (frame != NULL) { *frame = '\0'; GtkWidget *frame_box; if ((frame_box = (GtkWidget *) g_hash_table_lookup (rt->opthash, set->group)) == NULL) frame_box = _e2_confdlg_get_page (set, rt); //recurse with altered set->group gchar *frame_title = e2_utils_str_stretch (frame + 1); GtkWidget *frame_widget = gtk_frame_new (frame_title); g_free (frame_title); GtkWidget *vbox = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (frame_widget), vbox); gtk_container_set_border_width (GTK_CONTAINER (vbox), E2_PADDING_SMALL); gtk_box_pack_start (GTK_BOX (frame_box), frame_widget, FALSE, FALSE, 0); box = vbox; *frame = ':'; } else { gchar *child = strrchr (set->group, '.'); //always ascii '.', don't need g_utf8_strrchr() if (child != NULL) { *child = '\0'; box = _e2_confdlg_get_page (set, rt); //recurse with altered set->group if (page_last_name == NULL) //we didn't have a page-name from last time or earlier this time page_last_name = g_strdup (set->group); GtkTreeIter *iter2; iter2 = g_object_get_data (G_OBJECT (box), "iter"); if (iter2) box = _e2_confdlg_add_page (iter2, child + 1, #ifdef NEWSCROLL set->type != E2_OPTION_TYPE_TREE, #endif rt); *child = '.'; } else //this is a group without a descendant { if (page_last_name == NULL) //we didn't have a page-name from last time or earlier this time page_last_name = g_strdup (set->group); box = _e2_confdlg_add_page (NULL, set->group, #ifdef NEWSCROLL set->type != E2_OPTION_TYPE_TREE, #endif rt); } } g_hash_table_insert (rt->opthash, g_strdup(set->group), box); } return box; } /** @brief create and show configuration dialog @param page name of page to show when dialog is started, or "" or NULL to use the last-used page @return */ void e2_config_dialog_create (gchar *page) { printd (DEBUG, "create config dialog (%s)", page); //check if there is already a config dialog opened if (config_dialog != NULL) { gtk_window_present (GTK_WINDOW (config_dialog)); return; } E2_ConfigDialogRuntime *rt = ALLOCATE (E2_ConfigDialogRuntime); CHECKALLOCATEDWARN (rt, return); //ensure we open at the start, if there's no matching page now //i.e. after conversion to basic config, when on a page now hidden rt->openpage = 0; rt->openpath = NULL; config_dialog = e2_dialog_create (NULL, NULL, _("configuration"), _e2_confdlg_response_cb, rt); e2_option_connect (config_dialog, FALSE); //the main hpane - contains the categories treeview and the frame GtkWidget *hpane = gtk_hpaned_new (); gtk_container_add (GTK_CONTAINER (GTK_DIALOG (config_dialog)->vbox), hpane); //scrolled window for the categories treeview GtkWidget *sw = e2_widget_get_sw (GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, GTK_SHADOW_IN); gtk_widget_set_size_request (sw, 200, 0); gtk_paned_pack1 (GTK_PANED (hpane), sw, FALSE, TRUE); //categories treestore has single node, 2 columns //for (displayed) name and (hidden) enumerator/index rt->store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_INT); //categories tree view GtkWidget *catsview; //the categories treeview rt->treeview = catsview = gtk_tree_view_new_with_model (GTK_TREE_MODEL (rt->store)); g_object_unref (rt->store); gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (catsview), TRUE); //#ifdef USE_GTK2_10 // gtk_tree_view_set_enable_tree_lines (GTK_TREE_VIEW (catstore), TRUE); //#endif GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (catsview)); gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); GtkCellRenderer *renderer = gtk_cell_renderer_text_new (); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (catsview), -1, _("Categories"), renderer, "text", 0, NULL); // GtkTreeViewColumn *column = gtk_tree_view_get_column (GTK_TREE_VIEW // (treeview), 0); // gtk_tree_view_column_set_clickable (GTK_TREE_VIEW_COLUMN (column), FALSE); gtk_container_add (GTK_CONTAINER (sw), catsview); //the notebook rt->notebook = GTK_NOTEBOOK (gtk_notebook_new ()); gtk_notebook_set_show_tabs (rt->notebook, FALSE); gtk_notebook_set_show_border (rt->notebook, FALSE); #ifdef NEWSCROLL //in this case, each page has its own sw gtk_paned_pack2 (GTK_PANED (hpane), GTK_WIDGET(rt->notebook), TRUE, TRUE); gtk_widget_set_size_request (GTK_WIDGET(rt->notebook), 10, 10); #else sw = e2_widget_get_sw_plain (GTK_POLICY_NEVER, GTK_POLICY_NEVER); e2_widget_sw_add_with_viewport (sw, GTK_WIDGET (rt->notebook)); gtk_paned_pack2 (GTK_PANED (hpane), sw, TRUE, TRUE); #endif //note which page we want to open at if (page != NULL && *page != '\0') { if (page_last_name != NULL) g_free (page_last_name); page_last_name = g_strdup (page); } /*if page_last_name is still NULL (i.e. when dialog is first run) that string is set when the first page is added to the notebook*/ //iterate through the options, setting up category tree and page contents E2_OptionFlags includemask = (e2_option_bool_get ("advanced-config")) ? E2_OPTION_FLAG_BASIC | E2_OPTION_FLAG_ADVANCED : E2_OPTION_FLAG_BASIC | E2_OPTION_FLAG_BASICONLY ; rt->opthash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)_e2_hashfree); guint i; gpointer *walker; E2_OptionSet *set; for (i = 0, walker = options_array->pdata; i < options_array->len; i++, walker++) { GtkSizeGroup *size_group; set = *walker; if (!(set->flags & includemask)) { set->widget = NULL; //make sure that this is ignored when updating after 'ok' or 'apply' continue; } if (set->type != E2_OPTION_TYPE_TREE) { //make sure there's a size group for the page gchar *size_group_key = g_strconcat (set->group, "_sizegroup", NULL); //no translation size_group = (GtkSizeGroup *) g_hash_table_lookup (rt->opthash, size_group_key); if (GTK_IS_SIZE_GROUP (size_group)) g_free (size_group_key); else { size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); g_hash_table_insert (rt->opthash, size_group_key, size_group); } } else size_group = NULL; //warning prevention only //get page for this set //and if necessary, create categories view item(s) and //notebook page(s) for parent group and any child(ren) GtkWidget *box = _e2_confdlg_get_page (set, rt); //FIXME create only the the startup page now, rest after display GtkWidget *hbox, *label; //, *button; gchar *label_text; switch (set->type) { case E2_OPTION_TYPE_TREE: e2_option_tree_add_widget (config_dialog, box, set); break; case E2_OPTION_TYPE_BOOL: //button = e2_widget_add_tied_check_button (box, set, config_dialog); //CHECKME size_group needed ? //gtk_size_group_add_widget (size_group, button); break; case E2_OPTION_TYPE_STR: hbox = gtk_hbox_new (FALSE, E2_PADDING); gtk_box_pack_start (GTK_BOX (box), hbox, TRUE, TRUE, E2_PADDING); label = e2_widget_add_mid_label (hbox, set->desc, 0.0, FALSE, 0); gtk_size_group_add_widget (size_group, label); _e2_confdlg_set_entry (hbox, set); #ifdef E2_ASSISTED e2_widget_set_label_relations (GTK_LABEL (label), set->widget); #endif break; case E2_OPTION_TYPE_INT: e2_option_int_add_widget (config_dialog, box, size_group, set); break; case E2_OPTION_TYPE_SEL: e2_option_sel_add_widget (config_dialog, box, size_group, set); break; case E2_OPTION_TYPE_FONT: //hbox for option label, font entry and font dialog button hbox = gtk_hbox_new (FALSE, E2_PADDING_SMALL); gtk_box_pack_start (GTK_BOX (box), hbox, TRUE, TRUE, E2_PADDING_SMALL); //option label label_text = g_strconcat (set->desc, ": ", NULL); label = e2_widget_add_mid_label (hbox, label_text, 0.0, FALSE, E2_PADDING_SMALL); g_free (label_text); gtk_size_group_add_widget (size_group, label); //font entry for font name _e2_confdlg_set_entry (hbox, set); #ifdef E2_ASSISTED e2_widget_set_label_relations (GTK_LABEL (label), set->widget); #endif gtk_entry_set_text (GTK_ENTRY (set->widget), set->sval); //button for font select dialog e2_button_add (hbox, FALSE, 0, _("change"), GTK_STOCK_SELECT_FONT, _("Click to open a font select dialog"), _e2_confdlg_font_select_cb, set); //label for font example label = e2_widget_add_mid_label (box, "", 0.0, FALSE, 0); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.0); //set label contents _e2_confdlg_font_entry_cb (GTK_EDITABLE (set->widget), label); //react on changes to the font name g_signal_connect (GTK_EDITABLE (set->widget), "changed", G_CALLBACK (_e2_confdlg_font_entry_cb), label); break; case E2_OPTION_TYPE_COLOR: //hbox for option label, color entry and color dialog button hbox = gtk_hbox_new (FALSE, E2_PADDING_SMALL); gtk_box_pack_start (GTK_BOX (box), hbox, TRUE, TRUE, E2_PADDING_SMALL); //option label label_text = g_strconcat (set->desc, ": ", NULL); label = e2_widget_add_mid_label (hbox, label_text, 0.0, FALSE, E2_PADDING_SMALL); g_free (label_text); gtk_size_group_add_widget (size_group, label); //color entry for color names/color hex values _e2_confdlg_set_entry (hbox, set); #ifdef E2_ASSISTED e2_widget_set_label_relations (GTK_LABEL (label), set->widget); #endif gtk_entry_set_text (GTK_ENTRY (set->widget), set->sval); //button for color select dialog e2_button_add (hbox, FALSE, 0, _("change"), GTK_STOCK_SELECT_COLOR, _("Click to open a color selection dialog"), _e2_confdlg_color_select_cb, set); //label for color example label = e2_widget_add_mid_label (box, "", 0.0, FALSE, 0); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.0); //set label contents _e2_confdlg_color_entry_cb (GTK_EDITABLE (set->widget), label); //react on changes to the color name/color hex value g_signal_connect (GTK_EDITABLE (set->widget), "changed", G_CALLBACK (_e2_confdlg_color_entry_cb), label); break; default: break; } } /*a set's dependency (if any) may be 'forward' in the array, in which case the relevant widget not known when the set was processed in the previous walk, so need to setup dependencies after all options are setup i.e. iterate again ... FIXME in pass 1, log the items with un-resolved dependencies, then just do those here */ for (i = 0, walker = options_array->pdata; i < options_array->len; i++, walker++) { set = *walker; if (!(set->flags & includemask)) continue; if (set->depends != NULL) { if (set->depends[0] == '!') { //negated dependency E2_OptionSet *dep = e2_option_get (set->depends + 1); if ((dep != NULL) && (dep->type == E2_OPTION_TYPE_BOOL)) { if (dep->widget != NULL) //dep is in this dialog g_signal_connect (G_OBJECT (dep->widget), "toggled", G_CALLBACK (_e2_confdlg_negdepends_cb), set->widget); gtk_widget_set_sensitive (set->widget, ! e2_option_bool_get_direct (dep)); //if dep is not in the dialog, it will remain in its current state } } else //not negated { E2_OptionSet *dep = e2_option_get (set->depends); if ((dep != NULL) && (dep->type == E2_OPTION_TYPE_BOOL)) { if (dep->widget != NULL) //dep is in this dialog g_signal_connect (G_OBJECT (dep->widget), "toggled", G_CALLBACK (_e2_confdlg_posdepends_cb_), set->widget); gtk_widget_set_sensitive (set->widget, e2_option_bool_get_direct (dep)); } else //always insensitive if dep not boolean if (dep->type != E2_OPTION_TYPE_BOOL) { gtk_widget_set_sensitive (set->widget, FALSE); } //ignore instruction if dep set doesn't exist } } } //clean up g_hash_table_destroy (rt->opthash); //expand the category tree view gtk_tree_view_expand_all (GTK_TREE_VIEW (catsview)); //show and select the startup row GtkTreePath *path = rt->openpath; //when converting from advanced to basic config, we can't //be sure the current page will be displayed if (path != NULL) { gtk_tree_view_set_cursor (GTK_TREE_VIEW (catsview), path, NULL, TRUE); gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (catsview), path, NULL, FALSE, 0, 0); gtk_tree_path_free (path); } //if no cache data from last usage, we don't want an unpleasant size if (app.cfgdlg_width < 10) app.cfgdlg_width = 400; if (app.cfgdlg_height < 10) app.cfgdlg_height = 300; gtk_window_resize (GTK_WINDOW(config_dialog), app.cfgdlg_width, app.cfgdlg_height); //revert to last size if not too small //receptacle for cut/copied row(s) data generated from context menu //the keys are constant strings, not to be replaced or freed tree_view_buffer_hash = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) e2_option_tree_menu_hash_clean); // g_signal_connect (G_OBJECT (config_dialog), "delete-event", handled in response process // G_CALLBACK (_e2_confdlg_cancel_cb), NULL); // g_signal_connect_after (G_OBJECT (config_dialog), "key-press-event", // G_CALLBACK (e2_confdlg_key_press_cb), NULL); // g_signal_connect (G_OBJECT (catsview), "key-press-event", // G_CALLBACK (e2_confdlg_key_press_cb), NULL); g_signal_connect (G_OBJECT (catsview), "button-press-event", G_CALLBACK (_e2_confdlg_button_press_cb), NULL); g_signal_connect (G_OBJECT (selection), "changed", G_CALLBACK (_e2_confdlg_category_selected_cb), rt); //make the notebook open at the correct page //(can't get it to work, if set sooner than in the 'show' cb ...) g_signal_connect (G_OBJECT (config_dialog), "show", G_CALLBACK (_e2_confdlg_show_cb2), rt); //#ifdef NEWSCROLL // g_signal_connect (G_OBJECT (config_dialog), "show", // G_CALLBACK (_e2_confdlg_show_cb), rt->notebook); //#else #ifndef NEWSCROLL g_signal_connect (G_OBJECT (config_dialog), "show", G_CALLBACK (_e2_confdlg_show_cb), sw); #endif e2_dialog_add_undefined_button_custom (config_dialog, FALSE, E2_RESPONSE_USER1, _("_Default"), GTK_STOCK_CLEAR, _("Revert all options to their default settings"), NULL, NULL); if (e2_option_bool_get ("advanced-config")) e2_dialog_add_undefined_button_custom (config_dialog, FALSE, E2_RESPONSE_USER2, _("_Basic"), GTK_STOCK_REDO, _("Display only the basic configuration options"), NULL, NULL); else e2_dialog_add_undefined_button_custom (config_dialog, FALSE, E2_RESPONSE_USER2, _("Ad_vanced"), GTK_STOCK_REDO, _("Display all configuration options"), NULL, NULL); // E2_BUTTON_CANCEL.showflags |= E2_BTN_DEFAULT; no default button E2_BUTTON_OK.showflags &= ~E2_BTN_DEFAULT; e2_dialog_show (config_dialog, app.main_window, 0, &E2_BUTTON_CANCEL, &E2_BUTTON_APPLY, &E2_BUTTON_OK, NULL); } emelfm2-0.4.1/src/dialogs/e2_dialog.c0000600000175000017500000012126610771554360016231 0ustar cairocairo/* $Id: e2_dialog.c 838 2008-03-23 22:25:52Z tpgww $ Copyright (C) 2003-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/dialogs/e2_dialog.c @brief dialog utility functions This file contains dialog utility functions. */ /** \page dialogs dialogs ToDo - describe how dialogs work */ #include "emelfm2.h" #include "e2_dialog.h" #include "e2_option.h" #include "e2_filelist.h" #include static DialogButtons _e2_dialog_line_input (gchar* window_title, gchar *prompt, gchar *suggestion, gboolean select_text, //tag PASSWORDINPUT gboolean hide_text, gchar **input, OW_ButtonFlags extras, gboolean history, GList **history_list); /*********************/ /***** callbacks *****/ /*********************/ /** @brief remove the source for a pending dialog resize @param dialog the affected dialog widget @param data UNUSED data specified when the idle was established @return */ static void _e2_dialog_resize_destroy_cb (GtkWidget *dialog, gpointer data) { gpointer idle_id = g_object_get_data (G_OBJECT (dialog), "e2-dialog-resize-idle-id"); if (idle_id != NULL) { g_source_remove (GPOINTER_TO_UINT(idle_id)); g_object_set_data (G_OBJECT (dialog), "e2-dialog-resize-idle-id", NULL); } } /** @brief idle callback to resize @a dialog @param dialog the widget to be resized @return FALSE always, to remove the idle source */ static gboolean _e2_dialog_resize_idle_cb (GtkWidget *dialog) { if ((dialog->allocation.width != dialog->requisition.width) || (dialog->allocation.height != dialog->requisition.height)) { // GtkPolicyType barpolicy = GTK_POLICY_NEVER; GtkScrolledWindow *scrolled = g_object_get_data (G_OBJECT (dialog), "e2-dialog-resize-scrolled"); gdk_threads_enter (); if (scrolled != NULL) { //turn any scrollbar(s) off, so the resize works as intended //assume both bars have same policy // barpolicy = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (scrolled), "hscrollbar-policy")); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), GTK_POLICY_NEVER, GTK_POLICY_NEVER); WAIT_FOR_EVENTS } gtk_window_resize (GTK_WINDOW (dialog), dialog->requisition.width, dialog->requisition.height); WAIT_FOR_EVENTS //turn bars back on if (scrolled != NULL) //&& barpolicy != GTK_POLICY_NEVER) { gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); //barpolicy, barpolicy); } gdk_threads_leave (); } //record 0 idle id, we're done now g_object_set_data (G_OBJECT (dialog), "e2-dialog-resize-idle-id", NULL); return FALSE; } /** @brief "show" callback for a dialog with scrolled window Turns on automatic scrollbars for @a scrolled @param dialog UNUSED the dialog widget containing @a scrolled @param scrolled scrolled window to be updated @return */ void e2_dialog_show_cb (GtkWidget *dialog, GtkScrolledWindow *scrolled) { gtk_scrolled_window_set_policy (scrolled, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); } /** @brief "show" callback for a dialog with notebook Turns on automatic scrollbars for each page of @a book @param dialog UNUSED the dialog widget containing @a book @param book notebook to be updated @return */ void e2_dialog_show_notebook_cb (GtkWidget *dialog, GtkNotebook *book) { gint i, pagecount = gtk_notebook_get_n_pages (book); for (i = 0; i < pagecount; i++) { GtkWidget *page; GtkScrolledWindow *sw; page = gtk_notebook_get_nth_page (book, i); sw = (GtkScrolledWindow *)g_object_get_data (G_OBJECT (page), "e2-tab-scrolled-window"); if (sw != NULL) gtk_scrolled_window_set_policy (sw, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); } } /** @brief dialog button-press callback, to pop up a context menu @param dialog the widget which received the mouse button press @param event pointer to event data struct @param menu pointer to context menu for the dialog @return TRUE if the event was a right-button click */ static gboolean _e2_dialog_button_press_cb (GtkWidget *dialog, GdkEventButton *event, GtkWidget *menu) { if (event->button == 3) { gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 3, event->time); return TRUE; } return FALSE; } /** @brief handle Return keypresses in a line-input dialog This is convenient because the buttons in the dialog are typically not focused @param entry the entry widget for the combo box @param result pointer to store for result code @return */ static void _e2_dialog_activated_cb (GtkEntry *entry, DialogButtons *dialog_result) { *dialog_result = OK; gtk_main_quit (); } /** @brief default callback to handle responses from @a dialog This is essentially to handle cancellation requests @param dialog the dialog where the response was triggered @param response the response enumerator @param data UNUSED data specified when the callback was connected @return */ static void _e2_dialog_response_cb (GtkDialog *dialog, gint response, gpointer data) { gint negresponse = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (dialog), "e2-negative-response")); if (response == negresponse) // || response == GTK_RESPONSE_DELETE_EVENT) e2_dialog_cancel_cb (NULL, GTK_WIDGET (dialog)); } /******************/ /***** public *****/ /******************/ /** @brief convert a dialog button response to the interface standard This expects a dialog paused with gtk_main() @param dialog UNUSED the dialog where the response was generated @param response the generated response number @param dialog_result ptr to store for the decoded result @return */ void e2_dialog_response_decode_cb (GtkDialog *dialog, gint response, DialogButtons *dialog_result) { switch (response) { case GTK_RESPONSE_OK: case GTK_RESPONSE_YES: case GTK_RESPONSE_APPLY: *dialog_result = OK; break; case GTK_RESPONSE_CANCEL: case GTK_RESPONSE_NO: *dialog_result = CANCEL; break; case E2_RESPONSE_YESTOALL: case E2_RESPONSE_APPLYTOALL: *dialog_result = YES_TO_ALL; break; // case E2_RESPONSE_NOTOALL: default: *dialog_result = NO_TO_ALL; break; } gtk_main_quit (); return; } //these callbacks used for 3 filter dialogs & others /** @brief close dialog @a dialog @param widget the activated button, or NULL after a keypress @param dialog the dialog widget @return TRUE always */ gboolean e2_dialog_cancel_cb (GtkWidget *widget, GtkWidget *dialog) { gtk_widget_destroy (dialog); gtk_widget_set_sensitive (app.main_window, TRUE); // gtk_widget_grab_focus (curr_view->treeview); return TRUE; } /** @brief close dialog @a dialog when Esc key is pressed @param widget the focused widget when the key was pressed @param event pointer to event data struct @param dialog the dialog widget @return TRUE if Esc key was pressed */ gboolean e2_dialog_key_press_cb (GtkWidget *widget, GdkEventKey *event, GtkWidget *dialog) { if (event->keyval == GDK_Escape) return e2_dialog_cancel_cb (NULL, dialog); return FALSE; } /** @brief initiate the assigned 'negative response' for dialog @a data @a data is unused here but is needed when [un]blocking this callback @param widget the dialog widget which received the keypress @param event pointer to event data struct @param data pointer to dialog widget (= widget) @return TRUE if Esc key was pressed */ gboolean e2_dialog_key_neg_cb (GtkWidget *widget, GdkEventKey *event, gpointer data) { // printd (DEBUG, "e2_dialog_key_neg_cb (widget:_,event:_,data_)"); if (event->keyval == GDK_Escape) { gint response = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), "e2-negative-response")); gtk_dialog_response (GTK_DIALOG (widget), response); return TRUE; } return FALSE; } /* BAD use what is currently focused, not always the positive response gboolean e2_dialog_key_pos_cb (GtkWidget *widget, GdkEventKey *event, gpointer data) { printd (DEBUG, "e2_dialog_key_pos_cb (widget:_,event:_,data_)"); if (event->keyval == GDK_Return) { gint response = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (data), "e2-positive-response")); gtk_dialog_response (GTK_DIALOG (data), response); return TRUE; } return FALSE; } */ /** @brief setup various dialog window parameters @param dialog the widget to be processed @param parent the parent dialog widget of @a dialog @return */ void e2_dialog_setup (GtkWidget *dialog, GtkWidget *parent) { //some funcs here go down to xlib etc CHECKME problem if re-entrant ? // CLOSEBGL_IF_OPEN GtkWindow *thiswindow = GTK_WINDOW (dialog); if (parent != NULL) gtk_window_set_transient_for (thiswindow, GTK_WINDOW (parent)); gtk_window_set_destroy_with_parent (thiswindow, TRUE); gtk_window_set_wmclass (thiswindow, gtk_window_get_title (thiswindow), PROGNAME); gtk_window_set_role (thiswindow, "dialog"); gtk_window_set_position (thiswindow, e2_option_int_get ("dialog-position")); gtk_window_set_resizable (thiswindow, TRUE); //moved to here from ... show // gtk_widget_set_size_request (dialog, 0, 0); //KILLS LATESHOW etc in this spot (at least) !! // OPENBGL_IF_CLOSED } /** @brief setup and run dialog @param dialog the widget to be processed @param parent the parent dialog widget of @a dialog, or NULL @param flags bitflags indicating how the dialog is to be run @param button start of NULL-terminated sequence of button data-structs @return the value returned from the dialog */ gint e2_dialog_show (GtkWidget *dialog, GtkWidget *parent, E2_DialogFlags flags, E2_Button *button, ...) { // GtkWidget *lastbutton; //just in case we want a pointer to the last 1 //some of the calls here have no buttons //but we get warned if the flags button is made the last computsory one if (button != NULL) { va_list args; va_start (args, button); while (button != NULL) { //lastbutton = e2_dialog_add_defined_button (dialog, button); button = va_arg (args, E2_Button *); } va_end (args); } e2_dialog_setup (dialog, parent); return e2_dialog_run (dialog, parent, flags); } /** @brief run dialog This supports out-of-loop dialog single-setup If modal, it's intended to be used for filelist-related dialogs, as the active list is focused when the run is completed @param dialog the widget to be processed @param parent the parent dialog widget of @a dialog, or NULL @param flags bitflags indicating how the dialog is to be run @return the value returned from the dialog, 0 if the dialog is not run */ gint e2_dialog_run (GtkWidget *dialog, GtkWidget *parent, E2_DialogFlags flags) { //FIXME if single-setup used, need to re-select default button */ if (flags & E2_DIALOG_THREADED) gdk_threads_enter (); if ((flags & E2_DIALOG_RUNMODAL) && (parent != NULL)) gtk_widget_set_sensitive (parent, FALSE); if (flags & E2_DIALOG_DONT_SHOW_ALL) gtk_widget_show (dialog); //this duplicates effect in gtk_dialog_run() else gtk_widget_show_all (dialog); if (flags & E2_DIALOG_THREADED) gdk_threads_leave (); if (flags & E2_DIALOG_RUNMODAL) { if (flags & E2_DIALOG_THREADED) gdk_threads_enter (); gint ret = gtk_dialog_run (GTK_DIALOG (dialog)); if (parent != NULL) gtk_widget_set_sensitive (parent, TRUE); //ret could be GTK_RESPONSE_NONE or GTK_RESPONSE_DELETE_EVENT !! if (flags & E2_DIALOG_FREE) gtk_widget_destroy (dialog); gtk_widget_grab_focus (curr_view->treeview); if (flags & E2_DIALOG_THREADED) gdk_threads_leave (); return ret; } return 0; } /** @brief create a dialog If @a callback is NULL, the caller must locally handle "delete-event" signals if anything special is needed for that If @a label contains any text which looks like "bad" pango-markup, then the label must have been escaped before calling here @param stock name of gtk-stock-icon, or absolute localised path of icon file, to be shown near the top of the dialog, or NULL @param label_text text to be shown near the top of the dialog, or NULL @param title title string for the dialog, or NULL @param callback response function for the dialog, or NULL @param data pointer to data to be provided to @a callback @return the dialog widget */ GtkWidget *e2_dialog_create (const gchar *stock, const gchar *label_text, const gchar *title, gpointer callback, gpointer data) { GtkWidget *dialog = gtk_dialog_new (); //BGL may be open or closed here CLOSEBGL_IF_OPEN e2_window_set_title (dialog, title); gtk_widget_add_events (dialog, GDK_ALL_EVENTS_MASK); //uses xlib? OPENBGL_IF_CLOSED GtkWidget *img = (stock != NULL ) ? e2_widget_get_icon (stock, GTK_ICON_SIZE_DIALOG) : NULL; if (img != NULL || label_text != NULL) { GtkWidget *hbox = gtk_hbox_new (FALSE, E2_PADDING); gtk_container_set_border_width (GTK_CONTAINER (hbox), E2_PADDING); g_object_set_data (G_OBJECT (dialog), "e2-dialog-hbox", hbox); if (img != NULL) { // gtk_misc_set_alignment (GTK_MISC (img), 0.5, 0.0); //no effect // gtk_box_pack_start (GTK_BOX (hbox), img, FALSE, FALSE, E2_PADDING); gint isize = e2_utils_get_icon_size (GTK_ICON_SIZE_DIALOG); GtkWidget *align = gtk_alignment_new (1.0, 0.0, 0.0, 0.0); gtk_widget_set_usize (align, isize*1.2, isize); gtk_container_add (GTK_CONTAINER (align), img); gtk_box_pack_start (GTK_BOX (hbox), align, FALSE, FALSE, 0); } if (label_text != NULL) { GtkWidget *label = e2_widget_add_mid_label (hbox, label_text, 0.5, TRUE, (img != NULL) ? 2 : E2_PADDING); g_object_set_data (G_OBJECT (dialog), "e2-dialog-label", label); } gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), hbox, FALSE, FALSE, 0); gtk_widget_show_all (hbox); } #ifdef E2_COMPOSIT e2_window_set_opacity (dialog, DIALOG_OPACITY_LEVEL); //constant opacity for dialogs #endif //see above gtk_widget_add_events (dialog, GDK_ALL_EVENTS_MASK); if (callback != NULL) g_signal_connect (G_OBJECT (dialog), "response", G_CALLBACK (callback), data); else g_signal_connect (G_OBJECT (dialog), "response", G_CALLBACK (_e2_dialog_response_cb), NULL); //the dialog may not have a CANCEL button, but the response will be decoded anyway e2_dialog_set_negative_response (dialog, GTK_RESPONSE_CANCEL); g_signal_connect (G_OBJECT (dialog), "key-press-event", G_CALLBACK (e2_dialog_key_neg_cb), dialog); //don't need a "delete-event" callback, that event generates a reponse GTK_RESPONSE_DELETE_EVENT return dialog; } /** @brief create a vbox with scrolled window, in @a dialog @param dialog the widget to hold the box @return the vbox widget */ GtkWidget *e2_dialog_add_sw (GtkWidget *dialog) { //start without scrollbars, so that the dialog is first displayed with the //size of the things packed into the sw GtkWidget *scrolled = e2_widget_add_sw (GTK_DIALOG (dialog)->vbox, GTK_POLICY_NEVER, GTK_POLICY_NEVER, TRUE, 0); //arrange for automatic scrollbars when the dialog is shown, after sizing g_signal_connect (G_OBJECT (GTK_DIALOG (dialog)), "show", G_CALLBACK (e2_dialog_show_cb), scrolled); GtkWidget *vbox = e2_widget_get_box (TRUE, FALSE, 0); gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled), vbox); gtk_viewport_set_shadow_type (GTK_VIEWPORT (GTK_BIN (scrolled)->child), GTK_SHADOW_NONE); GtkWidget *hbox = g_object_get_data (G_OBJECT (dialog), "e2-dialog-hbox"); if (hbox != NULL) { gtk_widget_reparent (hbox, vbox); gtk_box_set_child_packing (GTK_BOX (vbox), hbox, FALSE, FALSE, 0, GTK_PACK_START); } gtk_widget_show (vbox); return vbox; } /** @brief setup to show/hide dialog button icon This is a helper for the various add-button functions @param stock string with name of icon that was requested @return stock or NULL if no icon is to be shown */ static gchar *_e2_dialog_button_image_check (gchar *stock) { return (e2_option_bool_get ("dialog-button-icons")) ? stock : NULL; } /** @brief add a pre-defined button to the 'action-area' of @a dialog Any custom tooltip will be cleared (and maybe freed) after button is added @param dialog the widget to hold the button @param button ptr to standard data struct for a defined button @return the created button widget */ GtkWidget *e2_dialog_add_defined_button (GtkWidget *dialog, E2_Button *button) { GtkWidget *retbutton = NULL; if (!(button->showflags & E2_BTN_HIDDEN)) { gchar *tip = (button->showflags & E2_BTN_TIPPED) ? button->tip : NULL; retbutton = e2_button_get (button->label, _e2_dialog_button_image_check (button->stock), tip, NULL, NULL); gtk_dialog_add_action_widget (GTK_DIALOG (dialog), retbutton, button->response); if (button->showflags & E2_BTN_DEFAULT) gtk_dialog_set_default_response (GTK_DIALOG (dialog), button->response); else if (button->showflags & E2_BTN_GREY) gtk_widget_set_sensitive (retbutton, FALSE); gtk_widget_show_all (retbutton); } //revert to default button parameters, ready for next usage if (button->showflags & E2_BTN_RENAMED) button->label = gettext (button->default_label); if (button->showflags & E2_BTN_FREETIP) { g_free (button->tip); button->tip = NULL; } else if (button->showflags & E2_BTN_TIPPED) button->tip = NULL; button->showflags = button->default_flags; return retbutton; } /** @brief add a simple custom button to the 'action-area' of @a dialog @param dialog the widget to hold the button @param stock string with icon name, or NULL @param label string with button label @param response the dialog response to be assigned to the button @return the created button widget */ GtkWidget *e2_dialog_add_undefined_button (GtkWidget *dialog, gchar *stock, gchar *label, gint response) { GtkWidget *button = e2_button_get (label, _e2_dialog_button_image_check (stock), NULL, NULL, NULL); gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, response); gtk_widget_show_all (button); return button; } /** @brief add a custom button to the 'action-area' of @a dialog @param dialog the widget to hold the button @param is_default TRUE if the created button will be the default @param button pointer to populated button data struct @param tip string with button tooltip @param callback callback func when button is clicked @param data ptr to data supplied to @a callback @return the created button widget */ GtkWidget *e2_dialog_add_button_custom (GtkWidget *dialog, gboolean is_default, E2_Button *button, gchar *tip, void *callback, void *data) { return e2_dialog_add_undefined_button_custom (dialog, is_default, button->response, button->label, _e2_dialog_button_image_check (button->stock), tip, callback, data); } /** @brief add a custom 'unstructured' button to the 'action-area' of @a dialog @param dialog the widget to hold the button @param is_default TRUE if the created button will be the default @param response the dialog response to be assigned to the button @param label string with button label @param stock string with icon name, or NULL @param tip string with button tooltip @param callback callback func when button is clicked @param data ptr to data supplied to @a callback @return the created button widget */ GtkWidget *e2_dialog_add_undefined_button_custom ( GtkWidget *dialog, gboolean is_default, guint response, gchar *label, gchar *stock, gchar *tip, gpointer callback, gpointer data ) { E2_ButtonFlags myflags = E2_BUTTON_CAN_FOCUS; if (is_default) { myflags |= E2_BUTTON_CAN_DEFAULT; gtk_dialog_set_default_response (GTK_DIALOG (dialog), response); } GtkWidget *button; button = e2_button_get_full (label, _e2_dialog_button_image_check (stock), GTK_ICON_SIZE_BUTTON, tip, callback, data, myflags); gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, response); gtk_widget_show_all (button); return button; } /** @brief add a (gtk) toggle button to the 'action-area' of @a dialog @param dialog the widget to hold the button @param value T/F the initial setting of the toggle @param label string with button label @param tip string with button tooltip, or NULL @param response the dialog response to be assigned to the button @return the created button widget */ GtkWidget *e2_dialog_add_toggle_button (GtkWidget *dialog, gboolean value, gchar *label, gchar *tip, gint response) { GtkWidget *button = e2_button_get_toggle (FALSE, value, label, tip, NULL, NULL); gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, response); return button; } /** @brief add a check button to the 'action-area' of @a dialog @param dialog the widget to hold the button @param value T/F the initial setting of the toggle @param label string with button label @param tip string with button tooltip, or NULL @param response the dialog response to be assigned to the button @return the created button widget */ GtkWidget *e2_dialog_add_check_button (GtkWidget *dialog, gboolean value, gchar *label, gchar *tip, gint response) { GtkWidget *button = e2_button_get_toggle (TRUE, value, label, tip, NULL, NULL); gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, response); return button; } /** @brief "clicked" signal callback for file-chooser dialog toggle-hidden button @param button the clicked button widget @param chooser the chooser interface for the dialog @return */ static void _e2_dialog_toggglehidden_cb (GtkButton *button, GtkFileChooser *chooser) { //toggle hidden-items display & button-image gboolean nowhidden = gtk_file_chooser_get_show_hidden (chooser); gtk_file_chooser_set_show_hidden (chooser, ! nowhidden); e2_button_set_image (GTK_WIDGET (button), (nowhidden) ? "hidden_show_"E2IP".png" : "hidden_noshow_"E2IP".png"); } /** @brief setup a file-chooser dialog The actual dialog is not created here as there's no passthrough of variadic args for button-icons and -signals. @param dialog the newly-created file-chooser-dialog @param title title string for the dialog, or NULL @param startpath initial item to select in the dialog (absolute utf8 path), or NULL @param action enumerator of type of chooser dialog @param showhidden TRUE to start with hidden items displayed @param multi TRUE if selection of multiple items is allowed @param defresponse the default response for the dialog @return the filechooser widget thai's part of the dialog */ void e2_dialog_setup_chooser (GtkWidget *dialog, const gchar *title, const gchar *startpath, GtkFileChooserAction action, gboolean showhidden, gboolean multi, gint defresponse) { //FIXME vfs gchar *local, *s; GtkFileChooser *chooser = GTK_FILE_CHOOSER (dialog); /* GtkWidget *dialog = e2_dialog_create (NULL, NULL, title, _e2_some_response_cb, somedata); GtkWidget *vbox = GTK_DIALOG (dialog)->vbox; gtk_box_pack_start (GTK_BOX(vbox), wchooser, TRUE, TRUE, 0); e2_dialog_setup (dialog, parent); */ e2_window_set_title (dialog, title); #ifdef E2_COMPOSIT e2_window_set_opacity (dialog, DIALOG_OPACITY_LEVEL); //constant opacity for dialogs #endif /* //hide the dialog's standard 'open' button GtkContainer *bbox = GTK_CONTAINER (GTK_DIALOG (dialog)->action_area); GList* children = gtk_container_get_children (bbox); gtk_widget_hide ((GtkWidget *)children->data); g_list_free (children); */ GtkWidget *hidebtn = e2_dialog_add_undefined_button_custom (dialog, FALSE, E2_RESPONSE_USER1, _("_Hidden"), (showhidden) ? "hidden_noshow_"E2IP".png" : "hidden_show_"E2IP".png", _("Toggle display of hidden items"), _e2_dialog_toggglehidden_cb, chooser); GtkWidget *box = (GTK_DIALOG (dialog))->action_area; gtk_box_reorder_child (GTK_BOX (box), hidebtn, 0); #ifdef USE_GTK2_8 if ((action == GTK_FILE_CHOOSER_ACTION_SAVE || action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER || action == GTK_FILE_CHOOSER_ACTION_OPEN) && e2_option_bool_get ("confirm-overwrite")) gtk_file_chooser_set_do_overwrite_confirmation (chooser, TRUE); #endif gtk_dialog_set_default_response (GTK_DIALOG (dialog), defresponse); gtk_file_chooser_set_select_multiple (chooser, multi); if (showhidden) gtk_file_chooser_set_show_hidden (chooser, TRUE); if (startpath != NULL) { local = D_FILENAME_TO_LOCALE (startpath); s = local + strlen (local) - 1; if (*s == G_DIR_SEPARATOR) { //no file named, need to trick selector into opening correct dir s = local; local = e2_utils_strcat (local, " "); g_free (s); } gtk_file_chooser_set_filename (chooser, local); g_free (local); } else { #ifdef E2_VFSTMP FIXME #else local = D_FILENAME_TO_LOCALE (curr_view->dir); s = local + strlen (local) - 1; if (*s == G_DIR_SEPARATOR) *s = '\0'; gtk_file_chooser_set_current_folder (chooser, local); g_free (local); #endif // gtk_file_chooser_set_current_name (chooser, // "Untitled document"); //this must be utf8 } //FIXME change size: here or after mapping or showing does not work // gint window_width = MIN(400, app.window.panes_paned->allocation.width*2/3); // gtk_window_resize (GTK_WINDOW (dialog), window_width, -1); // gint width, height; // gtk_window_get_size (GTK_WINDOW (dialog), &width, &height); // width = MIN(width, app.window.panes_paned->allocation.width*2/3); // gtk_window_resize (GTK_WINDOW (dialog), width*2, height*6); // gtk_window_resize (GTK_WINDOW (dialog), width, -1); // g_signal_connect (G_OBJECT (dialog), "map", //"show", // G_CALLBACK (_e2_dialog_show_cb), NULL); //the dialog may not have a CANCEL button, but the response will be decoded anyway e2_dialog_set_negative_response (dialog, GTK_RESPONSE_CANCEL); g_signal_connect (G_OBJECT (dialog), "key-press-event", G_CALLBACK (e2_dialog_key_neg_cb), dialog); //don't need a "delete-event" callback, that event generates a reponse GTK_RESPONSE_DELETE_EVENT // e2_dialog_setup (dialog, parent); } /** @brief display dialog to get user input with obfuscated text display, for passwords @param window_title window title string @param prompt prompt string @param input pointer to store for the inputted password string @return button code returned by the dialog */ /* tag PASSWORDINPUT DialogButtons e2_dialog_password_input (gchar* window_title, gchar *prompt, gchar **input) { gchar *realprompt = (prompt != NULL) ? prompt : _("Enter password:"); return _e2_dialog_line_input (window_title, realprompt, NULL, FALSE, TRUE, input, 0, FALSE, NULL); } */ /** @brief display dialog to get user input @param window_title window title string @param prompt prompt string @param suggestion suggested answer string @param extras button-related flags @param select_text TRUE to select the displayed @a suggestion @param input pointer to store for the inputted password string @return button code returned by the dialog */ DialogButtons e2_dialog_line_input (gchar* window_title, gchar *prompt, gchar *suggestion, OW_ButtonFlags extras, gboolean select_text, gchar **input) { return _e2_dialog_line_input (window_title, prompt, suggestion, select_text,// tag PASSWORDINPUT FALSE, input, extras, FALSE, NULL); } /** @brief display dialog to get user input, via a combobox @param window_title window title string @param prompt prompt string @param suggestion string to show in combobox entry, or NULL @param extras button-related flags @param history_list the history list for a combo entry @param input pointer to store for the entered string @return button code returned by the dialog */ DialogButtons e2_dialog_combo_input (gchar* window_title, gchar *prompt, gchar *suggestion, OW_ButtonFlags extras, GList **history_list, gchar **input) { return _e2_dialog_line_input (window_title, prompt, suggestion, TRUE, // tag PASSWORDINPUT FALSE, input, extras, TRUE, history_list); } /** @brief generic dialog for user line-input @param window_title window title string @param prompt prompt string @param suggestion suggested answer string @param extras button-related flags @param select_text TRUE to select the displayed @a suggestion @param input pointer to store for the entered string @param history TRUE to make the input a combobox with history @param history_list the history list for a combo entry @return button code returned by the dialog */ static DialogButtons _e2_dialog_line_input (gchar* window_title, gchar *prompt, gchar *suggestion, gboolean select_text, // tag PASSWORDINPUT gboolean hide_text, gchar **input, OW_ButtonFlags extras, gboolean history, GList **history_list) { GtkWidget *dialogentry; GtkWidget *dialogcombo = NULL; //assignment for compiler-warning prevention only /* GtkWidget *dialog = get_dialog (app.main_window, GTK_MESSAGE_QUESTION, #ifdef LATESHOW ""); #else prompt); #endif gchar *title = (window_title != NULL) ? window_title : _("user input"); e2_dialog_set_title (dialog, title); */ gchar *title = (window_title != NULL) ? window_title : _("user input"); DialogButtons retval; GtkWidget *dialog = e2_dialog_create (GTK_STOCK_DIALOG_QUESTION, #ifdef LATESHOW "", #else prompt, #endif title, e2_dialog_response_decode_cb, &retval); if (history) { dialogcombo = e2_combobox_add (GTK_DIALOG (dialog)->vbox, FALSE, E2_PADDING, NULL, NULL, history_list, E2_COMBOBOX_HAS_ENTRY | E2_COMBOBOX_FOCUS_ON_CHANGE); dialogentry = GTK_BIN (dialogcombo)->child; if (suggestion != NULL) { gtk_entry_set_text (GTK_ENTRY (dialogentry), suggestion); //select_text is TRUE gtk_editable_select_region (GTK_EDITABLE (dialogentry), 0, -1); } } else dialogentry = e2_widget_add_entry (GTK_DIALOG (dialog)->vbox, #ifdef LATESHOW NULL, FALSE, FALSE); #else suggestion, FALSE, select_text); #endif #ifdef E2_ASSISTED GtkWidget *label = (GtkWidget *) g_object_get_data (G_OBJECT (dialog), "e2-dialog-label"); e2_widget_set_label_relations (GTK_LABEL (label), dialogentry); #endif //handle Return-key presses when the entry is focused g_signal_connect (G_OBJECT (dialogentry), "activate", G_CALLBACK (_e2_dialog_activated_cb), &retval); /* tag PASSWORDINPUT if (hide_text) gtk_entry_set_visibility (GTK_ENTRY (dialogentry), FALSE); */ // these separated, to do them once for re-run dialogs if (extras) { if (extras & NOALL) { E2_BUTTON_YESTOALL.showflags |= E2_BTN_GREY; e2_dialog_set_negative_response (dialog, E2_RESPONSE_NOTOALL); } else if (!(extras & BOTHALL)) { E2_BUTTON_YESTOALL.showflags |= E2_BTN_GREY; E2_BUTTON_NOTOALL.showflags |= E2_BTN_GREY; } e2_dialog_add_defined_button (dialog, &E2_BUTTON_NOTOALL); e2_dialog_add_defined_button (dialog, &E2_BUTTON_YESTOALL); } //set default button to 'cancel' // E2_BUTTON_CANCEL.showflags |= E2_BTN_DEFAULT; // E2_BUTTON_OK.showflags &= ~E2_BTN_DEFAULT; e2_dialog_add_defined_button (dialog, &E2_BUTTON_CANCEL); e2_dialog_add_defined_button (dialog, &E2_BUTTON_OK); e2_dialog_setup (dialog, app.main_window); #ifdef LATESHOW // FIXME figure out how to do all the above once-only (if needed) instead of each dialog re-run // ------------------------------------------------------------------------------------------- // add prompt to dialog now (eg in re-run)instead of at the start (1st-time) // GtkLabel *label = GTK_LABEL (GTK_MESSAGE_DIALOG (dialog)->label); GtkLabel *label = GTK_LABEL (GTK_DIALOG (dialog)->label); gtk_label_set_text (label, prompt); gtk_label_set_use_markup (label, TRUE); #ifdef E2_ASSISTED e2_widget_set_label_relations (label, dialogentry); #endif // & add default/suggestion now if (history) { //CHECKME set to last-used entry? // if (history_list != NULL) // e2_combobox_set_active (dialogcombo, 0) } else { gtk_entry_set_text (GTK_ENTRY (dialogentry), suggestion); if (select_text) gtk_editable_select_region (GTK_EDITABLE (dialogentry), 0, -1); } #endif //CHECKME uses of this other than in renaming tasks are ok with this non-sensitive ? e2_dialog_run (dialog, app.main_window, 0); //don't desensitize the parent window gtk_main (); if (retval == OK) { *input = gtk_editable_get_chars (GTK_EDITABLE (dialogentry), 0, -1 ); //free this after return if (**input == '\0') { //dopey user supplied a blank name g_free (*input); retval = CANCEL; } else if (history) e2_list_update_history (*input, history_list, NULL, 10, FALSE); } // FIXME just hide the window, if so specified // in the meantime ... gtk_widget_destroy (dialog); return retval; } /** @brief dialog for overwrite check Assumes that @a dlocal (at least) exists @param slocal localised string with source-item path, or NULL for no date-check @param dlocal localised string with destination-item path @param extras flags for which buttons to show @return button code returned by the dialog */ DialogButtons e2_dialog_ow_check (gchar *slocal, gchar *dlocal, OW_ButtonFlags extras) { // printd (DEBUG, "overwrite check"); #ifdef E2_VFS VPATH ddata; #endif struct stat sb; GString *dialog_prompt = g_string_sized_new (NAME_MAX + 64); //pick a reasonable initial size gchar *utf = F_DISPLAYNAME_FROM_LOCALE (dlocal); //before applying bold markup, escape any pango-annoying component of the item name gchar *public = g_markup_escape_text (utf, -1); gchar *type = NULL; if (slocal != NULL) { // E2_ERR_DECLARE #ifdef E2_VFS ddata.localpath = slocal; #ifdef E2_VFSTMP //FIXME spacedata #endif ddata.spacedata = NULL; if (!e2_fs_stat (&ddata, &sb E2_ERR_NONE())) #else if (!e2_fs_stat (slocal, &sb E2_ERR_NONE())) //should never fail #endif { gboolean sdir = S_ISDIR (sb.st_mode); time_t stime = sb.st_mtime; #ifdef E2_VFS ddata.localpath = dlocal; #ifdef E2_VFSTMP //FIXME spacedata #endif if (e2_fs_stat (&ddata, &sb E2_ERR_NONE())) #else if (e2_fs_stat (dlocal, &sb E2_ERR_NONE())) #endif { //FIXME handle error properly - maybe nothing to overwrite now //E2_ERR_CLEAR } else if (sdir || !S_ISDIR (sb.st_mode)) //no special treatment for file-to-dir { if (stime > sb.st_mtime) type = _("older"); //dest is older else if (stime < sb.st_mtime) type = _("newer"); //source is older } } //#ifdef E2_VFS // else // E2_ERR_CLEAR //#endif } if (type == NULL) type = _("existing"); #ifdef E2_VFS ddata.localpath = dlocal; # ifdef E2_VFSTMP //FIXME spacedata # endif ddata.spacedata = NULL; if (!e2_fs_lstat (&ddata, &sb E2_ERR_NONE()) #else if (!e2_fs_lstat (dlocal, &sb E2_ERR_NONE()) //should never fail #endif && S_ISDIR (sb.st_mode) && !S_ISLNK (sb.st_mode)) g_string_printf (dialog_prompt, _("Remove all contents of %s\n%s ?"), type, public); else { gchar *dbase = g_path_get_basename (public); gchar *dpath = g_path_get_dirname (public); g_string_printf (dialog_prompt, _("Overwrite %s %s\nin %s ?"), type, dbase, dpath); g_free (dbase); g_free (dpath); } DialogButtons retval; GtkWidget *dialog = e2_dialog_create (GTK_STOCK_DIALOG_WARNING, dialog_prompt->str, _("confirm"), e2_dialog_response_decode_cb, &retval); F_FREE (utf); g_free (public); g_string_free (dialog_prompt, TRUE); //FIXME adjust this to suit single-setup when multi is TRUE if (extras & NOALL) { E2_BUTTON_YESTOALL.showflags |= E2_BTN_GREY; e2_dialog_set_negative_response (dialog, E2_RESPONSE_NOTOALL); } else if (!(extras & BOTHALL)) { E2_BUTTON_YESTOALL.showflags |= E2_BTN_GREY; E2_BUTTON_NOTOALL.showflags |= E2_BTN_GREY; } //set default button to 'no' E2_BUTTON_NO.showflags |= E2_BTN_DEFAULT; E2_BUTTON_YES.showflags &= ~E2_BTN_DEFAULT; //non-modal dialog e2_dialog_show (dialog, app.main_window, 0, //no _RUN (hence window stays sensitive) no _FREE, in case we want to hide it &E2_BUTTON_NOTOALL, &E2_BUTTON_YESTOALL, &E2_BUTTON_NO, &E2_BUTTON_YES, NULL); gtk_main (); // FIXME just hide the window, if so specified // in the meantime ... gtk_widget_destroy (dialog); return retval; } /** @brief run warning dialog for confirmation @param prompt prompt string @return button code returned by the dialog */ DialogButtons e2_dialog_warning (gchar *prompt) { DialogButtons retval; GtkWidget *dialog = e2_dialog_create (GTK_STOCK_DIALOG_WARNING, prompt, _("confirm"), e2_dialog_response_decode_cb, &retval); //set default button to 'cancel' E2_BUTTON_CANCEL.showflags |= E2_BTN_DEFAULT; E2_BUTTON_OK.showflags &= ~E2_BTN_DEFAULT; e2_dialog_show (dialog, NULL, //parent stays sensitive 0, &E2_BUTTON_CANCEL, &E2_BUTTON_OK, NULL); gtk_main (); //because the decoder has gtk_main_quit(); gtk_widget_destroy (dialog); return retval; } /** @brief create "too slow" dialog There is no actual display of anything, so gdk mutex management is not done (assumes BGL is on) Note: when juggling dialog between threads, avoid gtk_main[_quit] usage in dialog display code and response cb. @param prompt_type context-specific part of prompt string @param tip_type context-specific part of cancel-button tip @param response_func pointer to function to use for dialog's "response" callback, or NULL @param data data to provide to the dialog's response callback @return the dialog widget */ GtkWidget *e2_dialog_slow (gchar *prompt_type, gchar *tip_type, gpointer response_func, gpointer data) { gchar *prompt = g_strdup_printf ( _("%s is taking a long time. Continue waiting ?"), prompt_type); GtkWidget *dialog = e2_dialog_create (GTK_STOCK_DIALOG_QUESTION, prompt, _("confirm"), (response_func != NULL) ? response_func : _e2_dialog_response_cb, data); g_free (prompt); E2_BUTTON_YESTOALL.showflags |= (E2_BTN_RENAMED | E2_BTN_TIPPED); E2_BUTTON_YESTOALL.label = _("_Quiet"); E2_BUTTON_YESTOALL.tip = _("Don't ask any more"); e2_dialog_add_defined_button (dialog, &E2_BUTTON_YESTOALL); E2_BUTTON_NO.showflags |= (E2_BTN_TIPPED | E2_BTN_FREETIP); E2_BUTTON_NO.tip = g_strdup_printf (_("Cancel the %s"), tip_type); e2_dialog_add_defined_button (dialog, &E2_BUTTON_NO); E2_BUTTON_YES.showflags |= E2_BTN_TIPPED; E2_BUTTON_YES.tip = _("Wait some more"); e2_dialog_add_defined_button (dialog, &E2_BUTTON_YES); e2_dialog_set_negative_response (dialog, GTK_RESPONSE_YES); e2_dialog_setup (dialog, app.main_window); return dialog; } /** @brief setup context menu for @a dialog @param dialog the dialog to which @a menu belongs @param menu context menu for @a dialog @return */ void e2_dialog_attach_menu (GtkWidget *dialog, GtkWidget *menu) { g_return_if_fail ((dialog != NULL) && (menu != NULL)); g_signal_connect (G_OBJECT (dialog), "button-press-event", G_CALLBACK (_e2_dialog_button_press_cb), menu); g_object_set_data_full (G_OBJECT (dialog), "e2-context-menu", menu, (GDestroyNotify) gtk_widget_destroy); } /* UNUSED void e2_dialog_set_positive_response (GtkWidget *dialog, gint response) { g_object_set_data (G_OBJECT (dialog), "e2-positive-response", GINT_TO_POINTER (response)); // gtk_dialog_set_default_response (GTK_DIALOG (dialog), response); } */ /** @brief assign the 'negative' response for @a dialog @param dialog the dialog widget to be processed @param response the number of the "negative" response @return */ void e2_dialog_set_negative_response (GtkWidget *dialog, gint response) { g_object_set_data (G_OBJECT (dialog), "e2-negative-response", GINT_TO_POINTER (response)); } /* UNUSED as we only use the -ve response, now void e2_dialog_set_responses (GtkWidget *dialog, gint pos, gint neg) { e2_dialog_set_positive_response (dialog, pos); e2_dialog_set_negative_response (dialog, neg); } */ /** @brief proportionally change size of @a dialog @param dialog the dialog widget to be resized @param factor multiple to apply to width and height of @a dialog @return */ void e2_dialog_resize (GtkWidget *dialog, gfloat factor) { gint width, height; gtk_window_get_size (GTK_WINDOW (dialog), &width, &height); width *= factor; height *= factor; gtk_window_resize (GTK_WINDOW (dialog), width, height); } /** @brief setup to resize @a dialog at an idle time @param dialog the dialog widget to be resized @param scrolled a scrolled window widget in @a dialog, or NULL @return */ void e2_dialog_resize_with_sw (GtkWidget *dialog, GtkWidget *scrolled) { guint idle_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (dialog), "e2-dialog-resize-idle-id")); if (idle_id != 0) g_source_remove (idle_id); idle_id = g_idle_add ((GSourceFunc) _e2_dialog_resize_idle_cb, dialog); g_object_set_data (G_OBJECT (dialog), "e2-dialog-resize-idle-id", GUINT_TO_POINTER (idle_id)); g_object_set_data (G_OBJECT (dialog), "e2-dialog-resize-scrolled", scrolled); g_signal_connect (G_OBJECT (dialog), "destroy", G_CALLBACK (_e2_dialog_resize_destroy_cb), NULL); } /** @brief set dialog window cursor to @a type Expects BGL on/active @param dialog the affected dialog widget @param type enumerator of desired cursor type @return */ void e2_dialog_set_cursor (GtkWidget *dialog, GdkCursorType type) { GdkCursor *cursor = gdk_cursor_new (type); gdk_window_set_cursor (dialog->window, cursor); gdk_cursor_unref (cursor); gdk_flush (); } /** @brief register dialog-related options @return */ void e2_dialog_options_register (void) { const gchar *opt_dialog_pos_position[] = {_("none"), _("center"), _("mouse"), _("always center"), _("center on parent"), NULL}; gchar *group_name = g_strconcat(_C(20),":",_C(25),NULL); //_("interface:miscellaneous" e2_option_sel_register ("dialog-position", group_name, _("dialog position"), _("This determines the position where dialog windows will pop up"), NULL, 1, opt_dialog_pos_position, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP); } emelfm2-0.4.1/src/dialogs/e2_about_dialog.h0000600000175000017500000000166011010340377017407 0ustar cairocairo/* $Id: e2_about_dialog.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_ABOUT_DIALOG_H__ #define __E2_ABOUT_DIALOG_H__ void e2_about_dialog_actions_register (void); #endif //ndef __E2_ABOUT_DIALOG_H__ emelfm2-0.4.1/src/dialogs/e2_ownership_dialog.h0000600000175000017500000000245211010340377020313 0ustar cairocairo/* $Id: e2_ownership_dialog.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_OWNERSHIP_DIALOG_H__ #define __E2_OWNERSHIP_DIALOG_H__ #include "emelfm2.h" // the maximum number of membership groups that a user may see #define REPORTABLE_GROUPS 25 // the lowest alternate UID available to non-root users #define UID_LOWLIMIT 500 void e2_ownership_dialog_warn (GtkWidget *box); DialogButtons e2_ownership_dialog_run (VPATH *localpath, uid_t *owner_ret, gid_t *group_ret, gboolean *recurse_ret, gboolean *permission_ret, gboolean multisrc); #endif //ndef __E2_OWNERSHIP_DIALOG_H__ emelfm2-0.4.1/src/dialogs/e2_file_info_dialog.c0000600000175000017500000005532411007710172020230 0ustar cairocairo/* $Id: e2_file_info_dialog.c 863 2008-05-05 22:55:54Z tpgww $ Copyright (C) 2003-2008 tooar Portions copyright (C) 1999 Michael Clark. This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "emelfm2.h" #include #include #include #include #include #include #include "e2_dialog.h" //max length of the file type string #define FILE_TYPE_LENGTH 128 typedef struct _E2_FileInfoRuntime { GtkWidget *dialog; gchar *localpath; //path of item being displayed GtkWidget *description; GtkWidget *target; GtkWidget *size; GtkWidget *user; GtkWidget *group; GtkWidget *permissions; GtkWidget *accessdate; GtkWidget *moddate; GtkWidget *changedate; } E2_FileInfoRuntime; /** @brief get description and mimetype of @a filepath, using the 'file' command @a readable_type and/or @a mime_type are set to NULL if nothing valid is available @param filepath localised path string of item to be processed @param readable_type store for pointer to determined description string @param mime_type store for pointer to determined mime (and for text files, charset too) string @return TRUE if both description and mume commands are executed successfully (which happens if file options are bad) */ static gboolean _e2_file_info_dialog_get_file_type (VPATH *filepath, gchar **readable_type, gchar **mime_type) { #ifdef E2_VFS if (e2_fs_item_is_mounted (filepath)) { #endif size_t bsize = 512; #ifdef USE_GLIB2_10 gchar *buf = (gchar *) g_slice_alloc ((gulong) bsize); #else gchar *buf = (gchar *) g_try_malloc ((gulong) bsize); #endif CHECKALLOCATEDWARNT (buf, ); if (buf == NULL) { *readable_type = NULL; *mime_type = NULL; return FALSE; } //CHECKME -b option ok ? -i option to get mimetype gchar *command = g_strdup_printf ("file -bhnprs \"%s\"", VPSTR (filepath)); E2_FILE *pipe = e2_fs_open_pipe (command); g_free (command); if (pipe == NULL) { *readable_type = NULL; *mime_type = NULL; return FALSE; } # ifdef __USE_GNU ssize_t bytes_read = getdelim (&buf, &bsize, '\n', pipe); if (bytes_read > 1) //not an empty line { *(buf+bytes_read-1) = '\0'; //strip trailing \n # else //getdelim() not available gchar *strtmp = fgets (buf, bsize, pipe); if (strtmp != NULL && *strtmp != '\n') { *(buf+strlen(buf)-1) = '\0'; # endif *readable_type = g_strdup (buf); } else { *readable_type = NULL; //NOTE this is really an error, but returns TRUE } e2_fs_pipe_close (pipe); //get mimetype (and for text files, encoding type) string //we use -i option to get encoding for text files, instead of just --mime-type, //because separate usage of --mime-encoding does not work properly command = g_strdup_printf ("file -bhnpri \"%s\"", VPSTR (filepath)); pipe = e2_fs_open_pipe (command); g_free (command); if (pipe == NULL) { if (*readable_type != NULL) { g_free (*readable_type); *readable_type = NULL; } *mime_type = NULL; return FALSE; } # ifdef __USE_GNU bytes_read = getdelim (&buf, &bsize, '\n', pipe); if (bytes_read > 1) //not an empty line { *(buf+bytes_read-1) = '\0'; //strip trailing \n # else //getdelim() not available strtmp = fgets (buf, bsize, pipe); if (strtmp != NULL && *strtmp != '\n') { *(buf+strlen(buf)-1) = '\0'; # endif *mime_type = g_strdup (buf); } else { *mime_type = NULL; } e2_fs_pipe_close (pipe); # ifdef USE_GLIB2_10 g_slice_free1 (bsize, buf); # else g_free (buf); # endif return TRUE; #ifdef E2_VFS } else //not a local item { # ifdef E2_VFSTMP FIXME # endif *readable_type = NULL; *mime_type = NULL; return FALSE; } #endif } /** @brief set popup menu position This function is supplied when calling gtk_menu_popup(), to position the displayed menu. set @a push_in to TRUE for menu completely inside the screen, FALSE for menu clamped to screen size @param menu the GtkMenu to be positioned @param x place to store gint representing the menu left @param y place to store gint representing the menu top @param push_in place to store pushin flag @param dialog the dialog widget in focus when the menu key was pressed @return */ static void _e2_file_info_dialog_set_menu_position (GtkWidget *menu, gint *x, gint *y, gboolean *push_in, GtkWidget *dialog) { gint left, top; gtk_window_get_position (GTK_WINDOW (dialog), &left, &top); GtkAllocation alloc = dialog->allocation; *x = left + alloc.x + alloc.width/2; *y = top + alloc.y + alloc.height/2; *push_in = FALSE; } /** @brief copy dialog data to clipboard @param menuitem UNUSED the selected widget @param rt runtime struct to work on @return */ static void _e2_file_info_dialog_copy_cb (GtkWidget *menuitem, E2_FileInfoRuntime *rt) { //some format massaging is needed ... gchar *utf = F_FILENAME_FROM_LOCALE (rt->localpath); const gchar *target; if (rt->target != NULL) target = gtk_label_get_text (GTK_LABEL(rt->target)); else target = ""; gchar *typelabel; const gchar *type = gtk_label_get_text (GTK_LABEL(rt->description)); if (g_str_has_prefix (type, _("Type:"))) typelabel = ""; else typelabel = g_strconcat (_("Type:"), " ", NULL); GString *text = g_string_sized_new (512); g_string_printf (text, "%s %s\n" "%s%s%s\n" "%s %s\n" "%s %s\n""%s %s\n" "%s %s\n" "%s %s\n" "%s %s\n" "%s %s\n", _("Item:"), utf, typelabel, type, target, _("Size:"), gtk_label_get_text (GTK_LABEL(rt->size)), _("User:"), gtk_label_get_text (GTK_LABEL(rt->user)), _("Group:"), gtk_label_get_text (GTK_LABEL(rt->group)), _("Permissions:"), gtk_label_get_text (GTK_LABEL(rt->permissions)), _("Accessed:"), gtk_label_get_text (GTK_LABEL(rt->accessdate)), _("Modified:"), gtk_label_get_text (GTK_LABEL(rt->moddate)), _("Changed:"), gtk_label_get_text (GTK_LABEL(rt->changedate)) ); GtkClipboard *cb = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); gtk_clipboard_set_text (cb, text->str, text->len); F_FREE (utf); if (*typelabel != '\0') g_free (typelabel); g_string_free (text, TRUE); } /** @brief construct and show dialog context menu @param textview the textview widget where the click happened @param event_button which mouse button was clicked (0 for a menu key) @param event_time time that the event happened (0 for a menu key) @param rt runtime struct to work on @return */ static void _e2_file_info_dialog_show_context_menu (GtkWidget *dialog, guint event_button, gint event_time, E2_FileInfoRuntime *rt) { GtkWidget *menu = gtk_menu_new (); e2_menu_add (menu, _("_Copy"), GTK_STOCK_COPY, _("Copy displayed data"), _e2_file_info_dialog_copy_cb, rt); g_signal_connect (G_OBJECT (menu), "selection-done", G_CALLBACK (e2_menu_destroy_cb), NULL); if (event_button == 0) gtk_menu_popup (GTK_MENU (menu), NULL, NULL, (GtkMenuPositionFunc) _e2_file_info_dialog_set_menu_position, rt->dialog, event_button, event_time); else //this was a button-3 click gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, event_button, event_time); } /** @brief menu-button press callback @param textview the textview widget where the press happened @param rt dialog runtime data struct @return TRUE always */ static gboolean _e2_file_info_dialog_popup_menu_cb (GtkWidget *dialog, E2_FileInfoRuntime *rt) { gint event_time = gtk_get_current_event_time (); _e2_file_info_dialog_show_context_menu (dialog, 0, event_time, rt); return TRUE; } /** @brief mouse button press callback @param textview the widget where the button was pressed @param event gdk event data @param rt rt data for the dialog @return TRUE (stop other handlers) for btn 3 press, else FALSE (allow other handlers) */ static gboolean _e2_file_info_dialog_button_press_cb (GtkWidget *dialog, GdkEventButton *event, E2_FileInfoRuntime *rt) { if (event->button == 3) { _e2_file_info_dialog_show_context_menu (dialog, 3, event->time, rt); return TRUE; } return FALSE; } /** @brief dialog response callback @param dialog the permissions-dialog where the response was triggered @param response the response for the clicked button @param result pointer to store for the dialog return value @return */ static void _e2_file_info_dialog_response_cb (GtkDialog *dialog, gint response, DialogButtons *result) { gtk_widget_destroy (GTK_WIDGET (dialog)); if (response == GTK_RESPONSE_CLOSE) //not covered in the general decoder { *result = CANCEL; gtk_main_quit (); } else e2_dialog_response_decode_cb (dialog, response, result); } /** @brief create and run file info dialog @param localpath localised path string of item to be processed @param multi TRUE when the dialog needs buttons consistent with >1 item being processed @return enumerator for pressed dialog button */ DialogButtons e2_file_info_dialog_run (VPATH *localpath, gboolean multi) { GtkWidget *dialog; GtkWidget *dialog_vbox; GtkWidget *table; GString *label_text = g_string_sized_new (NAME_MAX + 32); //this is plenty big for all uses here gchar *s1, *s2; gchar *mime; gchar date_string[32]; struct stat statbuf; struct tm *tm_ptr; struct passwd *pw_buf; struct group *grp_buf; E2_FileInfoRuntime rt; DialogButtons choice; if (e2_fs_lstat (localpath, &statbuf E2_ERR_NONE())) //explain to user ?? return CANCEL; //must prevent the default dialog response, it stuffs up Q cleanups rt.dialog = dialog = e2_dialog_create (NULL, NULL, _("file info"), _e2_file_info_dialog_response_cb, &choice); dialog_vbox = GTK_DIALOG (dialog)->vbox; gtk_container_set_border_width (GTK_CONTAINER (dialog_vbox), E2_PADDING); rt.localpath = VPSTR (localpath); //store data for copying s1 = g_filename_display_basename (VPSTR (localpath)); s2 = g_markup_escape_text (s1, -1); g_string_printf (label_text, "%s", s2); e2_widget_add_mid_label (dialog_vbox, label_text->str, 0.5, TRUE, 0); g_free (s1); g_free (s2); e2_widget_add_separator (dialog_vbox, TRUE, E2_PADDING); mime = NULL; if (S_ISREG (statbuf.st_mode)) { gchar *readable = NULL; //CHECKME if (statbuf.st_dev == 0 || statbuf.st_size > statbuf.st_blocks * statbuf.st_blksize) { s2 = _("Virtual file"); } else { //for regular files, get type-data from 'file' command if (_e2_file_info_dialog_get_file_type (localpath, &readable, &mime) && readable != NULL //pipe open may succeed with bad option && *readable != '\0') s2 = e2_utf8_from_locale (readable); //freeme later else { if (readable != NULL) //ensure s2 etc not freed later { g_free (readable); readable = NULL; } s2 = _("Unknown"); } } GdkPixbuf *pixbuf = NULL; if (mime != NULL && *mime != '\0') { gchar *m2 = g_strdup (mime); //mime string returned by 'find' is of the form "text/plain; " //from freedesktop.org, icon name is of the form media-subtype (e.g. text-plain). //If no specific icon is found, fallback to media-x-generic (e.g. text-x-generic). s1 = strchr (m2, ';'); if (s1 != NULL) *s1 = '\0'; s1 = strchr (m2, '/'); if (s1 != NULL) *s1 = '-'; GtkIconTheme *icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (app.main_window)); //app.main_window->screen; pixbuf = gtk_icon_theme_load_icon (icon_theme, m2, GTK_ICON_SIZE_LARGE_TOOLBAR, 0, NULL); if (pixbuf == NULL) { if (s1 != NULL) { *s1 = '\0'; gchar *inm = g_strconcat (m2, "-x-generic", NULL); pixbuf = gtk_icon_theme_load_icon (icon_theme, inm, GTK_ICON_SIZE_LARGE_TOOLBAR, 0, NULL); g_free (inm); if (pixbuf == NULL) { //fallback to anything that looks half-reasonable ! s1++; //pass the \0 GList *inames = gtk_icon_theme_list_icons (icon_theme, NULL); GList *member; for (member=inames; member != NULL; member = member->next) { inm = (gchar *)member->data; // printd (DEBUG, "candidate name = %s", inm); if (strstr (inm, s1) != NULL) { pixbuf = gtk_icon_theme_load_icon (icon_theme, inm, GTK_ICON_SIZE_LARGE_TOOLBAR, 0, NULL); break; } } e2_list_free_with_data (&inames); } } } g_free (m2); } if (pixbuf != NULL) { GtkWidget *image = gtk_image_new_from_pixbuf (pixbuf); GtkWidget *hbox = e2_widget_add_box (dialog_vbox, TRUE, 0, FALSE, FALSE, E2_PADDING_LARGE); gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0); //CHECKME escape label ? rt.description = e2_widget_add_mid_label (hbox, s2, 0, TRUE, 0); g_object_unref (G_OBJECT (pixbuf)); } else { g_string_printf (label_text, "%s %s", _("Type:"), s2); //CHECKME escape label ? rt.description = e2_widget_add_mid_label (dialog_vbox, label_text->str, 0, TRUE, 0); } if (readable != NULL) { g_free (readable); g_free (s2); } } //links to dirs reported as dirs by (e2_fs_is_dir3 (localpath)) else if (S_ISDIR (statbuf.st_mode)) { GtkIconTheme *icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (app.main_window)); //app.main_window->screen; GdkPixbuf *pixbuf = gtk_icon_theme_load_icon (icon_theme, "stock_open", GTK_ICON_SIZE_LARGE_TOOLBAR, 0, NULL); s2 = _("Directory"); if (pixbuf != NULL) { GtkWidget *image = gtk_image_new_from_pixbuf (pixbuf); GtkWidget *hbox = e2_widget_add_box (dialog_vbox, TRUE, 0, FALSE, FALSE, E2_PADDING_LARGE); gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0); //CHECKME escape label ? rt.description = e2_widget_add_mid_label (hbox, s2, 0, TRUE, 0); g_object_unref (G_OBJECT (pixbuf)); } else { g_string_printf (label_text, "%s %s", _("Type:"), s2); rt.description = e2_widget_add_mid_label (dialog_vbox, label_text->str, 0, TRUE, 0); } //FIXME report the device holding the dir } else // not regular or directory { if (S_ISLNK (statbuf.st_mode)) s2 = _("Symbolic Link"); else if (S_ISCHR (statbuf.st_mode)) s2 = _("Character Device"); else if (S_ISBLK (statbuf.st_mode)) s2 = _("Block Device"); else if (S_ISFIFO (statbuf.st_mode)) s2 = _("FIFO Pipe"); else if (S_ISSOCK (statbuf.st_mode)) s2 = _("Socket"); else s2 = _("Unknown"); g_string_printf (label_text, "%s %s", _("Type:"), s2); rt.description = e2_widget_add_mid_label (dialog_vbox, label_text->str, 0, TRUE, 0); } if (S_ISLNK (statbuf.st_mode)) { gchar target[PATH_MAX]; gchar *filename = g_strdup (VPSTR (localpath)); gint len = strlen (filename); if (filename[len-1] == G_DIR_SEPARATOR) filename[len-1] = '\0'; #ifdef E2_VFS VPATH ddata = { filename, localpath->spacedata }; len = e2_fs_readlink (&ddata, target, sizeof (target) E2_ERR_NONE()); #else len = e2_fs_readlink (filename, target, sizeof (target) E2_ERR_NONE()); #endif if (len > 0) { target[len] = '\0'; s1 = F_DISPLAYNAME_FROM_LOCALE (target); s2 = g_markup_escape_text (s1, -1); label_text = g_string_assign (label_text, " "); g_string_append_printf (label_text, _("to %s"), s2); F_FREE (s1); g_free (s2); //statting of relative targets fails, so ... if (g_str_has_prefix (target, "."G_DIR_SEPARATOR_S)) { memcpy (target, (target + 2 * sizeof (gchar)), len); } if (g_str_has_prefix (target, ".."G_DIR_SEPARATOR_S)) { s1 = F_FILENAME_FROM_LOCALE (target); #ifdef E2_VFSTMP //FIXME which CWD for the baseline #else s2 = e2_utils_translate_relative_path (curr_view->dir, s1); #endif F_FREE (s1); s1 = F_FILENAME_TO_LOCALE (s2); g_strlcpy (target, s1, sizeof(target)); //the translation adds a trailing separator, which lstat assumes to be a dir match .. len = strlen (target); target[len-1] = '\0'; F_FREE (s1); g_free (s2); } //FIXME get intelligent absolute path if (!g_path_is_absolute (target)) { gchar *local; #ifdef E2_VFSTMP local = ; FIXME #else local = F_FILENAME_TO_LOCALE (curr_view->dir); #endif gchar *t2 = g_build_filename (local, target, NULL); g_strlcpy (target, t2, sizeof (target)); F_FREE (local); g_free (t2); } E2_ERR_DECLARE //check the real target, without looking thru chained links struct stat statbuf2; //don't disturb the size etc data for the link itelf #ifdef E2_VFS ddata.localpath = target; if (e2_fs_lstat (&ddata, &statbuf2 E2_ERR_PTR()) && E2_ERR_IS (ENOENT)) #else if (e2_fs_lstat (target, &statbuf2 E2_ERR_PTR()) && E2_ERR_IS (ENOENT)) #endif g_string_append_printf (label_text, "\n %s",_("(which is missing)")); else if (g_str_equal (localpath, target)) g_string_append_printf (label_text, "\n %s",_("(which is itself)")); else { if (e2_fs_walk_link (&filename E2_ERR_NONE())) { printd (DEBUG, "final target is %s", filename); if (!g_str_equal (filename, target)) { s1 = F_FILENAME_FROM_LOCALE (filename); #ifdef E2_VFSTMP //FIXME which CWD for the baseline #else s2 = e2_utils_create_relative_path (s1, curr_view->dir); #endif label_text = g_string_append (label_text, "\n "); g_string_append_printf (label_text, _("and ultimately to %s"), s2); F_FREE (s1); g_free (s2); } } } E2_ERR_CLEAR rt.target = e2_widget_add_mid_label (dialog_vbox, label_text->str, 0, TRUE, 0); } else rt.target = e2_widget_add_mid_label (dialog_vbox, _("(Cannot resolve the link)"), 0, TRUE, 0); g_free (filename); } else rt.target = NULL; //don't use this label when copying table = e2_widget_add_table (dialog_vbox, 10, 2, FALSE, TRUE, E2_PADDING); gtk_table_set_col_spacings (GTK_TABLE (table), E2_PADDING); if (mime != NULL) { if (g_str_has_prefix (mime, "text/")) { s2 = strstr (mime, " charset"); if (s2 != NULL) { s1 = g_utf8_strup (s2 + 9, -1); //uppercase encoding *s2 = '\0'; s2 = g_strconcat (mime, "\n", s1, NULL); g_free (s1); s1 = g_strconcat (_("Mime:"), "\n", _("Encoding:"), NULL); } else { s1 = _("Mime:"); s2 = mime; } } else { s1 = _("Mime:"); s2 = mime; } e2_widget_add_mid_label_to_table (table, s1, 0, 0,1,0,1); e2_widget_add_mid_label_to_table (table, s2, 0, 1,2,0,1); if (s2 != mime) { g_free (s1); g_free (s2); } } e2_widget_add_mid_label_to_table (table, _("Size:"), 0, 0,1,1,2); g_string_printf (label_text, "%"PRIu64, statbuf.st_size); //expand number to add separator char(s) const gchar *comma = nl_langinfo (THOUSEP); if (comma == NULL || *comma == '\0') comma = ","; guint len = label_text->len; guint ths = len-1; //0-based index guint i; while (ths > 2 && len < label_text->allocated_len) { for (i = len-1; i > ths-3; i--) label_text->str[i+1] = label_text->str[i]; label_text->str[i+1] = *comma; label_text->str[++len] = '\0'; ths = i; } label_text->len = len; label_text = g_string_append_c (label_text, ' '); label_text = g_string_append (label_text, _("bytes")); rt.size = e2_widget_add_mid_label_to_table (table, label_text->str, 0, 1,2,1,2); GtkWidget *sep = gtk_hseparator_new (); gtk_table_attach_defaults (GTK_TABLE (table), sep, 0,2,2,3); e2_widget_add_mid_label_to_table (table, _("User:"), 0, 0,1,3,4); if ((pw_buf = getpwuid (statbuf.st_uid)) != NULL) g_string_printf (label_text, "%s", pw_buf->pw_name); else g_string_printf (label_text, "%d", (guint) statbuf.st_uid); rt.user = e2_widget_add_mid_label_to_table (table, label_text->str, 0, 1,2,3,4); e2_widget_add_mid_label_to_table (table, _("Group:"), 0, 0,1,4,5); if ((grp_buf = getgrgid (statbuf.st_gid)) != NULL) g_string_printf (label_text, "%s", grp_buf->gr_name); else g_string_printf (label_text, "%d", (guint) statbuf.st_gid); rt.group = e2_widget_add_mid_label_to_table (table, label_text->str, 0, 1,2,4,5); gchar *perm_string = e2_fs_get_perm_string (statbuf.st_mode); g_string_assign (label_text, g_utf8_next_char (perm_string)); //skip the "type code" in 1st byte of string g_free (perm_string); g_string_insert_c (label_text, e2_utils_get_byte_position (label_text->str, 6), ' '); g_string_insert_c (label_text, e2_utils_get_byte_position (label_text->str, 3), ' '); e2_widget_add_mid_label_to_table (table, _("Permissions:"), 0, 0,1,5,6); rt.permissions = e2_widget_add_mid_label_to_table (table, label_text->str, 0, 1,2,5,6); sep = gtk_hseparator_new (); gtk_table_attach_defaults (GTK_TABLE (table), sep, 0,2,6,7); s1 = "%a %Od %b %EY %OI:%OM %p"; tm_ptr = localtime (&statbuf.st_atime); strftime (date_string, sizeof (date_string), s1, tm_ptr); s2 = e2_utf8_from_locale (date_string); e2_widget_add_mid_label_to_table (table, _("Accessed:"), 0, 0,1,7,8); rt.accessdate = e2_widget_add_mid_label_to_table (table, s2, 0, 1,2,7,8); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif rt.accessdate, _("Item was last opened, run, read etc")); g_free (s2); tm_ptr = localtime (&statbuf.st_mtime); strftime (date_string, sizeof (date_string), s1, tm_ptr); s2 = e2_utf8_from_locale (date_string); e2_widget_add_mid_label_to_table (table, _("Modified:"), 0, 0,1,8,9); rt.moddate = e2_widget_add_mid_label_to_table (table, s2, 0, 1,2,8,9); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif rt.moddate, _("Content of the item was last altered")); g_free (s2); tm_ptr = localtime (&statbuf.st_ctime); strftime (date_string, sizeof (date_string), s1, tm_ptr); s2 = e2_utf8_from_locale (date_string); e2_widget_add_mid_label_to_table (table, _("Changed:"), 0, 0,1,9,10); rt.changedate = e2_widget_add_mid_label_to_table (table, s2, 0, 1,2,9,10); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif rt.changedate, _("Property of the item (name, permission, owner etc) was last altered")); g_free (s2); g_string_free (label_text,TRUE); if (mime != NULL) g_free (mime); if (multi) { E2_BUTTON_NOTOALL.showflags &= ~E2_BTN_DEFAULT; e2_dialog_add_defined_button (dialog, &E2_BUTTON_NOTOALL); e2_dialog_set_negative_response (dialog, E2_RESPONSE_NOTOALL); } E2_BUTTON_CLOSE.showflags |= E2_BTN_DEFAULT; e2_dialog_add_defined_button (dialog, &E2_BUTTON_CLOSE); //enable menu g_signal_connect (G_OBJECT (dialog), "popup-menu", G_CALLBACK (_e2_file_info_dialog_popup_menu_cb), &rt); g_signal_connect (G_OBJECT (dialog), "button-press-event", G_CALLBACK (_e2_file_info_dialog_button_press_cb), &rt); e2_dialog_setup (dialog, app.main_window); gdk_threads_enter (); e2_dialog_run (dialog, app.main_window, 0); gtk_window_present (GTK_WINDOW (dialog)); //sometimes, dialog is not focused gtk_main (); gdk_threads_leave (); return choice; } emelfm2-0.4.1/src/dialogs/e2_password_dialog.h0000600000175000017500000000525510663404721020152 0ustar cairocairo/* $Id: e2_password_dialog.h 624 2007-08-23 22:27:29Z tpgww $ Copyright (C) 2007 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/dialogs/e2_password_dialog.h @brief header file for password dialog */ #include "emelfm2.h" #include "e2_dialog.h" /* typedef struct _E2_PWDialogRuntime { GtkWidget *dialog; //the displayed dialog DialogButtons result; //enumerator of what the user chose gboolean destroy; //TRUE to destroy dialog and data after user's choice GtkWidget *pwentry1; GtkWidget *pwentry2; // GtkWidget *table; //pointer to tablular layuout, if that's used gboolean confirm; //TRUE when requiring matching contents in pwentry1 and pwentr2 gboolean hint; //TRUE when the last char in either used entry is displayed as plaintext gboolean hide; //TRUE when no entered char is to be echoed gboolean plain; //TRUE when the entered text is displayed as is gchar **passwd; //place to store entered (and if relevant matched) plaintext password } E2_PWDialogRuntime; E2_PWDialogRuntime *e2_password_dialog_setup (gchar *title, gchar *stock, gchar *prompt, gboolean confirm, gboolean tabular, OW_ButtonFlags extras, gchar **pw, gboolean destroy); //UNUSED DialogButtons e2_password_dialog_run (gboolean threaded); */ typedef struct _E2_PWDataRuntime { GtkWidget *pwentry1; GtkWidget *pwentry2; // GtkWidget *table; //pointer to tablular layout, NULL if that's not used gboolean confirm; //TRUE when requiring matching contents in pwentry1 and pwentr2 gboolean hint; //TRUE when the last char in either used entry is displayed as plaintext gboolean hide; //TRUE when no entered char is to be echoed gboolean plain; //TRUE when the entered text is displayed as is gchar **passwd; //place to store entered (and if relevant matched) plaintext password } E2_PWDataRuntime; E2_PWDataRuntime *e2_password_dialog_setup (GtkWidget *box, gboolean confirm, //gboolean tabular, gchar *mainprompt, gchar **pw); gboolean e2_password_dialog_confirm (E2_PWDataRuntime *rt); void e2_password_dialog_backup (E2_PWDataRuntime *rt); emelfm2-0.4.1/src/dialogs/e2_ownership_dialog.c0000600000175000017500000003267111007760641020322 0ustar cairocairo/* $Id: e2_ownership_dialog.c 868 2008-05-06 04:42:09Z tpgww $ Copyright (C) 2003-2008 tooar Portions copyright (C) 1999 Michael Clark. This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "emelfm2.h" //#include #include #include #include "e2_dialog.h" #include "e2_ownership_dialog.h" typedef struct _E2_OwnersDldRuntime { GtkWidget *user_combo; GtkWidget *group_combo; GtkWidget *recurse_dirs_button; uid_t owner; gid_t group; gboolean recurse; gboolean permission; DialogButtons result; } E2_OwnersDldRuntime; /** @brief get user id corresponing to text entered into the dialog @param rt pointer to dialog data struct @return the id */ static uid_t _e2_ownership_dialog_get_user_id (E2_OwnersDldRuntime *rt) { gchar *user; struct passwd *pw_buf; uid_t user_id; user = (gchar *) gtk_entry_get_text (GTK_ENTRY (GTK_BIN (rt->user_combo)->child)); user = e2_utf8_to_locale (user); if (user != NULL) { if ((pw_buf = getpwnam (user)) != NULL) user_id = pw_buf->pw_uid; else { user_id = (uid_t) strtol (user, NULL, 10); if (errno == EINVAL) { //FIXME user 0 probably not relevant } } g_free (user); } else user_id = 0; //FIXME return user_id; } /** @brief get group id corresponing to text entered into the dialog @param rt pointer to dialog data struct @return the id */ static gid_t _e2_ownership_dialog_get_group_id (E2_OwnersDldRuntime *rt) { gchar *group; struct group *grp_buf; gid_t group_id; group = (gchar *) gtk_entry_get_text (GTK_ENTRY (GTK_BIN (rt->group_combo)->child)); group = e2_utf8_to_locale (group); if (group != NULL) { if ((grp_buf = getgrnam (group)) != NULL) group_id = grp_buf->gr_gid; else { group_id = (gid_t) strtol (group, NULL, 10); if (errno == EINVAL) { //FIXME group 0 probably not relevant } } g_free (group); } else group_id = 0; return group_id; } /** @brief dialog response callback @param dialog the ownership-dialog @param response the response for the clicked button @param rt pointer to dialog data struct @return */ static void _e2_ownership_dialog_response_cb (GtkDialog *dialog, gint response, E2_OwnersDldRuntime *rt) { switch (response) { case GTK_RESPONSE_OK: case E2_RESPONSE_APPLYTOALL: rt->owner = _e2_ownership_dialog_get_user_id (rt); rt->group = _e2_ownership_dialog_get_group_id (rt); break; default: break; } gtk_widget_destroy (GTK_WIDGET (dialog)); e2_dialog_response_decode_cb (dialog, response, &rt->result); } /** @brief update recurse flag if permission is valid @param togglebutton the clicked button @param rt pointer to dialog data struct @return */ static void _e2_ownership_dialog_toggle_recurse_button_cb (GtkToggleButton *togglebutton, E2_OwnersDldRuntime *rt) { if (rt->permission) rt->recurse = GTK_TOGGLE_BUTTON (rt->recurse_dirs_button)->active; return; } /** @brief show custom message about inadequate permission @param box the box to hold the data @return */ void e2_ownership_dialog_warn (GtkWidget *box) { GtkWidget *frame = gtk_frame_new (""); gtk_box_pack_start (GTK_BOX (box), frame, TRUE, TRUE, E2_PADDING); gtk_widget_show (frame); GtkWidget *sub_vbox = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (frame), sub_vbox); gtk_widget_show (sub_vbox); GtkWidget *hbox = gtk_hbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (sub_vbox), hbox); e2_widget_add_mid_label (hbox, _("You are not authorised to make any change."), 0.5, TRUE, E2_PADDING*2); gtk_widget_show (hbox); //padding line hbox = gtk_hbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (sub_vbox), hbox); e2_widget_add_mid_label (hbox, (" "), 0, FALSE, 0); gtk_widget_show (hbox); } /** @brief create and run a dialog for changing item permissions This is run from inside a queued thread-function @param localpath path of item to be processed @param owner_ret store for returning the new owner @param group_ret store for returning the new group @param recurse_ret store for returning whether to recurse the changes @param permission_ret store for returning whether a change is authorised @param multi TRUE if this dialog is part of a series for multiple items @return enumerator corresponing to the clicked dialog button */ DialogButtons e2_ownership_dialog_run (VPATH *localpath, uid_t *owner_ret, gid_t *group_ret, gboolean *recurse_ret, gboolean *permission_ret, gboolean multi) { GtkWidget *ownership_dialog; GtkWidget *dialog_vbox; GtkWidget *action_area; GtkWidget *table, *hbox; // E2_OwnersDldRuntime *rt = ALLOCATE (E2_OwnersDldRuntime); // CHECKALLOCATEDWARN (rt, return GTK_RESPONSE_CANCEL); E2_OwnersDldRuntime rt; struct stat statbuf; if (e2_fs_lstat (localpath, &statbuf E2_ERR_NONE())) return CANCEL; GString *label_text = g_string_sized_new (NAME_MAX+20); gboolean thisis_dir = e2_fs_is_dir3 (localpath E2_ERR_NONE()); ownership_dialog = e2_dialog_create (NULL, NULL, _("ownership"), _e2_ownership_dialog_response_cb, &rt); //rt); dialog_vbox = GTK_DIALOG (ownership_dialog)->vbox; action_area = GTK_DIALOG (ownership_dialog)->action_area; //this is for the buttons gtk_container_set_border_width (GTK_CONTAINER (dialog_vbox), E2_PADDING); // table = e2_widget_add_table (dialog_vbox, 2, 3, TRUE, TRUE, 0); //2 rows, 3 cols, homogen, fill, no pad // gtk_table_set_row_spacing (GTK_TABLE(table), 0, E2_PADDING); gchar *label = (thisis_dir) ? _("Directory name") : _("Filename") ; gchar *name = g_filename_display_basename (VPSTR (localpath)); g_string_printf (label_text, "%s: %s", label, name); g_free (name); //e2_widget_add_mid_label_to_table (table, label_text->str, 0, 1, 2, 0, 1); hbox = gtk_hbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX(dialog_vbox), hbox, TRUE, TRUE, E2_PADDING); //top, bottom padding e2_widget_add_mid_label (hbox, label_text->str, 0, TRUE, E2_PADDING); //L, R padding gtk_widget_show (hbox); gchar *s = e2_fs_get_perm_string (statbuf.st_mode); g_string_assign (label_text, s); g_free (s); g_string_insert_c (label_text, e2_utils_get_byte_position (label_text->str, 7), ' '); g_string_insert_c (label_text, e2_utils_get_byte_position (label_text->str, 4), ' '); g_string_insert_c (label_text, e2_utils_get_byte_position (label_text->str, 1), ' '); g_string_prepend (label_text, _("Permissions: ")); // e2_widget_add_mid_label_to_table (table, label_text->str, 0, 1, 2, 1, 2); hbox = gtk_hbox_new (FALSE, 0); e2_widget_add_mid_label (hbox, label_text->str, 0, TRUE, E2_PADDING); gtk_box_pack_start (GTK_BOX(dialog_vbox), hbox, TRUE, TRUE, 0); gtk_widget_show (hbox); g_string_free (label_text, TRUE); table = e2_widget_add_table (dialog_vbox, 2, 3, TRUE, TRUE, 0); //2 rows, 3 cols, homogen, fill, no pad gtk_container_set_border_width (GTK_CONTAINER (table), E2_PADDING); gtk_table_set_row_spacings (GTK_TABLE (table), E2_PADDING); e2_widget_add_mid_label_to_table (table, _("User: "), 0, 0, 1, 0, 1); rt.user_combo = e2_combobox_get (NULL, NULL, NULL, E2_COMBOBOX_HAS_ENTRY | E2_COMBOBOX_NO_AUTO_HISTORY | E2_COMBOBOX_CYCLE_HISTORY); gtk_table_attach_defaults (GTK_TABLE (table), rt.user_combo, 1, 3, 0, 1); // gint left col, gint right col, gint top row, gint bottom row) e2_widget_add_mid_label_to_table (table, _("Group: "), 0, 0, 1, 1, 2); rt.group_combo = e2_combobox_get (NULL, NULL, NULL, E2_COMBOBOX_HAS_ENTRY | E2_COMBOBOX_NO_AUTO_HISTORY | E2_COMBOBOX_CYCLE_HISTORY); gtk_table_attach_defaults (GTK_TABLE (table), rt.group_combo, 1, 3, 1, 2); { //setup the combobox data struct passwd *pw_buf; struct group *grp_buf; gchar *utf; gchar id_buf[32]; gint ctr = 0; //combo box entry counter, set to 1st value in list, for now gint user_index = 0, grp_index = 0; //default values for combobox display (= 1st entry) uid_t src_uid = statbuf.st_uid; gint myuid = getuid (); setpwent (); //path is to current dir gboolean writeok = e2_fs_check_write_permission (localpath E2_ERR_NONE()); if (writeok) { rt.permission = TRUE; //confirm ok to proceed while ((pw_buf = getpwent ()) != NULL) { //screen out superfluous names if ( (myuid > 0) //user is not root && ((guint) pw_buf->pw_uid > 0) //buffer entry is not root's && ((guint) pw_buf->pw_uid < UID_LOWLIMIT) //but is less than the system-user threshold ) continue; utf = e2_utf8_from_locale (pw_buf->pw_name); if (utf == NULL) utf = g_strdup_printf ("%d", (guint) pw_buf->pw_uid); gtk_combo_box_append_text (GTK_COMBO_BOX (rt.user_combo), utf); g_free (utf); //remember the combo index for the owner if ((uid_t) pw_buf->pw_uid == src_uid) user_index = ctr; else ctr++; } if ((pw_buf = getpwuid (statbuf.st_uid)) == NULL) { g_snprintf (id_buf, sizeof (id_buf), "%d", (guint) statbuf.st_uid); gtk_combo_box_append_text (GTK_COMBO_BOX (rt.user_combo), id_buf); user_index = ctr; } printd (DEBUG, "owner combo index = %d", user_index); ctr = 0; gid_t src_gid = statbuf.st_gid; if (myuid == 0) { while ((grp_buf = getgrent ()) != NULL) { utf = e2_utf8_from_locale (grp_buf->gr_name); if (utf == NULL) utf = g_strdup_printf ("%d", (guint) grp_buf->gr_gid); gtk_combo_box_append_text (GTK_COMBO_BOX (rt.group_combo), utf); g_free (utf); if ((gid_t) grp_buf->gr_gid == src_gid) grp_index = ctr; else ctr++; } } else //not root { gid_t grp_ids[REPORTABLE_GROUPS]; gint n = getgroups (REPORTABLE_GROUPS, grp_ids); for (ctr = 0; ctr < n; ctr++) { if ((grp_buf = getgrgid (grp_ids[ctr])) != NULL) { utf = e2_utf8_from_locale (grp_buf->gr_name); if (utf == NULL) utf = g_strdup_printf ("%d", (guint) grp_buf->gr_gid); gtk_combo_box_append_text (GTK_COMBO_BOX (rt.group_combo), utf); g_free (utf); if ((gid_t) grp_buf->gr_gid == src_gid) grp_index = ctr; } } } if ((grp_buf = getgrgid (statbuf.st_gid)) == NULL) { g_snprintf (id_buf, sizeof (id_buf), "%d", (guint) statbuf.st_gid); gtk_combo_box_append_text (GTK_COMBO_BOX (rt.group_combo), id_buf); grp_index = ctr; } printd (DEBUG, "group combo index = %d", grp_index); } else //no write permission, no changes allowed, just show current values { rt.permission = FALSE; //signal not ok to proceed, even if ok is clicked if ((pw_buf = getpwuid (statbuf.st_uid)) != NULL) gtk_combo_box_append_text (GTK_COMBO_BOX (rt.user_combo), g_strdup (pw_buf->pw_name)); else { g_snprintf (id_buf, sizeof (id_buf), "%d", (guint) statbuf.st_uid); gtk_combo_box_append_text (GTK_COMBO_BOX (rt.user_combo), id_buf); } if ((grp_buf = getgrgid (statbuf.st_gid)) != NULL) gtk_combo_box_append_text (GTK_COMBO_BOX (rt.group_combo), g_strdup (grp_buf->gr_name)); else { g_snprintf (id_buf, sizeof (id_buf), "%d", (guint) statbuf.st_gid); gtk_combo_box_append_text (GTK_COMBO_BOX (rt.group_combo), id_buf); } gtk_widget_set_sensitive (rt.user_combo, FALSE); gtk_widget_set_sensitive (rt.group_combo, FALSE); } gtk_combo_box_set_active (GTK_COMBO_BOX (rt.user_combo), user_index); gtk_combo_box_set_active (GTK_COMBO_BOX (rt.group_combo), grp_index); setpwent (); setgrent (); } if (rt.permission && thisis_dir) //note: no recurse button now if multiple-selection { table = e2_widget_add_table (dialog_vbox, 1, 3, TRUE, TRUE, 0); //1 row, 3 cols, homogen, fill, no pad gtk_table_set_row_spacing (GTK_TABLE(table), 0, E2_PADDING); rt.recurse_dirs_button = e2_button_add_toggle_to_table (table, _("Recurse"), FALSE, _e2_ownership_dialog_toggle_recurse_button_cb, &rt, //rt, 1, 2, 0, 1 ); // gint left, gint right, gint top, gint bottom } if (!rt.permission) e2_ownership_dialog_warn (dialog_vbox); //add buttons in the order that they will appear GtkWidget *btn; if (multi) { e2_dialog_set_negative_response (ownership_dialog, E2_RESPONSE_NOTOALL); e2_dialog_add_defined_button (ownership_dialog, &E2_BUTTON_NOTOALL); btn = e2_dialog_add_button_custom (ownership_dialog, FALSE, &E2_BUTTON_APPLYTOALL, NULL, NULL, NULL); if (!rt.permission) gtk_widget_set_sensitive (btn, FALSE); } if (rt.permission || multi) { btn = e2_dialog_add_defined_button (ownership_dialog, &E2_BUTTON_CANCEL); if (!rt.permission) gtk_widget_set_sensitive (btn, FALSE); } e2_dialog_add_button_custom (ownership_dialog, TRUE, &E2_BUTTON_OK, NULL, NULL, NULL); e2_dialog_setup (ownership_dialog, app.main_window); /* non-modal not supported e2_dialog_run (ownership_dialog, NULL, E2_DIALOG_NONMODAL | E2_DIALOG_THREADED | E2_DIALOG_FREE); */ gdk_threads_enter (); e2_dialog_run (ownership_dialog, NULL, 0); gtk_main (); gdk_threads_leave (); // DialogButtons result = rt.result; // if (result == OK || result == YES_TO_ALL) if (rt.result == OK || rt.result == YES_TO_ALL) { *owner_ret = rt.owner; *group_ret = rt.group; *recurse_ret = rt.recurse; *permission_ret = rt.permission; } // DEALLOCATE (E2_OwnersDldRuntime, rt); // return result; return rt.result; } emelfm2-0.4.1/src/dialogs/e2_mkdir_dialog.h0000600000175000017500000000305711010340377017405 0ustar cairocairo/* $Id: e2_mkdir_dialog.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_MKDIR_DIALOG_H__ #define __E2_MKDIR_DIALOG_H__ #include "emelfm2.h" #include "e2_task.h" typedef struct _E2_MkdirDialogRuntime { GtkWidget *dialog; GtkWidget *scrolled; GtkWidget *combo; GtkWidget *info_expander; // GtkWidget *info_box; GtkWidget *info_label; GtkWidget *info_label2; GtkWidget *menu; // GtkWidget *create_btn; gchar *path; //utf-8 encoded GList *history; gint history_cur; gboolean creation_possible; gboolean opt_show_last; gboolean opt_suggest_dir; guint idle_id; E2_TaskStatus *status; //pointer to Q status indicator } E2_MkdirDialogRuntime; GtkWidget *e2_mkdir_dialog_create (); void e2_mkdir_dialog_show (); void e2_mkdir_dialog_actions_register (); void e2_mkdir_dialog_options_register (); #endif //ndef __E2_MKDIR_DIALOG_H__ emelfm2-0.4.1/src/dialogs/e2_view_dialog.c0000600000175000017500000017511011007727330017250 0ustar cairocairo/* $Id: e2_view_dialog.c 866 2008-05-06 01:05:28Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "emelfm2.h" #include #include "e2_dialog.h" #include "e2_view_dialog.h" #include "e2_textiter.h" #include "e2_task.h" //stores for session-static and/or cached variables gboolean case_sensitive; gboolean search_backward; gboolean whole_words; gboolean search_wrap; GList *find_history = NULL; GList *view_history = NULL; //keycodes corresponding to mnemonics for translated labels guint find_keycode; guint hide_keycode; //we need this one because of widget-sharing with the edit dialog //value set to 0 for a view dialog extern guint replace_keycode; static void _e2_view_dialog_response_cb (GtkDialog *dialog, gint response, E2_ViewDialogRuntime *rt); static gboolean _e2_view_dialog_reviewQ (E2_ActionTaskData *qed); static gboolean _e2_view_dialog_viewatQ (E2_ActionTaskData *qed); /**************************/ /**** search functions ****/ /**************************/ #ifdef E2_MARK_FINDS /** @brief buffer-changed callback This is to turn off all highlighting of matched strings @param textbuffer the textbuffer which was changed @param rt dialog runtime data struct @return */ static void _e2_view_dialog_buffer_changed_cb (GtkTextBuffer *textbuffer, E2_ViewDialogRuntime *rt) { e2_view_dialog_clear_hilites (rt); } /** @brief setup to highlight all occurrences of a string in the buffer @param rt dialog runtime data struct @return */ void e2_view_dialog_init_hilites (E2_ViewDialogRuntime *rt) { gtk_text_buffer_create_tag (rt->textbuffer, "matched", "background", e2_option_str_get ("color-highlight"), NULL); rt->is_lit = FALSE; } /** @brief by forward scan, highlight all occurrences of a string in the buffer The current state of the case-insensitive flag is used when searching @param searchstr the string to find @param rt dialog runtime data struct @return */ static void _e2_view_dialog_set_hilites (const gchar *searchstr, E2_ViewDialogRuntime *rt) { static gchar *prior_search = NULL; //when some search-options are changed, need to cleanup first, then re-search if (rt->research) rt->research = FALSE; //avoid repeated scans for the same string else if (rt->is_lit && g_str_equal (searchstr, prior_search)) return; //clear anything from last scan e2_view_dialog_clear_hilites (rt); if (prior_search != NULL) g_free (prior_search); prior_search = g_strdup (searchstr); GtkTextIter start, end, iter; gtk_text_buffer_get_bounds (rt->textbuffer, &start, &end); E2TextSearchFlags flags = E2_SEARCH_TEXT_ONLY; //superset of GtkTextSearchFlags if (!rt->case_sensitive) flags |= E2_SEARCH_CASE_INSENSITIVE; if (rt->whole_words) flags |= E2_SEARCH_WHOLE_WORD; gboolean found = FALSE; gboolean any = !rt->whole_words; iter = start; while (e2_iter_forward_search (&iter, searchstr, flags, &start, &end, NULL)) { //as we only check for word-starts in the search func, ensure end is ok if relevant if (any || gtk_text_iter_ends_word (&end)) { found = TRUE; gtk_text_buffer_apply_tag_by_name (rt->textbuffer, "matched", &start, &end); } iter = end; } rt->is_lit = found; if (found && rt->replacebar != NULL) //in edit mode g_signal_connect (G_OBJECT (rt->textbuffer), "changed", G_CALLBACK(_e2_view_dialog_buffer_changed_cb), rt); } /** @brief extinguish highlighting of matched strings @param rt dialog runtime data struct @return */ void e2_view_dialog_clear_hilites (E2_ViewDialogRuntime *rt) { if (rt->is_lit) { GtkTextIter start, end; gtk_text_buffer_get_bounds (rt->textbuffer, &start, &end); gtk_text_buffer_remove_tag_by_name (rt->textbuffer, "matched", &start, &end); rt->is_lit = FALSE; if (rt->replacebar != NULL) //in edit mode g_signal_handlers_disconnect_by_func (G_OBJECT (rt->textbuffer), _e2_view_dialog_buffer_changed_cb, rt); } } #endif //def E2_MARK_FINDS /** @brief update display of entry for @a combo, when it is (re)displayed @param combo combobox widget to be processed @return */ void e2_view_dialog_update_combo (GtkWidget *combo) { if (e2_option_bool_get ("dialog-search-show-last") && e2_combobox_has_history (GTK_COMBO_BOX (combo))) gtk_editable_select_region (GTK_EDITABLE (GTK_BIN (combo)->child), 0, -1); else gtk_entry_set_text (GTK_ENTRY (GTK_BIN (combo)->child), ""); } /** @brief set textbuffer marks related to searching @param buffer textbuffer to be searched @param start pointer to textiter at location for start-mark @param end pointer to textiter at location for end-mark @return */ static void _e2_view_dialog_attach_search_iters (GtkTextBuffer *buffer, GtkTextIter *start, GtkTextIter *end) { GtkTextMark *mark = gtk_text_buffer_get_mark (buffer, "e2-search-start"); if (mark == NULL) gtk_text_buffer_create_mark (buffer, "e2-search-start", start, FALSE); else gtk_text_buffer_move_mark (buffer, mark, start); mark = gtk_text_buffer_get_mark (buffer, "e2-search-end"); if (mark == NULL) gtk_text_buffer_create_mark (buffer, "e2-search-end", end, FALSE); else gtk_text_buffer_move_mark (buffer, mark, end); } /** @brief clear textbuffer marks related to searching, if they exist @param buffer textbuffer which has been searched @return */ static void _e2_view_dialog_unattach_search_iters (GtkTextBuffer *buffer) { GtkTextMark *mark = gtk_text_buffer_get_mark (buffer, "e2-search-start"); if (mark != NULL) gtk_text_buffer_delete_mark (buffer, mark); mark = gtk_text_buffer_get_mark (buffer, "e2-search-end"); if (mark != NULL) gtk_text_buffer_delete_mark (buffer, mark); } /** @brief initiate search process after setting relevant flags @param search_iter pointer to iter where the search is to start @param start pointer to iter to store the start of a matched string @param end pointer to iter to store the end of a matched string @param find the string to search for, may include 1 or more '\n' @param incremental TRUE when performing an incremental search after a search-string keypress @param rt pointer to dialog runtime data struct @return TRUE if a match was found */ static gboolean _e2_view_dialog_dosearch (GtkTextIter *search_iter, GtkTextIter *start, GtkTextIter *end, const gchar *find, gboolean incremental, E2_ViewDialogRuntime *rt) { E2TextSearchFlags flags = E2_SEARCH_TEXT_ONLY; //superset of GtkTextSearchFlags if (!rt->case_sensitive) flags |= E2_SEARCH_CASE_INSENSITIVE; if (rt->whole_words) flags |= E2_SEARCH_WHOLE_WORD; if (rt->search_backward) { //try for incremental match of a current string in same place //FIXME find a more elegant way of doing this GtkTextIter next = *search_iter; if (incremental && !gtk_text_iter_forward_line (&next)) next = *search_iter; //revert after failure return (e2_iter_backward_search (&next, find, flags, start, end, NULL)); } else return (e2_iter_forward_search (search_iter, find, flags, start, end, NULL)); } /** @brief try to find string and update GUI accordingly @param first TRUE to initiate scanning from start or end of text, FALSE to "find next" @param incremental TRUE when performing an incremental scan after a match-string keypress @param rt pointer to dialog runtime data struct @return TRUE if a match was found */ gboolean e2_view_dialog_search (gboolean first, gboolean incremental, E2_ViewDialogRuntime *rt) { GtkTextIter iter; // get the find string GtkWidget *entry = GTK_BIN (rt->combo)->child; const gchar *find = gtk_entry_get_text (GTK_ENTRY (entry)); if (*find == '\0') { #ifdef E2_MARK_FINDS e2_view_dialog_clear_hilites (rt); #endif //hide any selection if (gtk_text_buffer_get_selection_bounds (rt->textbuffer, &iter, NULL)) gtk_text_buffer_select_range (rt->textbuffer, &iter, &iter); return FALSE; } if (!incremental) { #ifdef E2_MARK_FINDS _e2_view_dialog_set_hilites (find, rt); #endif e2_combobox_activated_cb (entry, GINT_TO_POINTER (FALSE)); e2_list_update_history (find, &find_history, NULL, 30, FALSE); } #ifdef E2_MARK_FINDS else //turn off any highlihts e2_view_dialog_clear_hilites (rt); #endif //check if the TextView's buffer has changed and get new TextIters GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (rt->textview)); /* if (buffer != rt->textbuffer) { rt->textbuffer = buffer; unattach_iters (buffer); search_iter = NULL; } */ GtkTextMark *mark = NULL; if (first) { //searching starts from cursor, so move that _e2_view_dialog_unattach_search_iters (buffer); //(re)start searching if (rt->search_backward) gtk_text_buffer_get_end_iter (buffer, &iter); else gtk_text_buffer_get_start_iter (buffer, &iter); gtk_text_buffer_place_cursor (buffer, &iter); } else { //searching from cursor, not from start or end of buffer gboolean fromstart = (incremental) ? TRUE : rt->search_backward; if (fromstart) mark = gtk_text_buffer_get_mark (buffer, "e2-search-start"); else mark = gtk_text_buffer_get_mark (buffer, "e2-search-end"); } GtkTextIter *search_iter = NULL; if (mark != NULL) { gtk_text_buffer_get_iter_at_mark (buffer, &iter, mark); search_iter = &iter; } if (search_iter == NULL) { //search from cursor mark = gtk_text_buffer_get_mark (buffer, "insert"); gtk_text_buffer_get_iter_at_mark (buffer, &iter, mark); search_iter = &iter; /* MAYBE search from top/bottom of window if cursor is off-window ? GdkRectangle visible; gtk_text_view_get_visible_rect (GTK_TEXT_VIEW (rt->textview), &visible); if (rt->opt_search_backward) //search backward from end of screen gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (rt->textview), &iter, visible.width, visible.height); else //search forward from top of screen gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (rt->textview), &iter, visible.x, visible.y); */ } // search for the string GtkTextIter start, end; gboolean found = _e2_view_dialog_dosearch (search_iter, &start, &end, find, incremental, rt); if (!found && rt->search_wrap) { _e2_view_dialog_unattach_search_iters (buffer); if (rt->search_backward) gtk_text_buffer_get_end_iter (buffer, &iter); else gtk_text_buffer_get_start_iter (buffer, &iter); found = _e2_view_dialog_dosearch (&iter, &start, &end, find, incremental, rt); } if (found) { gtk_widget_hide (rt->info_label); gtk_text_buffer_select_range (buffer, &start, &end); //always show where we are now _e2_view_dialog_attach_search_iters (buffer, &start, &end); //scroll found position into window, if not there already GdkRectangle visible_rect; //, iter_rect; gtk_text_view_get_visible_rect (GTK_TEXT_VIEW (rt->textview), &visible_rect); gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (rt->textview), &iter, visible_rect.x, visible_rect.y); gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (rt->textview), &end, visible_rect.x + visible_rect.width, visible_rect.y + visible_rect.height); if (!gtk_text_iter_in_range (&start, &iter, &end)) /* alternative approach, using vertical check only gtk_text_view_get_iter_location (GTK_TEXT_VIEW (rt->textview), &start, &iter_rect); if ((iter_rect.y + iter_rect.height) > (visible_rect.y + visible_rect.height) || iter_rect.y < visible_rect.y) */ gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (rt->textview), &start, 0.0, TRUE, 0.1, (rt->search_backward) ? 0.8 : 0.2); return TRUE; } gtk_widget_show (rt->info_label); return FALSE; } /*****************/ /***** utils *****/ /*****************/ /** @brief read text file to be viewed This is used by both view and edit dialogs Assumes the item being read is in curr_view i.e. no cd race from queueing Character encoding is performed if necessary and possible. @a rt ->filepath has the localised name of the file to be read. It will be replaced by a newly-allocated string, to be freed elsewhere Error messages and any macro-instigated dialog expect BGL open @param localfile name of item to read, localised text, not neccessarily absolute @param rt dialog runtime data struct @return TRUE if the read and conversion was successful */ gboolean e2_view_dialog_read_text (VPATH *localfile, E2_ViewDialogRuntime *rt) { E2_ERR_DECLARE gchar *localpath; if (!g_path_is_absolute (VPSTR (localfile))) localpath = g_build_filename (curr_view->dir, VPSTR (localfile), NULL); else localpath = g_strdup (VPSTR (localfile)); //copy in case we want to keep it #ifdef E2_VFS VPATH ddata = { localpath, localfile->spacedata }; if (e2_fs_access (&ddata, R_OK E2_ERR_PTR())) //traverse a link #else if (e2_fs_access (localpath, R_OK E2_ERR_PTR())) //traverse a link #endif { e2_fs_error_local (_("Cannot read '%s'"), #ifdef E2_VFS &ddata E2_ERR_MSGL()); #else localpath E2_ERR_MSGL()); #endif E2_ERR_CLEAR g_free (localpath); return FALSE; } #ifdef E2_VFS if (e2_fs_is_dir3 (&ddata E2_ERR_NONE())) #else if (e2_fs_is_dir3 (localpath E2_ERR_NONE())) #endif { e2_fs_error_simple (_("'%s' is a directory"), #ifdef E2_VFS &ddata); #else localpath); #endif g_free (localpath); return FALSE; } gboolean usable; gpointer contents; gchar *converter = NULL; if (e2_option_bool_get ("use-external-encoder")) { converter = e2_option_str_get ("command-encoder"); if (*converter != '\0') { gchar *command; #ifdef E2_VFSTMP //can't sniff non-local files #endif //sniff the file to check if conversion is really needed //this is not too good for files with mixed encoding ! command = g_strconcat ("cat ", localpath, NULL); if (e2_fs_sniff_command_output (command, &contents, 1024)) { usable = e2_utf8_detect_charset ((guchar *)contents, &rt->charset); g_free (contents); } else usable = FALSE; g_free (command); if (!usable) { //we need to do a conversion //substitute file for any %f or %p in command string, get language etc gdk_threads_enter (); //in case there's a dialog-macro in the command command = e2_utils_expand_macros (converter, localpath); gdk_threads_leave (); if (command == NULL || command == GINT_TO_POINTER(1)) { g_free (localpath); return FALSE; } else { rt->charset = "UNKNOWN"; //no translation if (!e2_fs_get_command_output (command, &contents)) { gchar *msg = g_strdup_printf (_("Encoding conversion command '%s' failed"), converter); gdk_threads_enter (); e2_output_print_error (msg, TRUE); gdk_threads_leave (); converter = NULL; //set flag for internal conversion fallback } g_free (command); } } else //no external conversion needed converter = NULL; } else converter = NULL; } if (converter == NULL //not externally converted #ifdef E2_VFS && !e2_fs_get_file_contents (&ddata, &contents, NULL, TRUE E2_ERR_PTR())) #else && !e2_fs_get_file_contents (localpath, &contents, NULL, TRUE E2_ERR_PTR())) #endif { e2_fs_error_local (_("Error reading file %s"), #ifdef E2_VFS &ddata E2_ERR_MSGL()); #else localpath E2_ERR_MSGL()); #endif E2_ERR_CLEAR g_free (localpath); return FALSE; } //fix CR's in the text if necessary rt->linebreak = e2_utils_LF_line_ends ((gchar *)contents); //fix character encoding if necessary and possible if (converter == NULL) //not externally converted { if (*((gchar *)contents) != '\0') { gchar *format = _("Conversion from %s encoding failed: \"%s\""); gchar *msg, *utf; GError *utf_error; usable = e2_utf8_detect_charset ((guchar *)contents, &rt->charset); if (!usable) { //convert using the identified encoding if we can //but no point in 'non-conversion' ... if (strstr (rt->charset, "UTF-8") == NULL) { utf_error = NULL; utf = g_convert ((gchar *)contents, -1, "UTF-8", rt->charset, NULL, NULL, &utf_error); if (utf != NULL) { printd (DEBUG, "converted content from %s to UTF8", rt->charset); usable = TRUE; g_free (contents); //need free() if file buffer allocated by malloc() contents = utf; } else { printd (DEBUG, "FAILED conversion of content from %s to UTF8", rt->charset); if (!g_str_equal (rt->charset, "CP1252")) //that's generally a default when we can't find the real value { msg = g_strdup_printf (format, rt->charset, utf_error->message); gdk_threads_enter (); e2_output_print_error (msg, TRUE); gdk_threads_leave (); } g_error_free (utf_error); } } } if (!usable) { //try default coding as a fallback const gchar *defset; e2_utils_get_charset (&defset); //again, no point in 'non-conversion' if (strstr (defset, "UTF-8") != NULL) defset = e2_cl_options.fallback_encoding; if (strstr (defset, "UTF-8") != NULL) defset = "ISO-8859-1"; printd (DEBUG, "Conversion from %s encoding to be attempted", defset); utf_error = NULL; utf = g_convert ((gchar *)contents, -1, "UTF-8", defset, NULL, NULL, &utf_error); if (utf == NULL) { msg = g_strdup_printf (format, defset, utf_error->message); gdk_threads_enter (); e2_output_print_error (msg, TRUE); gdk_threads_leave (); g_error_free (utf_error); g_free (contents); //need free() if file buffer allocated by malloc() return FALSE; } else if (!g_utf8_validate (utf, -1, NULL)) { g_free (utf); g_free (contents); //need free() if file buffer allocated by malloc() return FALSE; } rt->charset = "UNKNOWN"; //prevent attempts at saving to the unusable charset g_free (contents); //need free() if file buffer allocated by malloc() contents = utf; } } else //assume default encoding for empty files { e2_utils_get_charset (&rt->charset); // printd (DEBUG, "empty file, assume default charset %s", rt->charset); } } if (rt->localpath != NULL) g_free (rt->localpath); rt->localpath = localpath; //we're ok, so remember the real path #ifdef E2_VFS rt->spacedata = localfile->spacedata; #endif //for some reason, the buffer sometimes takes a while to be created while (!GTK_IS_TEXT_BUFFER (rt->textbuffer)) rt->textbuffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (rt->textview)); gtk_text_buffer_set_text (rt->textbuffer, (gchar *)contents, -1); g_free (contents); //put cursor at start of buffer, for searching etc GtkTextIter start; gtk_text_buffer_get_start_iter (rt->textbuffer, &start); gtk_text_buffer_place_cursor (rt->textbuffer, &start); printd (DEBUG, "Read text file charset is %s", rt->charset); return TRUE; } /** @brief set dialog font and get size of a typical character in that font @param char_width store for character width for font @param char_height store for character height for font @param rt pointer to dialog data struct @return */ void e2_view_dialog_set_font (gint *char_width, gint *char_height, E2_ViewDialogRuntime *rt) { gchar *fntname; if (e2_option_bool_get ("dialog-view-use-font")) { fntname = e2_option_str_get ("dialog-view-font"); if (*fntname == '\0') fntname = NULL; } else fntname = NULL; if (fntname == NULL) { GtkSettings* defs = gtk_settings_get_default (); g_object_get (G_OBJECT (defs), "gtk-font-name", &fntname, NULL); if (fntname == NULL) //CHECKME needed ? { printd (WARN, "No default font detected"); fntname = "Sans 10"; } } e2_widget_set_font (rt->textview, fntname); e2_widget_get_font_pixels (rt->textview, char_width, char_height); } /** @brief set popup menu position This function is supplied when calling gtk_menu_popup(), to position the displayed menu. Set @a push_in to TRUE for menu completely inside the screen, FALSE for menu clamped to screen size @param menu the GtkMenu to be positioned @param x place to store gint representing the menu left @param y place to store gint representing the menu top @param push_in place to store pushin flag @param rt data struct for dialog in focus when the menu key was pressed @return */ void e2_view_dialog_set_menu_position (GtkWidget *menu, gint *x, gint *y, gboolean *push_in, E2_ViewDialogRuntime *rt) { gint left, top; gtk_window_get_position (GTK_WINDOW (rt->dialog), &left, &top); GtkAllocation alloc = rt->textview->allocation; *x = left + alloc.x + alloc.width/2; *y = top + alloc.y + alloc.height/2; *push_in = FALSE; } #ifdef USE_GTK2_10 /** @brief callback for "begin-print" signal on print-operation for selected or all text @param op the print operation @param context print context for the operation @param rt runtime data struct for the dialog @return */ static void _e2_view_dialog_begin_print_cb (GtkPrintOperation *op, GtkPrintContext *context, E2_ViewDialogRuntime *rt) { GtkTextIter start, end; if (gtk_text_buffer_get_has_selection (rt->textbuffer)) gtk_text_buffer_get_selection_bounds (rt->textbuffer, &start, &end); else gtk_text_buffer_get_bounds (rt->textbuffer, &start, &end); gchar *seltext = gtk_text_buffer_get_text (rt->textbuffer, &start, &end, FALSE); g_strstrip (seltext); if (*seltext != '\0') { PangoLayout *layout = gtk_print_context_create_pango_layout (context); //remember it for other uses g_object_set_data_full (G_OBJECT (context), "e2-print-layout", layout, (GDestroyNotify) g_object_unref); //cleanup when context is destroyed PangoFontDescription *font_desc = gtk_widget_get_style (rt->textview)->font_desc; pango_layout_set_font_description (layout, font_desc); gdouble page_width = gtk_print_context_get_width (context); //in pixels //don't assume printer device-unit is one point gdouble dpi = gtk_print_context_get_dpi_x (context); page_width = page_width * 72 / dpi; //now in points pango_layout_set_width (layout, (gint) page_width * PANGO_SCALE); pango_layout_set_text (layout, seltext, -1); gdouble page_height = gtk_print_context_get_height (context); //in pixels if (!pango_font_description_get_size_is_absolute (font_desc)) { dpi = gtk_print_context_get_dpi_y (context); page_height = page_height * 72 / dpi; //now in points } gint text_height = pango_font_description_get_size (font_desc) / PANGO_SCALE; //in points or pixels gint lines_per_page = page_height / text_height; gint line_count = pango_layout_get_line_count (layout); gint page_count = (line_count - 1) / lines_per_page + 1; //round upwards gtk_print_operation_set_n_pages (op, page_count); //setup page header with filename PangoLayout *layout_name = gtk_print_context_create_pango_layout (context); g_object_set_data_full (G_OBJECT (context), "e2-print-name", layout_name, (GDestroyNotify) g_object_unref); //cleanup when context is destroyed pango_layout_set_font_description (layout_name, font_desc); pango_layout_set_width (layout_name, (gint) page_width * PANGO_SCALE); gchar *base = g_path_get_basename (rt->localpath); gchar *page_title = F_FILENAME_FROM_LOCALE (base); pango_layout_set_text (layout_name, page_title, -1); g_free (base); F_FREE (page_title); //and for page counter, if > 1 page if (page_count > 1) { PangoLayout *layout_num = gtk_print_context_create_pango_layout (context); pango_layout_set_font_description (layout_num, font_desc); // pango_layout_set_alignment (layout_right, PANGO_ALIGN_RIGHT); g_object_set_data_full (G_OBJECT (context), "e2-print-page", layout_num, (GDestroyNotify) g_object_unref); //cleanup when context is destroyed } } g_free (seltext); } /* * @brief callback for "end-print" signal on print-operation for selected or all text @param op UNUSDED the print operation @param context print context for the operation @param data UNUSED data specified when callback was connected @return */ /*static void _e2_view_dialog_end_print_cb (GtkPrintOperation *op, GtkPrintContext *context, gpointer data) { } */ /** @brief callback for "draw-page" signal on print-operation for selected or all text @param op UNUSDED the print operation @param context print context for the operation @param page_num 0-based index of printed pages count @param rt runtime data struct for the dialog @return */ static void _e2_view_dialog_draw_page_cb (GtkPrintOperation *op, GtkPrintContext *context, gint page_num, E2_ViewDialogRuntime *rt) { // PangoLayoutLine *line, *layout_lh, *layout_rh; cairo_t *cr = gtk_print_context_get_cairo_context (context); if (cr != NULL) { gdouble dpi; //print header, comprising filename, and page-number if > 1 page PangoFontDescription *font_desc = gtk_widget_get_style (rt->textview)->font_desc; //CHECKME dpi_v instead of 72 ? cairo_move_to (cr, 0, - 10 * 72 / 25.4); //left margin, up 10 mm from top of text PangoLayout *layout = g_object_get_data (G_OBJECT (context), "e2-print-name"); pango_cairo_show_layout (cr, layout); gint n_pages; g_object_get (G_OBJECT (op), "n-pages", &n_pages, NULL); if (n_pages > 1) { PangoLayout *layout_right = g_object_get_data (G_OBJECT (context), "e2-print-page"); gchar *page_text = g_strdup_printf ("%d / %d", page_num + 1, n_pages); pango_layout_set_text (layout_right, page_text, -1); g_free (page_text); gdouble page_width = gtk_print_context_get_width (context); //in pixels dpi = gtk_print_context_get_dpi_x (context); page_width = page_width * 72 / dpi; //in points gint layout_width; pango_layout_get_size (layout_right, &layout_width, NULL); //CHECKME dpi_v instead of 72 ? cairo_move_to (cr, page_width - layout_width / PANGO_SCALE, - 10 * 72 / 25.4); //up 10 mm from top of text pango_cairo_show_layout (cr, layout_right); } layout = g_object_get_data (G_OBJECT (context), "e2-print-layout"); gdouble page_height = gtk_print_context_get_height (context); //in pixels if (!pango_font_description_get_size_is_absolute (font_desc)) { dpi = gtk_print_context_get_dpi_y (context); page_height = page_height / dpi * 72; //now in points } gint text_height = pango_font_description_get_size (font_desc) / PANGO_SCALE; //in pixels or points guint lines_per_page = page_height / text_height; gint line_count = pango_layout_get_line_count (layout); guint last_line = (line_count > lines_per_page * (page_num + 1)) ? lines_per_page * (page_num + 1) : line_count; guint pageline, fileline; for (pageline = 1, fileline = lines_per_page * page_num; //CHECKME pageline=0 (move to tops of lines?) fileline < last_line; pageline++, fileline++) { PangoLayoutLine *linelay = pango_layout_get_line (layout, fileline); cairo_move_to (cr, 0, pageline * text_height); pango_cairo_show_layout_line (cr, linelay); } } } /** @brief callback for "status-changed" signal on print-operation for selected or all text @param op the print operation @param data UNUSED data specified when callback was connected @return */ static void _e2_view_dialog_print_status_cb (GtkPrintOperation *op, gpointer data) { GtkPrintStatus pstat = gtk_print_operation_get_status (op); switch (pstat) { case GTK_PRINT_STATUS_PENDING_ISSUE: case GTK_PRINT_STATUS_FINISHED: //also for end of preview case GTK_PRINT_STATUS_FINISHED_ABORTED: //BAD for sync, maybe OK for async g_object_unref (G_OBJECT (op)); //cleanup, ref => 2 default: break; } } /** @brief asynchronously print currently-selected text or all text if no selection @param menuitem UNUSED the selected widget, or NULL @param rt runtime struct to work on @return */ void e2_view_dialog_print_cb (GtkWidget *menuitem, E2_ViewDialogRuntime *rt) { // static GtkPrintSettings *settings = NULL; static GtkPageSetup *page_setup = NULL; GtkPrintOperation *op = gtk_print_operation_new (); /* too hard to properly manage refcount in all usage cases if (settings != NULL) //re-use the settings from a former print operation gtk_print_operation_set_print_settings (op, settings); */ if (page_setup == NULL) { page_setup = gtk_page_setup_new (); //never cleaned gtk_page_setup_set_top_margin (page_setup, 25.0, GTK_UNIT_MM); gtk_page_setup_set_bottom_margin (page_setup, 20.0, GTK_UNIT_MM); gtk_page_setup_set_left_margin (page_setup, 20.0, GTK_UNIT_MM); gtk_page_setup_set_right_margin (page_setup, 20.0, GTK_UNIT_MM); } gtk_print_operation_set_default_page_setup (op, page_setup); gtk_print_operation_set_allow_async (op, TRUE); g_signal_connect (G_OBJECT (op), "begin-print", G_CALLBACK (_e2_view_dialog_begin_print_cb), rt); // g_signal_connect (G_OBJECT (op), "end-print", // G_CALLBACK(_e2_view_dialog_end_print_cb), NULL); g_signal_connect (G_OBJECT (op), "draw-page", G_CALLBACK (_e2_view_dialog_draw_page_cb), rt); g_signal_connect (op, "status-changed", G_CALLBACK (_e2_view_dialog_print_status_cb), NULL); GError *error = NULL; GtkPrintOperationResult result = gtk_print_operation_run (op, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, GTK_WINDOW (rt->dialog), &error); switch (result) { // GObject *debug2; case GTK_PRINT_OPERATION_RESULT_ERROR: e2_output_print_error (error->message, FALSE); g_error_free (error); /* case GTK_PRINT_OPERATION_RESULT_APPLY: //e.g. after sync preview case GTK_PRINT_OPERATION_RESULT_IN_PROGRESS: TOO HARD TO PROPERLY MANAGE SETTINGS REFCOUNT IN ALL USAGE CASES */ default: // if (result != GTK_PRINT_OPERATION_RESULT_APPLY)//this for after sync preview // debug2 = G_OBJECT (op); //refcount 2 in async mode if (!(result == GTK_PRINT_OPERATION_RESULT_IN_PROGRESS || result == GTK_PRINT_OPERATION_RESULT_APPLY)) //e.g. after a sync preview cancelled g_object_unref (G_OBJECT (op)); break; } } #endif //def USE_GTK2_10 /** @brief perform copy @param menuitem UNUSED the selected widget, or NULL @param rt runtime struct to work on @return */ static void _e2_view_dialog_copy_cb (GtkWidget *menuitem, E2_ViewDialogRuntime *rt) { GtkClipboard *cb = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); gtk_text_buffer_copy_clipboard (rt->textbuffer, cb); } /** @brief construct and show dialog context menu @param textview the textview widget where the click happened @param event_button which mouse button was clicked (0 for a menu key) @param event_time time that the event happened (0 for a menu key) @param rt runtime struct to work on @return */ static void _e2_view_dialog_show_context_menu (GtkWidget *textview, guint event_button, gint event_time, E2_ViewDialogRuntime *rt) { gchar *item_name; GtkWidget *menu = gtk_menu_new (); /* GtkWidget *item = e2_menu_add (menu, _("_Find"), GTK_STOCK_FIND, NULL, _e2_view_dialog_menu_find_cb, rt); #ifdef USE_GTK2_12TIPS e2_widget_set_toggletip ( #else gtk_tooltips_set_tip (app.tooltips, #endif item, _("Show the search options bar"), _("Find the first/next occurrence")); GtkWidget *submenu = e2_menu_add_submenu (menu, _("_Settings"), GTK_STOCK_PREFERENCES); e2_option_create_menu (GTK_WIDGET (textview), submenu, opt_view_wrap, NULL, NULL, etc app.output.opt_show_on_new, NULL, NULL, app.output.opt_show_on_focus_in, NULL, NULL, NULL); */ GtkWidget *item = e2_menu_add (menu, _("_Copy"), GTK_STOCK_COPY, _("Copy selected text"), _e2_view_dialog_copy_cb, rt); gtk_widget_set_sensitive (item, gtk_text_buffer_get_selection_bounds (rt->textbuffer, NULL, NULL)); #ifdef USE_GTK2_10 gchar *tip = (gtk_text_buffer_get_has_selection (rt->textbuffer)) ? _("Print selected text") : _("Print file"); e2_menu_add (menu, _("_Print.."), GTK_STOCK_PRINT, tip, e2_view_dialog_print_cb, rt); #endif e2_menu_add_separator (menu); item_name = g_strconcat (_A(2),".",_A(32),NULL); e2_menu_add_action (menu, _("_Settings"), GTK_STOCK_PREFERENCES, _("Open the configuration dialog at the options page"), item_name, _C(11), //_("dialogs") NULL); g_free(item_name); g_signal_connect (G_OBJECT (menu), "selection-done", G_CALLBACK (e2_menu_destroy_cb), NULL); if (event_button == 0) gtk_menu_popup (GTK_MENU (menu), NULL, NULL, (GtkMenuPositionFunc) e2_view_dialog_set_menu_position, rt, event_button, event_time); else //this was a button-3 click gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, event_button, event_time); } /** @brief construct and show hbox with search-bar items @param rt runtime struct to work on @return hbox for the searchbar */ GtkWidget *e2_view_dialog_create_searchbar (E2_ViewDialogRuntime *rt) { rt->is_hidden = TRUE; //searchbar is not displayed until a search is started rt->release_blocked = FALSE; GtkWidget *hbox = gtk_hbox_new (FALSE, 0); //add search-string combo rt->combo = e2_combobox_add (hbox, TRUE, 0, NULL, NULL, &find_history, E2_COMBOBOX_HAS_ENTRY | E2_COMBOBOX_FOCUS_ON_CHANGE); //| E2_COMBOBOX_NO_AUTO_HISTORY); gtk_widget_set_size_request (rt->combo, 230, -1); GtkWidget *entry = GTK_BIN (rt->combo)->child; //any signal applied to this widget will also apply to the dialog window //as a whole and to any other element of it!!! g_signal_connect_after (G_OBJECT (entry), "key-release-event", G_CALLBACK (e2_view_dialog_combokey_cb), rt); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif entry, _("Finds")); //add search-option buttons //if also replacing, to make expandable entries the same width, //the buttons need to be in a specific hbox in a size group GtkWidget *hbox2; if (rt->sgroup != NULL) //this is setup in edit dialog code { hbox2 = e2_widget_add_box (hbox, FALSE, 0, FALSE, FALSE, 0); gtk_size_group_add_widget (rt->sgroup, hbox2); } else hbox2 = hbox; GtkWidget *btn = e2_button_add_toggle (hbox2, TRUE, rt->case_sensitive, _("_match case"), _("If activated, text case does matter when searching"), FALSE, E2_PADDING_XSMALL, e2_view_dialog_toggled, &rt->case_sensitive); //flag that extra processing is needed in the callback g_object_set_data (G_OBJECT (btn), "e2_dlg_runtime", rt); btn = e2_button_add_toggle (hbox2, TRUE, rt->whole_words, _("wh_ole words"),_("If activated, matches must be surrounded by word-separator characters"), FALSE, E2_PADDING_XSMALL, e2_view_dialog_toggled, &rt->whole_words); g_object_set_data (G_OBJECT (btn), "e2_dlg_runtime", rt); e2_button_add_toggle (hbox2, TRUE, rt->search_backward, _("_backward"),_("If activated, searching proceeds toward document start"), FALSE, E2_PADDING_XSMALL, e2_view_dialog_toggled, &rt->search_backward); e2_button_add_toggle (hbox2, TRUE, rt->search_wrap, _("_loop"), _("If activated, searching cycles from either end to the other"), FALSE, E2_PADDING_XSMALL, e2_view_dialog_toggled, &rt->search_wrap); gtk_widget_show (hbox); return hbox; } /** @brief setup initial view position, when re-viewing/editing a file @param filepath utf8 string with full path of item being re-opened @param rt runtime struct to work on @return hbox for the searchbar */ void e2_view_dialog_show_atlast (VPATH *localpath, E2_ViewDialogRuntime *rt) { GtkTextIter top; GList *iterator; E2_ViewHistory *viewed; for (iterator = view_history; iterator != NULL; iterator = iterator->next) { viewed = (E2_ViewHistory *) iterator->data; #ifdef E2_VFSTMP compare spacedata ? #endif if (g_str_equal (viewed->localpath, VPSTR (localpath))) break; } if (iterator != NULL) { //cannot use buffer coordinates here (bad for all but smallest files) gtk_text_buffer_get_iter_at_line (rt->textbuffer, &top, viewed->topline); //need to add a mark to get the scroll to work properly GtkTextMark *mark = gtk_text_buffer_create_mark (rt->textbuffer, NULL, &top, FALSE); gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (rt->textview), mark, 0.0, TRUE, 0.0, 0.0); } else gtk_text_buffer_get_start_iter (rt->textbuffer, &top); gtk_text_buffer_place_cursor (rt->textbuffer, &top); e2_dialog_show (rt->dialog, app.main_window, E2_DIALOG_DONT_SHOW_ALL, NULL); gtk_window_present (GTK_WINDOW (rt->dialog)); } /** @brief cleanup when ending a view dialog @param rt runtime struct to work on @return */ void e2_view_dialog_destroy (E2_ViewDialogRuntime *rt) { //in case we last performed an incremental search ... if (!rt->is_hidden) { GtkWidget *entry = GTK_BIN (rt->combo)->child; const gchar *find = gtk_entry_get_text (GTK_ENTRY (entry)); if (*find != '\0') { e2_combobox_activated_cb (entry, GINT_TO_POINTER (FALSE)); e2_list_update_history (find, &find_history, NULL, 30, FALSE); } } //backup some dialog-specific options case_sensitive = rt->case_sensitive; whole_words = rt->whole_words; search_backward = rt->search_backward; search_wrap = rt->search_wrap; //in case we want to re-view, remember where we are now //(need to use line no. as buffer coords are no use when reloading) GList *iterator; E2_ViewHistory *viewed; for (iterator = view_history; iterator != NULL; iterator = iterator->next) { viewed = (E2_ViewHistory *) iterator->data; if (g_str_equal (viewed->localpath, rt->localpath)) break; } if (iterator == NULL) { viewed = MALLOCATE (E2_ViewHistory); //too small for slice, never deallocated CHECKALLOCATEDWARN (viewed, ); if (viewed != NULL) { #ifdef E2_VFSTMP spacedata needed ? #endif viewed->localpath = g_strdup (rt->localpath); view_history = g_list_append (view_history, viewed); } } GdkRectangle visible_rect; GtkTextIter start; gtk_text_view_get_visible_rect (GTK_TEXT_VIEW (rt->textview), &visible_rect); gtk_text_view_get_line_at_y (GTK_TEXT_VIEW (rt->textview), &start, visible_rect.y, NULL); // if () // { //when editing the output buffer, we don't want to change it GtkTextIter scan; guint i = 0; do { gtk_text_buffer_get_iter_at_line (rt->textbuffer, &scan, i++); } while (gtk_text_iter_compare (&scan, &start) < 0); viewed->topline = i-1; /* } else { //get gtk to count the preceding lines by deleting the rest of the conents GtkTextIter end; gtk_text_buffer_get_end_iter (rt->textbuffer, &end); gtk_text_buffer_delete (rt->textbuffer, &start, &end); viewed->topline = gtk_text_buffer_get_line_count (rt->textbuffer); if (viewed->topline > 0) viewed->topline--; } */ #ifdef E2_TRANSIENTKEYS e2_keybinding_unregister (rt->key_binding, GTK_WIDGET (rt->textview)); g_free (rt->key_binding); #endif gtk_widget_destroy (rt->dialog); gtk_widget_grab_focus (curr_view->treeview); //CHECKME consistency ok ? g_free (rt->localpath); // if (rt->idle_id != 0) // g_source_remove (rt->idle_id); DEALLOCATE (E2_ViewDialogRuntime, rt); } /*********************/ /***** callbacks *****/ /*********************/ /** @brief menu-button press callback @param textview the textview widget where the press happened @param rt dialog runtime data struct @return TRUE always */ static gboolean _e2_view_dialog_popup_menu_cb (GtkWidget *textview, E2_ViewDialogRuntime *rt) { gint event_time = gtk_get_current_event_time (); _e2_view_dialog_show_context_menu (textview, 0, event_time, rt); return TRUE; } /** @brief mouse button press callback @param textview the widget where the button was pressed @param event gdk event data @param rt rt data for the dialog @return TRUE (stop other handlers) for btn 3 press, else FALSE (allow other handlers) */ static gboolean _e2_view_dialog_button_press_cb (GtkWidget *textview, GdkEventButton *event, E2_ViewDialogRuntime *rt) { if (event->button == 3) { _e2_view_dialog_show_context_menu (textview, 3, event->time, rt); return TRUE; } return FALSE; } /** @brief check-button state change callback @param button the button widget that changed @param store pointer to store for the new state of @a button @return */ void e2_view_dialog_toggled (GtkWidget *button, gboolean *store) { *store = GTK_TOGGLE_BUTTON (button)->active; //whole-word and case-match buttons need extra handling E2_ViewDialogRuntime *rt = g_object_get_data (G_OBJECT (button), "e2_dlg_runtime"); if (rt != NULL) rt->research = TRUE; //trigger re-highlighting of matches } /** @brief general dialog key press callback @param textview UNUSED the widget where the button was pressed @param event gdk event data @param rt rt data for the dialog @return TRUE (stop other handlers) for recognised keys */ static gboolean _e2_view_dialog_key_press_cb (GtkWidget *textview, GdkEventKey *event, E2_ViewDialogRuntime *rt) { printd (DEBUG, "_e2_view_dialog_key_press_cb textview: %x event: %x data: %x, key: %x", textview, event, rt, event->keyval); guint mask = gtk_accelerator_get_default_mod_mask () & event->state; guint lower = (gdk_keyval_is_upper (event->keyval)) ? gdk_keyval_to_lower (event->keyval) : event->keyval; if (//g_ascii_isalpha (event->keyval) //&& (event->keyval < 0xF000 || event->keyval > 0xFFFF) //&& (mask & GDK_CONTROL_MASK || mask == GDK_MOD1_MASK)) { //the key is a letter and the modifier is Alt or a modifier is Ctrl (Shift+Ctrl reverses direction) if (lower == find_keycode || lower == GDK_g) //special case, again { rt->release_blocked = TRUE; //block anomalous key-release-event searches _e2_view_dialog_response_cb (GTK_DIALOG (rt->dialog), (lower == find_keycode) ? E2_RESPONSE_FIND : E2_RESPONSE_USER2, rt); return TRUE; } else if (lower == hide_keycode) { _e2_view_dialog_response_cb (GTK_DIALOG (rt->dialog), E2_RESPONSE_USER3, rt); return TRUE; } } printd (DEBUG, "_e2_view_dialog_key_press_cb returns FALSE"); return FALSE; } /** @brief key-release callback This is intended for the search combo entry, but actually applies to any key release for any widget in the dialog (and we block spurious ones with a flag) This generally implements non-incremental searches, except when various special cases are detected. @param entry UNUSED the entry widget where the key was pressed @param event pointer to event data struct @param rt pointer to data struct for the search @return FALSE always */ gboolean e2_view_dialog_combokey_cb (GtkWidget *entry, GdkEventKey *event, E2_ViewDialogRuntime *rt) { if (event->keyval == GDK_BackSpace || event->keyval == GDK_Delete) e2_view_dialog_search (FALSE, TRUE, rt); else { guint mask = event->state & gtk_accelerator_get_default_mod_mask (); //except for release, incremental search is ineffective if (event->keyval == GDK_Return) { if (mask == GDK_SHIFT_MASK) { //temporary direction change rt->search_backward = !rt->search_backward; e2_view_dialog_search (FALSE, FALSE, rt); rt->search_backward = !rt->search_backward; } else //non-incremental search, from start if is pressed e2_view_dialog_search ((mask == GDK_CONTROL_MASK), FALSE, rt); } else if (event->keyval < 0xF000 || event->keyval > 0xFFFF) //only interested in "text" keyreleases { //we recognise the equivalent of button presses here guint lower = (gdk_keyval_is_upper (event->keyval)) ? gdk_keyval_to_lower (event->keyval) : event->keyval; if (mask & GDK_CONTROL_MASK || mask == GDK_MOD1_MASK) { //the modifier is Alt or a modifier is Ctrl (Shift+Ctrl reverses direction) if (lower == find_keycode || lower == GDK_g) //special case, not in button label { if (rt->release_blocked) //this is an event after a textview keypress { rt->release_blocked = FALSE; return FALSE; } else { printd (DEBUG, "e2_view_dialog_combokey_cb - going to response cb"); _e2_view_dialog_response_cb (GTK_DIALOG (rt->dialog), (lower == find_keycode) ? E2_RESPONSE_FIND : E2_RESPONSE_USER2, rt); return TRUE; } } else if (lower == hide_keycode) { if (rt->replacebar != NULL) //actually in edit mode return (e2_edit_dialog_key_press_cb (rt->textview, event, rt)); else { _e2_view_dialog_response_cb (GTK_DIALOG (rt->dialog), E2_RESPONSE_USER3, rt); return TRUE; } } else if (lower == replace_keycode) { //this combo is used for the edit dialog too if (rt->replacebar != NULL) //actually in edit mode return (e2_edit_dialog_key_press_cb (rt->textview, event, rt)); } } // by default, do an incremental search e2_view_dialog_search (FALSE, TRUE, rt); } } return FALSE; } /** @brief view dialog response callback This can also be called directly, from other mechanisms to initiate a search The trigger may have been a f or g keypress @param dialog UNUSED the dialog where the response was triggered @param response the number assigned the activated widget @param view rt data for the dialog @return */ static void _e2_view_dialog_response_cb (GtkDialog *dialog, gint response, E2_ViewDialogRuntime *rt) { printd (DEBUG, "_e2_view_dialog_response_cb (dialog:_,response:%d,rt:_)", response); switch (response) { case E2_RESPONSE_USER1: //text wrap rt->textwrap = !rt->textwrap; gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (rt->textview), rt->textwrap ? GTK_WRAP_WORD: GTK_WRAP_NONE); break; case E2_RESPONSE_USER2: //find next (after g press) case E2_RESPONSE_FIND: if (!rt->is_hidden) //search panel is visible { //these tests are irrelevant for an actual response callback, //as no callback happens then //if a mod key is pressed, treat it specially //Ctrl = first, Shift = temp reverse GdkModifierType mask = e2_utils_get_modifiers () & gtk_accelerator_get_default_mod_mask (); if (mask & GDK_SHIFT_MASK) { //temporary direction change rt->search_backward = !rt->search_backward; e2_view_dialog_search (response == E2_RESPONSE_FIND, FALSE, rt); rt->search_backward = !rt->search_backward; } else //perform non-incremental search, from start|end if is pressed e2_view_dialog_search (response == E2_RESPONSE_FIND, FALSE, rt); } else { //show the search panel gtk_widget_show (rt->panel); rt->is_hidden = FALSE; if (rt->hidebtn != NULL) gtk_widget_show (rt->hidebtn); GtkTextIter start_iter, end_iter; if (gtk_text_buffer_get_selection_bounds (rt->textbuffer, &start_iter, &end_iter)) { gchar *find_string = gtk_text_buffer_get_text (rt->textbuffer, &start_iter, &end_iter, FALSE); gtk_entry_set_text (GTK_ENTRY (GTK_BIN (rt->combo)->child), find_string); gtk_editable_select_region (GTK_EDITABLE (GTK_BIN (rt->combo)->child), 0, -1); e2_view_dialog_search (response == E2_RESPONSE_FIND, FALSE, rt); //CHECKME never search for first when first showing the bar g_free (find_string); } else e2_view_dialog_update_combo (rt->combo); //focus on the text entry gtk_widget_grab_focus (GTK_BIN (rt->combo)->child); //swap find btn tip if (rt->findbtn != NULL) e2_widget_swap_tooltip (rt->findbtn); } break; /* case E2_RESPONSE_USER2: //find next _e2_view_dialog_search (FALSE, FALSE, rt); break; */ case E2_RESPONSE_USER3: //hide { //in case we last performed an incremental search ... GtkWidget *entry = GTK_BIN (rt->combo)->child; const gchar *find = gtk_entry_get_text (GTK_ENTRY (entry)); if (*find != '\0') { e2_combobox_activated_cb (entry, GINT_TO_POINTER (FALSE)); e2_list_update_history (find, &find_history, NULL, 30, FALSE); } gtk_widget_hide (rt->panel); rt->is_hidden = TRUE; // gtk_widget_hide (rt->nextbtn); if (rt->hidebtn != NULL) //in view mode gtk_widget_hide (rt->hidebtn); gtk_widget_hide (rt->info_label); // gtk_widget_show (rt->refreshbtn); #ifdef E2_MARK_FINDS //turn off any highlihts e2_view_dialog_clear_hilites (rt); #endif //hide any selection GtkTextIter iter; if (gtk_text_buffer_get_selection_bounds (rt->textbuffer, &iter, NULL)) gtk_text_buffer_select_range (rt->textbuffer, &iter, &iter); //revert find btn tip if (rt->findbtn != NULL) e2_widget_swap_tooltip (rt->findbtn); gtk_widget_grab_focus (rt->textview); } break; default: e2_view_dialog_destroy (rt); break; } } #ifdef E2_TRANSIENTKEYS /** @brief function to setup default key-bindings for view dialog This is just to provide placeholders, the actual bindings are meaningless @param set pointer to option data struct @return */ static void _e2_view_dialog_keybindings (E2_OptionSet *set) { //the key name strings are parsed by gtk, and no translation is possible e2_option_tree_setup_defaults (set, g_strdup("keybindings=<"), //internal name //the category string(s) here need to match the binding name // g_strconcat(_C(17),"||||",NULL), //_("general" g_strconcat(_C(11),"||||",NULL), //_("dialogs" g_strconcat("\t",_A(101),"||||",NULL), //_("view" g_strconcat("\t\t|j","||",_A(119),".",_A(120),"|a",NULL), g_strconcat("\t\t|k","||",_A(119),".",_A(120),"|c",NULL), g_strdup(">"), NULL); } #endif /**********************/ /**** dialog setup ****/ /**********************/ /** @brief create and show view dialog Expects BGL closed when we arrive here @param localpath localised string which has path & name of item to be processed @param srt pointer to store for returning dialog data, or NULL @return the dialog widget, or NULL if there's a problem */ static GtkWidget *_e2_view_dialog_create (VPATH *localpath, E2_ViewDialogRuntime **srt) { //init view runtime object (0's to ensure edit-dialog things are NULL) E2_ViewDialogRuntime *rt = ALLOCATE0 (E2_ViewDialogRuntime); CHECKALLOCATEDWARN (rt, ) if (rt == NULL) { if (srt != NULL) *srt = NULL; return NULL; } rt->textview = gtk_text_view_new (); // add the item's content gdk_threads_leave (); //any downstream error message does its own mutex management if (!e2_view_dialog_read_text (localpath, rt)) { gdk_threads_enter (); gtk_widget_destroy (rt->textview); DEALLOCATE (E2_ViewDialogRuntime, rt); if (srt != NULL) *srt = NULL; return NULL; } gdk_threads_enter (); rt->case_sensitive = case_sensitive; rt->whole_words = whole_words; rt->search_backward = search_backward; rt->search_wrap = search_wrap; #ifdef E2_MARK_FINDS rt->research = FALSE; e2_view_dialog_init_hilites (rt); #endif gchar *utf = F_FILENAME_FROM_LOCALE (rt->localpath); rt->dialog = e2_dialog_create (NULL, utf, _("displaying file"), _e2_view_dialog_response_cb, rt); F_FREE (utf); //override some default label properties GtkWidget *label = g_object_get_data (G_OBJECT (rt->dialog), "e2-dialog-label"); // gtk_label_set_line_wrap (GTK_LABEL (label), FALSE); gtk_label_set_selectable (GTK_LABEL (label), TRUE); gtk_window_set_type_hint (GTK_WINDOW (rt->dialog), GDK_WINDOW_TYPE_HINT_NORMAL); e2_widget_add_separator (GTK_DIALOG (rt->dialog)->vbox, FALSE, 0); //create the view GtkWidget *sw = e2_widget_add_sw (GTK_DIALOG (rt->dialog)->vbox, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, TRUE, E2_PADDING); gtk_container_add (GTK_CONTAINER (sw), rt->textview); gtk_widget_show (rt->textview); // set view defaults gtk_text_view_set_editable (GTK_TEXT_VIEW (rt->textview), FALSE); gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (rt->textview), FALSE); rt->textwrap = e2_option_bool_get ("dialog-view-wrap"); gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (rt->textview), rt->textwrap ? GTK_WRAP_WORD : GTK_WRAP_NONE); gint char_width, char_height; e2_view_dialog_set_font (&char_width, &char_height, rt); rt->window_width = e2_option_int_get ("dialog-view-width"); rt->window_height = e2_option_int_get ("dialog-view-height");; // rt->idle_id = 0; //init search runtime data rt->history = e2_list_copy_with_data (find_history); //action area is a GtkHButtonBox packed at the end of the dialog's vbox //ditto for dialog->separator //locate find-bar between those 2 rt->panel = e2_widget_get_box (TRUE, FALSE, 0); gtk_box_pack_end (GTK_BOX (GTK_DIALOG (rt->dialog)->vbox), rt->panel, FALSE, TRUE, E2_PADDING_XSMALL); gtk_box_reorder_child (GTK_BOX (GTK_DIALOG (rt->dialog)->vbox), rt->panel, 1); //add handlebox GtkWidget *hndlbox = gtk_handle_box_new (); gtk_handle_box_set_shadow_type (GTK_HANDLE_BOX (hndlbox), GTK_SHADOW_NONE); gtk_handle_box_set_handle_position (GTK_HANDLE_BOX (hndlbox), GTK_POS_LEFT); gtk_container_add (GTK_CONTAINER (rt->panel), hndlbox); gtk_widget_show (hndlbox); GtkWidget *hbox = e2_view_dialog_create_searchbar (rt); gtk_container_add (GTK_CONTAINER (hndlbox), hbox); //now add things to the action-area //the "not found" warning is created but not displayed gchar *labeltext = g_strconcat ("", _("not found"), "", NULL); rt->info_label = e2_widget_add_mid_label (GTK_DIALOG (rt->dialog)->action_area, labeltext, 0.0, TRUE, 0); //left-align the label gtk_button_box_set_child_secondary ( GTK_BUTTON_BOX (GTK_DIALOG (rt->dialog)->action_area), rt->info_label, TRUE); gtk_widget_hide (rt->info_label); //search and/or view buttons labeltext = _("_Hide"); rt->hidebtn = e2_dialog_add_undefined_button (rt->dialog, GTK_STOCK_ZOOM_FIT, labeltext, E2_RESPONSE_USER3); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif rt->hidebtn, _("Hide the search options bar")); hide_keycode = e2_utils_get_mnemonic_keycode (labeltext); //this one not seen yet gtk_widget_hide (rt->hidebtn); e2_dialog_add_check_button (rt->dialog, rt->textwrap, _("_wrap"), _("If activated, text in the window will be word-wrapped"), E2_RESPONSE_USER1); labeltext = _("_Find"); rt->findbtn = e2_dialog_add_undefined_button (rt->dialog, GTK_STOCK_FIND, labeltext, E2_RESPONSE_FIND); #ifdef USE_GTK2_12TIPS e2_widget_set_toggletip ( #else gtk_tooltips_set_tip (app.tooltips, #endif rt->findbtn, _("Show the search options bar"), _("Find the next match")); find_keycode = e2_utils_get_mnemonic_keycode (labeltext); //for a view dialog, we never want to match replace keypresses replace_keycode = 0; e2_dialog_add_defined_button (rt->dialog, &E2_BUTTON_CLOSE); // e2_dialog_set_responses (rt->dialog, E2_RESPONSE_FIND, GTK_RESPONSE_CLOSE); e2_dialog_set_negative_response (rt->dialog, GTK_RESPONSE_CLOSE); //this prevents a check button from being activated by keyboard // gtk_dialog_set_default_response (GTK_DIALOG (rt->dialog), E2_RESPONSE_FIND); gtk_window_resize (GTK_WINDOW (rt->dialog), char_width * rt->window_width, (char_height+3) * rt->window_height); #ifdef E2_TRANSIENTKEYS //add dialog-specific key bindings, before the key-press callback //group name (must be freeable) // rt->key_binding = g_strconcat (_C(17), ".", _A(101), NULL); //_(general.view rt->key_binding = g_strconcat (_C(11), ".", _A(101), NULL); //_(dialogs.view e2_keybinding_register_transient (rt->key_binding, GTK_WIDGET (rt->textview), _e2_view_dialog_keybindings); #endif g_signal_connect (G_OBJECT (rt->textview), "popup-menu", G_CALLBACK (_e2_view_dialog_popup_menu_cb), rt); g_signal_connect (G_OBJECT (rt->textview), "button-press-event", G_CALLBACK (_e2_view_dialog_button_press_cb), rt); g_signal_connect (G_OBJECT (rt->textview), "key-press-event", G_CALLBACK (_e2_view_dialog_key_press_cb), rt); if (srt != NULL) *srt = rt; //make rt available to openat func return rt->dialog; } /*****************/ /**** actions ****/ /*****************/ /** @brief re-open file for viewing, at the last-used location in the file This returns immediately, so if run in a Q-thread, that will end @param from the widget that was activated to initiate the action @param art runtime data for the action @return TRUE if the dialog was opened */ static gboolean _e2_view_dialog_reopen (gpointer from, E2_ActionRuntime *art) { return (e2_task_do_task (E2_TASK_VIEW, art, from, _e2_view_dialog_reviewQ, NULL)); } static gboolean _e2_view_dialog_reviewQ (E2_ActionTaskData *qed) { //FIXME also allow specification of a name //process the 1st selected item in active pane GPtrArray *names = qed->names; E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata; //".." entries filtered when names compiled //for name comparisons, we need the full path gchar *localpath = e2_utils_strcat (qed->currdir, (*iterator)->filename); #ifdef E2_VFS VPATH ddata = { localpath, qed->currspace }; #endif E2_ViewDialogRuntime *vrt; gdk_threads_enter (); GtkWidget *dialog = _e2_view_dialog_create #ifdef E2_VFS (&ddata, &vrt); #else (localpath, &vrt); #endif gdk_threads_leave (); if (dialog != NULL) { WAIT_FOR_EVENTS_UNLOCKED_SLOWLY gdk_threads_enter (); #ifdef E2_VFS e2_view_dialog_show_atlast (&ddata, vrt); #else e2_view_dialog_show_atlast (localpath, vrt); #endif gdk_threads_leave (); g_free (localpath); return TRUE; } g_free (localpath); return FALSE; } /** @brief open file for viewing, at a location where there is a specified string @a art includes a string with filepath and an argument that is a text string to find in the file. Separator between path and find-target is the first unquoted unescaped ' ' char (not a \t) This returns immediately, so if run in a Q-thread, that will end @param from the widget that was activated to initiate the action @param art runtime data for the action @return TRUE if dialog was created */ static gboolean _e2_view_dialog_openat (gpointer from, E2_ActionRuntime *art) { return (e2_task_do_task (E2_TASK_VIEW, art, from, _e2_view_dialog_viewatQ, NULL)); } static gboolean _e2_view_dialog_viewatQ (E2_ActionTaskData *qed) { //for a help doc, has localised doc path, and a "[title]" arg in undefined encoding gchar *view_this = (gchar *) qed->rt_data; // gchar c; //strip off any arg FIXME doc path may have whitespace gchar *target = e2_utils_bare_strchr (view_this, ' '); //always ascii ' ', don't need g_utf8_strchr() if (target != NULL) { // c = *target; *target = '\0'; } #ifdef E2_VFS VPATH ddata = { view_this, qed->currspace }; #endif E2_ViewDialogRuntime *vrt; gdk_threads_enter (); //dialog setup wants BGL closed GtkWidget *dialog = _e2_view_dialog_create #ifdef E2_VFS (&ddata, &vrt); #else (view_this, &vrt); #endif gdk_threads_leave (); if (target != NULL) // *target = c; //revert the argument, ready for next time *target = ' '; //revert the argument, ready for next time if (dialog != NULL) { if (target != NULL) target = e2_utils_pass_whitespace (target+1); if (target != NULL && *(target+1) != '\0') { //find the target string and open there GtkTextIter iter, start, end; gtk_text_buffer_get_start_iter (vrt->textbuffer, &iter); if (gtk_text_iter_forward_search (&iter, target, 0, &start, &end, NULL)) { //need to add a mark to get the scroll to work properly //FIXME sometimes it scrolls a bit too far GtkTextMark *mark = gtk_text_buffer_create_mark (vrt->textbuffer, NULL, &start, FALSE); WAIT_FOR_EVENTS_UNLOCKED gdk_threads_enter (); //to support incremental searching, move the cursor //to the start of the located string gtk_text_buffer_place_cursor (vrt->textbuffer, &start); gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (vrt->textview), mark, 0.0, TRUE, 0.0, 0.2); gdk_threads_leave (); } } gdk_threads_enter (); e2_dialog_show (dialog, app.main_window, E2_DIALOG_DONT_SHOW_ALL, NULL); gtk_window_present (GTK_WINDOW (dialog)); gdk_threads_leave (); return TRUE; } return FALSE; } /******************/ /***** public *****/ /******************/ /** @brief create and show view dialog @a localpath may not have any path, or an absolute path. In those cases, curr_view->dir will be prepended This returns immediately, so if run in a Q-thread, that will end @param localpath localised string which has at least the name of item to be processed @return TRUE if the dialog was created */ gboolean e2_view_dialog_create (VPATH *localpath) { //there is no async thread protection here or downstream,, as this func can //be called from different contexts. Such protection, if needed, must be done by the caller GtkWidget *dialog = _e2_view_dialog_create (localpath, NULL); if (dialog != NULL) { WAIT_FOR_EVENTS_SLOWLY e2_dialog_show (dialog, app.main_window, E2_DIALOG_DONT_SHOW_ALL, NULL); gtk_window_present (GTK_WINDOW (dialog)); return TRUE; } return FALSE; } /** @brief create and show non-queued, non-logged view dialog, for user help Expects BGL on/closed @param view_this string with localised path of help doc, AND a heading name (in undefined encoding) as its argument @return TRUE if the dialog was created */ gboolean e2_view_dialog_create_immediate (VPATH *view_this) { E2_ActionTaskData qed; qed.rt_data = VPSTR (view_this); #ifdef E2_VFS qed.currspace = view_this->spacedata; #endif gdk_threads_leave (); //turn off the BGL gboolean retval = _e2_view_dialog_viewatQ (&qed); gdk_threads_enter (); return retval; } /** @brief register actions related to view dialog, and some other initialisation @return */ void e2_view_dialog_actions_register (void) { //HACK while we're at it, init some session parameters //these options are static for the session case_sensitive = e2_option_bool_get ("dialog-search-case-sensitive"); if (e2_option_bool_get ("dialog-search-history")) e2_cache_list_register ("search-history", &find_history); gchar *action_name = g_strconcat(_A(5),".",_A(102),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_view_dialog_reopen, NULL, FALSE); action_name = g_strconcat(_A(5),".",_A(103),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_view_dialog_openat, NULL, TRUE); } /** @brief register config options related to view dialog @return */ void e2_view_dialog_options_register (void) { gchar *group_name = g_strconcat(_C(11),":",_C(40),NULL); //_("dialogs:view" //first some options that may, but probably won't, change during the session e2_option_bool_register ("dialog-view-wrap", group_name, _("wrap text"), _("This causes the view window to open with text-wrapping enabled"), NULL, TRUE, E2_OPTION_FLAG_BASIC | E2_OPTION_FLAG_FREEGROUP); e2_option_int_register ("dialog-view-width", group_name, _("window width"), _("The view window will default to showing this many characters per line (but the the displayed buttons may make it wider than this)") , NULL, 84, 20, 1000, E2_OPTION_FLAG_ADVANCED); e2_option_int_register ("dialog-view-height", group_name, _("window height"), _("The view window will default to showing this many lines of text"), NULL, 30, 10, 1000, E2_OPTION_FLAG_ADVANCED); e2_option_bool_register ("dialog-view-use-font", group_name, _("use custom font"), _("If activated, the font specified below will be used, instead of the theme default"), NULL, FALSE, E2_OPTION_FLAG_BASIC); e2_option_font_register ("dialog-view-font", group_name, _("custom font for viewing files"), _("This is the font used for text in each view dialog"), "dialog-view-use-font", "Sans 10", //_I( font name E2_OPTION_FLAG_BASIC); e2_option_bool_register ("dialog-search-case-sensitive", group_name, _("case sensitive searches"), _("This causes the view window search-bar to first open with case-sensitive searching enabled"), NULL, FALSE, E2_OPTION_FLAG_BASIC); e2_option_bool_register ("dialog-search-show-last", group_name, _("show last search string"), _("This shows the last search-string in the entry field, when the view window search-bar is displayed"), NULL, TRUE, E2_OPTION_FLAG_ADVANCED); e2_option_bool_register ("dialog-search-history", group_name, _("keep search history"), _("This causes search strings to be remembered between sessions"), NULL, FALSE, E2_OPTION_FLAG_ADVANCED); } emelfm2-0.4.1/src/dialogs/e2_permissions_dialog.c0000600000175000017500000004374011007760641020656 0ustar cairocairo/* $Id: e2_permissions_dialog.c 868 2008-05-06 04:42:09Z tpgww $ Copyright (C) 2003-2008 tooar Portions copyright (C) 1999 Michael Clark. This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "emelfm2.h" #include #include #include "e2_dialog.h" #include "e2_permissions_dialog.h" #include "e2_ownership_dialog.h" typedef struct _E2_PermsDldRuntime { GtkWidget *chmod_buttons[12]; GtkWidget *set_perms_button; GtkWidget *add_perms_button; GtkWidget *remove_perms_button; GtkWidget *recurse_button; GtkWidget *recurse_dirs_button; GtkWidget *recurse_other_button; gboolean permission; E2_RecurseType recurse; mode_t mode; gchar *stringmode; DialogButtons result; } E2_PermsDldRuntime; enum { SETUID, SETGID, STICKY, USR_READ, USR_WRITE, USR_EXEC, GRP_READ, GRP_WRITE, GRP_EXEC, OTH_READ, OTH_WRITE, OTH_EXEC }; static mode_t mask[12] = {S_ISUID, S_ISGID, S_ISVTX, S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, S_IXGRP, S_IROTH, S_IWOTH, S_IXOTH}; //strings used for command string, not translated static gchar *add_strings[12] = {"u+s", "g+s", "o+t", "u+r", "u+w", "u+x", "g+r", "g+w", "g+x", "o+r", "o+w", "o+x"}; static gchar *remove_strings[12] = {"u-s", "g-s", "o-t", "u-r", "u-w", "u-x", "g-r", "g-w", "g-x", "o-r", "o-w", "o-x"}; /** @brief construct and save string-form mode string from current dialog settings The result is stored as a newly-allocated string at rt->mode. Maybe NULL on error. @param rt pointer to dialog data struct @return */ static void _e2_permissions_dialog_get_mode_string (E2_PermsDldRuntime *rt) { gint i; GString *mode_string = g_string_sized_new (16); if (GTK_TOGGLE_BUTTON (rt->set_perms_button)->active) { mode_t mode = 0; for (i = 0; i < 12; i++) { if (GTK_TOGGLE_BUTTON (rt->chmod_buttons[i])->active) mode |= mask[i]; } g_string_printf (mode_string, "%o", (guint) mode); } else if (GTK_TOGGLE_BUTTON (rt->add_perms_button)->active) { for (i = 0; i < 12; i++) { if (GTK_TOGGLE_BUTTON (rt->chmod_buttons[i])->active) { g_string_append (mode_string, add_strings[i]); g_string_append_c (mode_string, ','); } } i = mode_string->len; if (i > 0) g_string_truncate (mode_string, i-1); // get rid of trailing comma } else if (GTK_TOGGLE_BUTTON (rt->remove_perms_button)->active) { for (i = 0; i < 12; i++) { if (GTK_TOGGLE_BUTTON (rt->chmod_buttons[i])->active) { g_string_append (mode_string, remove_strings[i]); g_string_append_c (mode_string, ','); } } i = mode_string->len; if (i > 0) g_string_truncate (mode_string, i-1); // get rid of trailing comma } else { printd (DEBUG, "Strange error in permissions dialog."); g_string_free (mode_string, TRUE); rt->stringmode = NULL; } rt->stringmode = mode_string->str; g_string_free (mode_string, FALSE); } /** @brief add a check button for a current permission @param table the table to which the button will be added @param left left position for attaching to @a table @param right right position for attaching to @a table @param top top position for attaching to @a table @param bottom bottom position for attaching to @a table @param label label to add to the button, or NULL @param state state of the button @param i buttons-array index of added button @param rt pointer to dialog data struct @return */ static void _e2_permissions_dialog_add_chmod_button ( GtkWidget *table, gint left, gint right, gint top, gint bottom, gint i, gchar *label, gint state, E2_PermsDldRuntime *rt) { if (label == NULL) { //align the button reasonably GtkWidget *aligner = gtk_alignment_new (0.1 , 0.5, 0, 0 ); GtkWidget *check_button = gtk_check_button_new (); gtk_container_add (GTK_CONTAINER (aligner), check_button); gtk_table_attach (GTK_TABLE (table), aligner, left, right, top, bottom, GTK_FILL, //GtkAttachOptions xoptions GTK_FILL, //GtkAttachOptions yoptions 0, //xpadding 0 //ypadding ); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button), state); gtk_widget_show (check_button); rt->chmod_buttons[i] = check_button; } else //there is a label rt->chmod_buttons[i] = e2_button_add_toggle_to_table (table, label, state, NULL, NULL, left, right, top, bottom); //prevent changes if relevant if (!rt->permission) gtk_widget_set_sensitive (rt->chmod_buttons[i], FALSE); } /** @brief "toggled" signal callback for add_perms_button and remove_perms_button This sets all permission-button states to FALSE @param widget the activated button widget @param rt pointer to dialog data struct @return */ static void _e2_permissions_dialog_clear_chmod_buttons_cb (GtkWidget *widget, E2_PermsDldRuntime *rt) { gint i; for (i = 0; i < 12; i++) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (rt->chmod_buttons[i]), FALSE); } /** @brief "toggled" signal callback for set_perms_button This sets all permission-button states to conform to mode FIXME @param widget the activated button widget @param rt pointer to dialog data struct @return */ static void _e2_permissions_dialog_reset_chmod_buttons_cb (GtkWidget *widget, E2_PermsDldRuntime *rt) { mode_t st_mode = rt->mode; gboolean flag; gint i; for (i = 0; i < 12; i++) { flag = st_mode & mask[i]; gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (rt->chmod_buttons[i]), flag); } } /** @brief "toggled" signal callback for recurse_button @param widget the activated button widget @param rt pointer to dialog data struct @return */ static void _e2_permissions_dialog_toggle_recurse_button_cb (GtkWidget *widget, E2_PermsDldRuntime *rt) { if (!rt->permission) return; gboolean flag = (GTK_TOGGLE_BUTTON (widget)->active); gtk_widget_set_sensitive (rt->recurse_dirs_button, flag); gtk_widget_set_sensitive (rt->recurse_other_button, flag); } /** @brief "toggled" signal callback for recurse_dirs and recurs_others buttons @param widget the activated button widget @param rt pointer to dialog data struct @return */ static void _e2_permissions_dialog_toggle_recurse_type_cb (GtkWidget *widget, E2_PermsDldRuntime *rt) { if (!rt->permission) return; if (GTK_TOGGLE_BUTTON (widget)->active) return; //don't care about choice to turn recursion on if (widget == rt->recurse_dirs_button) { if (!(GTK_TOGGLE_BUTTON (rt->recurse_other_button)->active)) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (rt->recurse_other_button),TRUE); } else //widget == rt->recurse_other_button { if (!(GTK_TOGGLE_BUTTON (rt->recurse_dirs_button)->active)) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (rt->recurse_dirs_button),TRUE); } } /** @brief dialog response callback @param dialog the permissions-dialog @param response the response for the clicked button @param result pointer to store for the dialog return value @return */ static void _e2_permissions_dialog_response_cb (GtkDialog *dialog, gint response, E2_PermsDldRuntime *rt) { switch (response) { case GTK_RESPONSE_OK: case E2_RESPONSE_APPLYTOALL: rt->recurse = E2_RECURSE_NONE; //probably redundant if (rt->permission) //some widgets only exist if permission is TRUE; { _e2_permissions_dialog_get_mode_string (rt); //set rt->stringmode if (rt->recurse_button != NULL //the recurse button has been added to the dialog && GTK_TOGGLE_BUTTON (rt->recurse_button)->active) { if (GTK_TOGGLE_BUTTON (rt->recurse_dirs_button)->active) rt->recurse = E2_RECURSE_DIRS; if (GTK_TOGGLE_BUTTON (rt->recurse_other_button)->active) rt->recurse |= E2_RECURSE_OTHER; if (rt->recurse == (E2_RECURSE_DIRS | E2_RECURSE_OTHER)) rt->recurse = E2_RECURSE_ALL; } } break; default: break; } gtk_widget_destroy (GTK_WIDGET (dialog)); e2_dialog_response_decode_cb (dialog, response, &rt->result); } /** @brief create and run a dialog for changing item permissions @param localpath path of item to be processed @param mode_ret store for mode string to be used in the backend chmod function @param recurse_ret store for returning whether to recurse the changes @param permission_ret store for returning whether a change is authorised @param multi TRUE if this dialog is part of a series for multiple items @return enumerator corresponing to the clicked dialog button */ DialogButtons e2_permissions_dialog_run ( VPATH *localpath, gchar **mode_ret, E2_RecurseType *recurse_ret, gboolean *permission_ret, gboolean multi ) { GtkWidget *permissions_dialog; GtkWidget *dialog_vbox, *sub_vbox; GtkWidget *hbox; GtkWidget *frame; GtkWidget *action_area; GtkWidget *table; // gchar perm_string[32]; struct stat statbuf; struct passwd *pw_buf; struct group *grp_buf; E2_PermsDldRuntime rt; if (e2_fs_lstat (localpath, &statbuf E2_ERR_NONE())) return CANCEL; rt.permission = e2_fs_check_write_permission (localpath E2_ERR_NONE()); rt.recurse = E2_RECURSE_NONE; rt.stringmode = NULL; GString *label_text = g_string_sized_new (NAME_MAX+20); gboolean thisis_dir = e2_fs_is_dir3 (localpath E2_ERR_NONE()); permissions_dialog = e2_dialog_create (NULL, NULL, _("permissions"), _e2_permissions_dialog_response_cb, &rt); dialog_vbox = GTK_DIALOG (permissions_dialog)->vbox; action_area = GTK_DIALOG (permissions_dialog)->action_area; gtk_container_set_border_width (GTK_CONTAINER (dialog_vbox), E2_PADDING); gtk_dialog_set_has_separator (GTK_DIALOG (permissions_dialog), FALSE); //all info in frames, no need // table = e2_widget_add_table (dialog_vbox, 3, 3, TRUE, TRUE, 0); //3 rows, 3 cols, homogen, fill, no pad // gtk_table_set_row_spacing (GTK_TABLE(table), 0, E2_PADDING); gchar *label = (thisis_dir) ? _("Directory name") : _("Filename") ; gchar *name = g_filename_display_basename (VPSTR (localpath)); g_string_printf (label_text, "%s: %s", label, name); g_free (name); hbox = gtk_hbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX(dialog_vbox), hbox, TRUE, TRUE, E2_PADDING); //top, bottom padding e2_widget_add_mid_label (hbox, label_text->str, 0, TRUE, E2_PADDING); //L, R padding gtk_widget_show (hbox); label = _("User"); if ((pw_buf = getpwuid (statbuf.st_uid)) != NULL) name = e2_utf8_from_locale (pw_buf->pw_name); else name = NULL; if (name != NULL) { g_string_printf (label_text, "%s: %s", label, name); g_free (name); } else g_string_printf (label_text, "%s: %d", label, (guint) statbuf.st_uid); hbox = gtk_hbox_new (FALSE, 0); e2_widget_add_mid_label (hbox, label_text->str, 0, TRUE, E2_PADDING); gtk_box_pack_start (GTK_BOX(dialog_vbox), hbox, TRUE, TRUE, 0); gtk_widget_show (hbox); label = _("Group"); if ((grp_buf = getgrgid (statbuf.st_gid)) != NULL) name = e2_utf8_from_locale (grp_buf->gr_name); else name = NULL; if (name != NULL) { g_string_printf (label_text, "%s: %s", label, name); g_free (name); } else g_string_printf (label_text, "%s: %d", label, (guint) statbuf.st_gid); hbox = gtk_hbox_new (FALSE, 0); e2_widget_add_mid_label (hbox, label_text->str, 0, TRUE, E2_PADDING); gtk_box_pack_start (GTK_BOX(dialog_vbox), hbox, TRUE, TRUE, 0); gtk_widget_show (hbox); gchar *s = e2_fs_get_perm_string (statbuf.st_mode); g_string_assign (label_text, g_utf8_next_char (s)); //skip the "type code" in 1st byte of string g_free (s); g_string_insert_c (label_text, e2_utils_get_byte_position (label_text->str, 6), ' '); g_string_insert_c (label_text, e2_utils_get_byte_position (label_text->str, 3), ' '); g_string_insert_c (label_text, 0, ' '); label_text = g_string_prepend (label_text, _("currently")); hbox = gtk_hbox_new (FALSE, 0); e2_widget_add_mid_label (hbox, label_text->str, 0, TRUE, E2_PADDING); gtk_box_pack_start (GTK_BOX(dialog_vbox), hbox, TRUE, TRUE, 0); gtk_widget_show (hbox); frame = gtk_frame_new (_("Permissions")); gtk_box_pack_start (GTK_BOX (dialog_vbox), frame, TRUE, TRUE, E2_PADDING); gtk_widget_show (frame); sub_vbox = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (frame), sub_vbox); gtk_widget_show (sub_vbox); table = e2_widget_add_table (sub_vbox, 4, 5, TRUE, TRUE, 0); //4 rows, 5 cols, homogen, fill, no pad e2_widget_add_mid_label_to_table (table, _("Read"), 0, 1,2,0,1); //gint left, gint right, gint top, gint bottom) e2_widget_add_mid_label_to_table (table, _("Write"), 0, 2,3,0,1); e2_widget_add_mid_label_to_table (table, _("Exec"), 0.05, 3,4,0,1); e2_widget_add_mid_label_to_table (table, _("Special"), 0.2, 4,5,0,1); e2_widget_add_mid_label_to_table (table, _("User"), 0.1, 0,1,1,2); _e2_permissions_dialog_add_chmod_button (table, 1,2,1,2, USR_READ, NULL, statbuf.st_mode & S_IRUSR, &rt); _e2_permissions_dialog_add_chmod_button (table, 2,3,1,2, USR_WRITE, NULL, statbuf.st_mode & S_IWUSR, &rt); _e2_permissions_dialog_add_chmod_button (table, 3,4,1,2, USR_EXEC, NULL, statbuf.st_mode & S_IXUSR, &rt); _e2_permissions_dialog_add_chmod_button (table, 4,5,1,2, SETUID, _("Set UID"), statbuf.st_mode & S_ISUID, &rt); e2_widget_add_mid_label_to_table (table, _("Group"), 0.1, 0,1,2,3); _e2_permissions_dialog_add_chmod_button (table, 1,2,2,3, GRP_READ, NULL, statbuf.st_mode & S_IRGRP, &rt); _e2_permissions_dialog_add_chmod_button (table, 2,3,2,3, GRP_WRITE, NULL, statbuf.st_mode & S_IWGRP, &rt); _e2_permissions_dialog_add_chmod_button (table, 3,4,2,3, GRP_EXEC, NULL, statbuf.st_mode & S_IXGRP, &rt); _e2_permissions_dialog_add_chmod_button (table, 4,5,2,3, SETGID, _("Set GID"), statbuf.st_mode & S_ISGID, &rt); e2_widget_add_mid_label_to_table (table, _("Other"), 0.1, 0,1,3,4); _e2_permissions_dialog_add_chmod_button (table, 1,2,3,4, OTH_READ, NULL, statbuf.st_mode & S_IROTH, &rt); _e2_permissions_dialog_add_chmod_button (table, 2,3,3,4, OTH_WRITE, NULL, statbuf.st_mode & S_IWOTH, &rt); _e2_permissions_dialog_add_chmod_button (table, 3,4,3,4, OTH_EXEC, NULL, statbuf.st_mode & S_IXOTH, &rt); _e2_permissions_dialog_add_chmod_button (table, 4,5,3,4, STICKY, _("Sticky"), statbuf.st_mode & S_ISVTX, &rt); if (!rt.permission || !thisis_dir) { //prevent testing of missing widgets in the response cb rt.recurse_button = NULL; rt.recurse_dirs_button = NULL; rt.recurse_other_button = NULL; } if (rt.permission) { frame = gtk_frame_new (_("Action:")); gtk_box_pack_start (GTK_BOX (dialog_vbox), frame, TRUE, TRUE, E2_PADDING); gtk_widget_show (frame); sub_vbox = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (frame), sub_vbox); gtk_widget_show (sub_vbox); table = e2_widget_add_table (sub_vbox, 1, 5, TRUE, TRUE, 0); //1 rows, 5 cols, homogen, fill, no pad rt.mode = statbuf.st_mode; rt.set_perms_button = e2_button_add_radio_to_table (table, _("_Set"), NULL, TRUE, _e2_permissions_dialog_reset_chmod_buttons_cb, &rt, 1, 2, 0, 1 ); //gint left, gint right, gint top, gint bottom rt.add_perms_button = e2_button_add_radio_to_table (table, _("_Add"), gtk_radio_button_get_group (GTK_RADIO_BUTTON (rt.set_perms_button)), FALSE, _e2_permissions_dialog_clear_chmod_buttons_cb, &rt, 2, 3, 0, 1); rt.remove_perms_button = e2_button_add_radio_to_table (table, _("_Remove"), gtk_radio_button_get_group (GTK_RADIO_BUTTON (rt.add_perms_button)), FALSE, _e2_permissions_dialog_clear_chmod_buttons_cb, &rt, 3, 4, 0, 1); if (thisis_dir) //no longer do this for 'multi' { table = e2_widget_add_table (sub_vbox, 1, 5, TRUE, TRUE, 0); //1 row, 5 cols, homogen, fill, no pad rt.recurse_button = e2_button_add_toggle_to_table (table, _("R_ecurse:"), FALSE, _e2_permissions_dialog_toggle_recurse_button_cb, &rt, 1,2,0,1 ); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif rt.recurse_button, _("If activated, changes will be applied to selected items and" " also their descendents, if such items match your choice of" " \"directories\" and/or \"others\" (anything not a directory)")); rt.recurse_dirs_button = e2_button_add_toggle_to_table (table, _("d_irectories"), TRUE, _e2_permissions_dialog_toggle_recurse_type_cb, &rt, 2,3,0,1) ; gtk_widget_set_sensitive (rt.recurse_dirs_button, FALSE); rt.recurse_other_button = e2_button_add_toggle_to_table (table, _("o_thers"), TRUE, _e2_permissions_dialog_toggle_recurse_type_cb, &rt, 3,4,0,1) ; gtk_widget_set_sensitive (rt.recurse_other_button, FALSE); } } else //no right to change object permissions //show message e2_ownership_dialog_warn (dialog_vbox); g_string_free (label_text, TRUE); // add buttons in the order that they will appear GtkWidget *btn; if (multi) { e2_dialog_set_negative_response (permissions_dialog, E2_RESPONSE_NOTOALL); e2_dialog_add_defined_button (permissions_dialog, &E2_BUTTON_NOTOALL); btn = e2_dialog_add_button_custom (permissions_dialog, FALSE, &E2_BUTTON_APPLYTOALL, NULL, NULL, NULL); if (!rt.permission) gtk_widget_set_sensitive (btn, FALSE); } if (rt.permission || multi) { btn = e2_dialog_add_defined_button (permissions_dialog, &E2_BUTTON_CANCEL); if (!rt.permission) gtk_widget_set_sensitive (btn, FALSE); } e2_dialog_add_button_custom (permissions_dialog, TRUE, &E2_BUTTON_OK, NULL, NULL, NULL); e2_dialog_setup (permissions_dialog, app.main_window); /* non-modal not supported e2_dialog_run (permissions_dialog, NULL, E2_DIALOG_NONMODAL | E2_DIALOG_THREADED | E2_DIALOG_FREE); */ gdk_threads_enter (); e2_dialog_run (permissions_dialog, NULL, 0); gtk_main (); gdk_threads_leave (); *permission_ret = rt.permission; *recurse_ret = rt.recurse; if (rt.result == OK || rt.result == YES_TO_ALL) *mode_ret = rt.stringmode; //possibly NULL else if (rt.stringmode != NULL) { g_free (rt.stringmode); *mode_ret = NULL; } return rt.result; } emelfm2-0.4.1/src/dialogs/e2_select_image_dialog.c0000600000175000017500000005067311014473105020721 0ustar cairocairo/* $Id: e2_select_image_dialog.c 891 2008-05-20 06:57:09Z tpgww $ Copyright (C) 2003-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/dialogs/e2_select_image_dialog.c @brief Select image dialog This file contains all functions needed to create a select-image dialog, which is opened when an icon is clicked on a tree-option page in the main configuration dialog */ #include "emelfm2.h" #include #include "e2_dialog.h" #include "e2_select_image_dialog.h" #include "e2_config_dialog.h" static gchar *_e2_sidlg_get_icon_dir (E2_SID_Runtime *rt); static void _e2_sidlg_fill_custom_store (VPATH *localpath, E2_SID_Runtime *rt); static gint _e2_sidlg_show_current (gboolean checkboth, E2_SID_Runtime *rt); static GtkWidget *rem_btn; #ifdef E2_VFSTMP FIXME initial_dir needs to point to this at session start ? static VPATH localpath; #endif static VPATH *initial_dir; //for custom icons, not cleared at session end /*********************/ /***** callbacks *****/ /*********************/ /** @brief setup object properties so it's possible to determine the currently selected icon or file @param dialog the sid-dialog where the response was triggered @param response the response for the clicked button @param rt pointer to data for sid-dialog @return */ static void _e2_sidlg_response_cb (GtkDialog *dialog, gint response, E2_SID_Runtime *rt) { GtkTreePath *tpath; GList *selected; selected = (rt->page == 0) ? gtk_icon_view_get_selected_items (rt->customview): gtk_icon_view_get_selected_items (rt->stockview); if (selected != NULL) tpath = (GtkTreePath *)selected->data; //single-mode selection means at most, 1 item else tpath = NULL; //warning prevention only if (response == GTK_RESPONSE_OK || response == GTK_RESPONSE_APPLY) { if (rt->page == 0) { //working with a custom icon if (selected != NULL) { GtkTreeIter iter; gchar *fullname; gtk_tree_model_get_iter (rt->custommodel, &iter, tpath); gtk_tree_model_get (rt->custommodel, &iter, 2, &fullname, -1); g_object_set_data_full (G_OBJECT (dialog), "image", fullname, (GDestroyNotify) g_free); } else //signal that this is one to ignore g_object_set_data (G_OBJECT (dialog), "image", NULL); } else { //working with a stock icon if (selected != NULL) { GtkTreeIter iter; gchar *icon, *fullname; gtk_tree_model_get_iter (rt->stockmodel, &iter, tpath); gtk_tree_model_get (rt->stockmodel, &iter, 0, &icon, -1); fullname = g_strconcat ("gtk-", icon, NULL); g_object_set_data_full (G_OBJECT (dialog), "image", fullname, (GDestroyNotify) g_free); g_free (icon); } else g_object_set_data (G_OBJECT (dialog), "image", NULL); } } else if (response == E2_RESPONSE_REMOVE) { g_object_set_data_full (G_OBJECT (dialog), "image", g_strdup (""), (GDestroyNotify) g_free); if (selected != NULL) gtk_icon_view_unselect_path ( (rt->page == 0) ? rt->customview : rt->stockview, tpath); } else //some other response g_object_set_data (G_OBJECT (dialog), "image", NULL); //ignore it if (selected != NULL) { gtk_tree_path_free (tpath); g_list_free (selected); } gtk_widget_set_sensitive (rem_btn, (response != E2_RESPONSE_REMOVE)); } /** @brief issue ok response for @a dialog This is a callback for item-activated signal in stock-icons view @param iconview the object where the activation occurred @param path treepath of the activated item @param dialog the parent dialog widget @return */ static void _e2_sidlg_activated_cb (GtkIconView *iconview, GtkTreePath *path, GtkWidget *dialog) { gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); } /** @brief notebook page-switch callback @param notebook the object whose page changed @param page UNUSED the notebook page which is now focused @param page_num index of the new page @param rt pointer to data for sid-dialog @return */ static void _e2_sidlg_page_switch_cb (GtkNotebook *notebook, GtkNotebookPage *page, guint page_num, E2_SID_Runtime *rt) { rt->page = page_num; gtk_widget_set_sensitive (rt->dir_chooser, (page_num == 0)); } /** @brief cleanup sid-dialog data @param dialog @param rt pointer to data for sid-dialog @return */ static void _e2_sidlg_destroy_cb (GtkWidget *dialog, E2_SID_Runtime *rt) { g_object_set_data (G_OBJECT (rt->parent), rt->name, NULL); g_free (rt->name); g_free (rt->icon); DEALLOCATE (E2_SID_Runtime, rt); } /** @brief destroy sid-dialog @param rt pointer to data for sid-dialog @return */ static void _e2_sidlg_destroy_cb2 (E2_SID_Runtime *rt) { gtk_widget_destroy (rt->dialog); } /** @brief change custom icons directory @param chooser the selection object @param rt pointer to data for sid-dialog @return */ static void _e2_sidlg_dir_change_cb (GtkFileChooser *chooser, E2_SID_Runtime *rt) { gchar *uri = gtk_file_chooser_get_uri (chooser); if (uri != NULL) { gchar *dirpath = g_filename_from_uri (uri, NULL, NULL); if (dirpath != NULL) { gtk_list_store_clear (GTK_LIST_STORE (rt->custommodel)); #ifdef E2_VFSTMP VPATH local; local.localpath = dirpath; local.spacedata = ?; _e2_sidlg_fill_custom_store (&VPATH, rt); #else _e2_sidlg_fill_custom_store (dirpath, rt); #endif //show the relevant icon, if any _e2_sidlg_show_current (FALSE, rt); //remember for next session if (VPSTR (initial_dir) != NULL) g_free (VPSTR (initial_dir)); initial_dir = dirpath; } g_free (uri); } } /*****************/ /***** utils *****/ /*****************/ /** @brief get the appropriate directory for icons @param rt pointer to data for sid-dialog @return newly-allocated string with the path to use, localised with trailing / */ static gchar *_e2_sidlg_get_icon_dir (E2_SID_Runtime *rt) { gboolean useiconpath; gchar *iconpath, *freeme; gpointer dtype = g_object_get_data (G_OBJECT (rt->parent), "dialog-form"); if (GPOINTER_TO_INT (dtype) == E2_CFGDLG_SINGLE) { //doing a single-page dialog iconpath = e2_utils_get_icons_path (TRUE); } else { //doing a full config dialog //so a relevant option may have been changed in this session E2_OptionSet *set = e2_option_get ("use-icon-dir"); if (set->widget != NULL) //this option is part of the dialog useiconpath = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (set->widget)); else useiconpath = e2_option_bool_get_direct (set); if (useiconpath) { set = e2_option_get ("icon-dir"); if (set->widget != NULL) { freeme = gtk_editable_get_chars (GTK_EDITABLE (set->widget), 0, -1); iconpath = D_FILENAME_TO_LOCALE (freeme); g_free (freeme); if (!g_str_has_suffix (iconpath, G_DIR_SEPARATOR_S)) { freeme = iconpath; iconpath = g_strconcat (freeme, G_DIR_SEPARATOR_S, NULL); g_free (freeme); } } else iconpath = g_strdup (ICON_DIR G_DIR_SEPARATOR_S); //localised } else iconpath = g_strdup (ICON_DIR G_DIR_SEPARATOR_S); } return iconpath; } /** @brief fill liststore for custom icons @param localpath pointer to icons-directory path data @param rt pointer to data for sid-dialog @return */ static void _e2_sidlg_fill_custom_store (VPATH *localpath, E2_SID_Runtime *rt) { GList *entries; //get all files entries = (GList *)e2_fs_dir_foreach (localpath, E2_DIRWATCH_NO, //assume local icons, so fast read NULL, NULL, NULL E2_ERR_NONE()); if (!E2DREAD_FAILED (entries)) { GList *member; gint w, h; GtkSettings *s = gtk_settings_get_default (); if (!gtk_icon_size_lookup_for_settings (s, GTK_ICON_SIZE_LARGE_TOOLBAR, &w, &h)) { w = h = 18; //can't find useful size, use this default } GtkListStore *store = GTK_LIST_STORE (rt->custommodel); for (member = entries; member != NULL; member = g_list_next (member)) { GtkTreeIter iter; gchar *fullpath; GdkPixbuf *pix; //CHECKME encoding fullpath = g_build_filename (VPSTR(localpath), (gchar*) member->data, NULL); pix = gdk_pixbuf_new_from_file_at_scale (fullpath, w, h, TRUE, NULL); if (pix != NULL) { gtk_list_store_insert_with_values (store, &iter, -1, 0, (gchar*) member->data, 1, pix, 2, fullpath, //CHECKME encoding -1); } g_free (fullpath); } g_list_foreach (entries, (GFunc) g_free, NULL); g_list_free (entries); } } /** @brief create and fill liststore for custom icons @param rt pointer to data for sid-dialog @return the store */ static GtkListStore *_e2_sidlg_create_custom_store (E2_SID_Runtime *rt) { //store with columns for basename, pixbuf, fullpath CHECKME nama and path encoding ? GtkListStore *store = gtk_list_store_new (3, G_TYPE_STRING, GDK_TYPE_PIXBUF, G_TYPE_STRING); rt->custommodel = GTK_TREE_MODEL (store); //the filler wants this //FIXME populate this lot of icons at idle, if the current icon is not custom gchar *path = _e2_sidlg_get_icon_dir (rt); #ifdef E2_VFS VPATH localpath; localpath.localpath = path; localpath.spacedata = NULL; //local icon files _e2_sidlg_fill_custom_store (&localpath, rt); #else _e2_sidlg_fill_custom_store (path, rt); #endif g_free (path); return store; } /** @brief idle callback to populate stock icons view @param store object in which to save icons' data @return FALSE to terminate the source */ static gboolean _e2_sidlg_fill_stock_store (GtkListStore *store) { GSList *ids = gtk_stock_list_ids (); if (ids != NULL) { GtkStyle *style = gtk_widget_get_style (app.main_window); GtkTextDirection dir = gtk_widget_get_direction (app.main_window); GSList *member; ids = g_slist_sort (ids, (GCompareFunc) strcmp); for (member = ids; member != NULL; member = g_slist_next (member)) { GtkIconSet *iset = gtk_style_lookup_icon_set (style, (const gchar*) member->data); if (iset != NULL) { GdkPixbuf *pix = gtk_icon_set_render_icon (iset, style, dir, GTK_STATE_NORMAL, GTK_ICON_SIZE_LARGE_TOOLBAR, NULL, NULL); if (pix != NULL) { GtkTreeIter iter; gtk_list_store_insert_with_values (store, &iter, -1, 0, (gchar*) member->data + 4, //skip leading "gtk-" 1, pix, -1); g_object_unref (G_OBJECT (pix)); } } g_free (member->data); } g_slist_free (ids); } return FALSE; } /** @brief create empty liststore for all gtk stock icons @return the store */ static GtkListStore *_e2_sidlg_create_stock_store (void) { GtkListStore *store = gtk_list_store_new (2, G_TYPE_STRING, GDK_TYPE_PIXBUF); return store; } /** @brief create scrolled window showing stock icons @param rt pointer to data for sid-dialog @return */ static GtkWidget *_e2_sidlg_create_stock_icon_browser (E2_SID_Runtime *rt) { GtkListStore *store = _e2_sidlg_create_stock_store (); rt->stockmodel = GTK_TREE_MODEL (store); rt->stockview = GTK_ICON_VIEW (gtk_icon_view_new_with_model (rt->stockmodel)); g_object_unref (G_OBJECT (store)); gtk_icon_view_set_text_column (rt->stockview, 0); gtk_icon_view_set_pixbuf_column (rt->stockview, 1); gtk_icon_view_set_item_width (rt->stockview, 100); //FIXME relate width to font size gtk_icon_view_set_margin (rt->stockview, E2_PADDING); // gtk_icon_view_set_spacing (rt->stockview, E2_PADDING_XSMALL); gtk_icon_view_set_row_spacing (rt->stockview, E2_PADDING_SMALL); gtk_icon_view_set_column_spacing (rt->stockview, E2_PADDING_SMALL); g_signal_connect (G_OBJECT(rt->stockview), "item-activated", G_CALLBACK (_e2_sidlg_activated_cb), rt->dialog); GtkWidget *sw = e2_widget_get_sw_plain (GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_container_add (GTK_CONTAINER (sw), GTK_WIDGET (rt->stockview)); //FIXME populate this lot of icons at idle, if the current icon is not stock //g_idle_add ((GSourceFunc)_e2_sidlg_fill_stock_store, store); _e2_sidlg_fill_stock_store (store); return sw; } /** @brief create scrolled window showing stock icons @param rt pointer to data for sid-dialog @return */ static GtkWidget *_e2_sidlg_create_custom_icon_browser (E2_SID_Runtime *rt) { GtkListStore *store = _e2_sidlg_create_custom_store (rt); rt->custommodel = GTK_TREE_MODEL (store); rt->customview = GTK_ICON_VIEW (gtk_icon_view_new_with_model (rt->custommodel)); g_object_unref (G_OBJECT (store)); gtk_icon_view_set_text_column (rt->customview, 0); gtk_icon_view_set_pixbuf_column (rt->customview, 1); gtk_icon_view_set_item_width (rt->customview, 80); //FIXME relate width to font size gtk_icon_view_set_margin (rt->customview, E2_PADDING); // gtk_icon_view_set_spacing (rt->customview, E2_PADDING_XSMALL); gtk_icon_view_set_row_spacing (rt->customview, E2_PADDING); gtk_icon_view_set_column_spacing (rt->customview, E2_PADDING_SMALL); g_signal_connect (G_OBJECT(rt->customview), "item-activated", G_CALLBACK (_e2_sidlg_activated_cb), rt->dialog); GtkWidget *sw = e2_widget_get_sw_plain (GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_container_add (GTK_CONTAINER (sw), GTK_WIDGET (rt->customview)); return sw; } /** @brief initialize notebook page This checks stored strings, so expects both icon liststores to be already full of strings at least Sets rt->page, but does NOT change the notebook page accordingly @param checkboth TRUE to check stock items as well as others @param rt pointer to data for sid-dialog @return relevant notebook page no. */ static gint _e2_sidlg_show_current (gboolean checkboth, E2_SID_Runtime *rt) { GtkTreeIter iter; if (checkboth && gtk_tree_model_get_iter_first (rt->stockmodel, &iter) && e2_tree_find_iter_from_str_simple (rt->stockmodel, 0, rt->icon + 4, //the stored data omits leading "gtk-" &iter, FALSE)) { //the selected item is a stock item //select & show the corresponding icon in the view GtkTreePath *path = gtk_tree_model_get_path (rt->stockmodel, &iter); if (path != NULL) { gtk_icon_view_set_cursor (rt->stockview, path, NULL, FALSE); gtk_icon_view_select_path (rt->stockview, path); #ifdef USE_GTK2_8 //older gtk just ensures visible gtk_icon_view_scroll_to_path (rt->stockview, path, TRUE, 0.5, 0.5); #endif gtk_tree_path_free (path); } return 1; //notebook page no. } else { //the selected item is a custom-icon //point to the icon file, if possible if ((rt->icon != NULL) && (*rt->icon != '\0')) { //rt->icon has no path if the image file is in default dir, or it's a full path gint srchcol; if (g_path_is_absolute (rt->icon)) srchcol = 2; //look for full-path match else srchcol = 0; //look for name match //CHECKME file/path encoding ? if (gtk_tree_model_get_iter_first (rt->custommodel, &iter) && e2_tree_find_iter_from_str_simple (rt->custommodel, srchcol, rt->icon, &iter, FALSE)) { GtkTreePath *path = gtk_tree_model_get_path (rt->custommodel, &iter); if (path != NULL) { gtk_icon_view_set_cursor (rt->customview, path, NULL, FALSE); gtk_icon_view_select_path (rt->customview, path); #ifdef USE_GTK2_8 //older gtk just ensures visible gtk_icon_view_scroll_to_path (rt->customview, path, TRUE, 0.5, 0.5); #endif gtk_tree_path_free (path); } } } return 0; } } /******************/ /***** public *****/ /******************/ /** @brief create icon-selection dialog @param parent the main config-dialog widget @param name dialog title string @param icon gtk-stock-icon name, or path to icon file, taken from tree-option store @param response_func callback function for all responses for this dialog @param set pointer to data for the tree-option to which the dialog relates @return the dialog widget, or NULL if error occurred */ GtkWidget *e2_sid_create (GtkWidget *parent, const gchar *name, gchar *icon, gpointer response_func, E2_OptionSet *set) { //get or create and inititialize select image dialog runtime object and //ensure that only one runtime object (and only one dialog) exists //for every parent widget E2_SID_Runtime *rt = g_object_get_data (G_OBJECT (parent), name); //the user may have double-clicked on an icon, or ... if (rt != NULL && rt->dialog != NULL && GTK_WIDGET_VISIBLE (rt->dialog)) { //the dialog already exists printd (NOTICE, "select image dialog already exists"); if (rt->icon != NULL) g_free (rt->icon); rt->icon = g_strdup (icon); //make dialog show the icon _e2_sidlg_show_current (TRUE, rt); //present the window gtk_window_present (GTK_WINDOW (rt->dialog)); gtk_notebook_set_current_page (rt->notebook, rt->page); return rt->dialog; } else { printd (NOTICE, "creating select image dialog"); rt = ALLOCATE0 (E2_SID_Runtime); CHECKALLOCATEDWARN (rt, return NULL;) //set up runtime object rt->name = g_strdup (name); //CHECKME can icon be absolute path ? rt->icon = g_strdup (icon); rt->parent = parent; } //create dialog widgets rt->dialog = e2_dialog_create (NULL, NULL, rt->name, _e2_sidlg_response_cb, rt); g_signal_connect (rt->dialog, "response", G_CALLBACK (response_func), set); //when the dialog is destroyed, free the runtime object g_signal_connect (rt->dialog, "destroy", G_CALLBACK (_e2_sidlg_destroy_cb), rt); //ensure this dialog is destroyed with the parent widget if (parent != NULL) g_object_set_data_full (G_OBJECT (parent), rt->name, rt, (GDestroyNotify) _e2_sidlg_destroy_cb2); if (VPSTR (initial_dir) != NULL && (e2_utils_get_modifiers () & GDK_CONTROL_MASK)) { g_free (VPSTR (initial_dir)); initial_dir = NULL; } if (initial_dir == NULL) initial_dir = _e2_sidlg_get_icon_dir (rt); //before any notebook page-switch cb, setup this button const gchar *message = _("Choose icons directory"); rt->dir_chooser = gtk_file_chooser_button_new (message, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER); gtk_file_chooser_set_show_hidden (GTK_FILE_CHOOSER (rt->dir_chooser), TRUE); gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (rt->dir_chooser), initial_dir); g_signal_connect (G_OBJECT (rt->dir_chooser), "current-folder-changed", G_CALLBACK (_e2_sidlg_dir_change_cb), rt); gtk_widget_show (rt->dir_chooser); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif rt->dir_chooser, message); //a notebook is used to change between the icon views rt->notebook = GTK_NOTEBOOK (e2_widget_add_notebook (GTK_DIALOG (rt->dialog)->vbox, TRUE, E2_PADDING, _e2_sidlg_page_switch_cb, rt)); //the custom icon browser is a scrolled window GtkWidget *sw = _e2_sidlg_create_custom_icon_browser (rt); GtkWidget *label = gtk_label_new_with_mnemonic (_("_other")); gtk_notebook_append_page_menu (rt->notebook, sw, label, label); //and the stock icon browser is another scrolled window sw = _e2_sidlg_create_stock_icon_browser (rt); label = gtk_label_new_with_mnemonic (_("_stock")); gtk_notebook_append_page_menu (rt->notebook, sw, label, label); //decide the initial notebook page, icon, file, and remember page, that's //set to 0 in page-switch cb triggered in gtk_widget_show_all() gint page = _e2_sidlg_show_current (TRUE, rt); //we don't put the chooser in default action-area, that's always homogenous GtkWidget *bbox = gtk_hbox_new (FALSE, 0); GtkWidget *bbox2 = gtk_vbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (bbox), bbox2, FALSE, TRUE, 5); //FIXME relate size to buttonbox padding gtk_box_pack_start (GTK_BOX (bbox2), rt->dir_chooser, TRUE, TRUE, 5); g_object_ref (G_OBJECT (GTK_DIALOG (rt->dialog)->action_area)); gtk_container_remove (GTK_CONTAINER(GTK_DIALOG (rt->dialog)->vbox), GTK_DIALOG (rt->dialog)->action_area); gtk_box_pack_start (GTK_BOX (bbox), GTK_DIALOG (rt->dialog)->action_area, FALSE, FALSE, 0); g_object_unref (G_OBJECT (GTK_DIALOG (rt->dialog)->action_area)); gtk_box_pack_end (GTK_BOX (GTK_DIALOG (rt->dialog)->vbox), bbox, FALSE, FALSE, 0); rem_btn = e2_dialog_add_defined_button (rt->dialog, &E2_BUTTON_REMOVE); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif rem_btn, _("Remove the current icon")); if (*icon == '\0') //nothing to remove at present gtk_widget_set_sensitive (rem_btn, FALSE); gtk_window_set_default_size (GTK_WINDOW (rt->dialog), -1, 350); e2_dialog_show (rt->dialog, rt->parent, 0, &E2_BUTTON_APPLY, &E2_BUTTON_CANCEL, &E2_BUTTON_OK, NULL); //sets notebook page to 0 if (page != 0) gtk_notebook_set_current_page (rt->notebook, page); return rt->dialog; } emelfm2-0.4.1/src/dialogs/e2_select_dir_dialog.c0000600000175000017500000000431110647377413020421 0ustar cairocairo/* $Id: e2_select_dir_dialog.c 539 2007-07-18 11:52:43Z tpgww $ Copyright (C) 2005-2007 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "emelfm2.h" #include "e2_dialog.h" /** @brief display directory-chooser dialog @a newdir is set to a /-terminated string, newly-allocated, or else NULL, if no or invalid choice is made @param view pointer to view data for the pane to which this dialog will apply @param startdir path of dir to show in chooser when it opens, utf8 string @param newdir pointer to store for chosen new dir-path @return */ void e2_opendir_dialog_create (ViewInfo *view, gchar *startdir, gchar **newdir) { GtkWidget *dialog = gtk_file_chooser_dialog_new (NULL, GTK_WINDOW (app.main_window), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); e2_dialog_setup_chooser (dialog, _("open"), startdir, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, !view->show_hidden, //show- or hide-hidden FALSE, //single-selection GTK_RESPONSE_OK); //default response e2_dialog_setup (dialog, app.main_window); // gtk_widget_show (dialog); *newdir = NULL; gint response; while ((response = gtk_dialog_run (GTK_DIALOG (dialog))) == E2_RESPONSE_USER1) {} if (response == GTK_RESPONSE_OK) { gchar *dir = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); // if (dir != NULL) // { gchar *utf = F_FILENAME_FROM_LOCALE (dir); //not DISPLAY *newdir = g_strconcat (utf, G_DIR_SEPARATOR_S, NULL); g_free (dir); F_FREE (utf); // } } gtk_widget_destroy (dialog); } emelfm2-0.4.1/src/dialogs/e2_name_filter_dialog.c0000600000175000017500000001503210721376361020565 0ustar cairocairo/* $Id: e2_name_filter_dialog.c 748 2007-11-22 22:04:33Z tpgww $ Copyright (C) 2003-2007 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/dialogs/e2_name_filter_dialog.c @brief dialog for setting up arrangements for name-filtering to be applied to a filelist */ #include "emelfm2.h" #include #include "e2_dialog.h" #include "e2_filelist.h" typedef struct _E2_NmFltDlgData { GtkWidget *dialog; GtkWidget *pattern_entry; GtkWidget *invert_check; GtkWidget *case_sensitive_check; // GtkWidget *menu_item; //copy of pointer to clicked filters menu item // gboolean itemstate; //initial state of menu_item // gboolean blocked = FALSE; //flag to prevent toggles when not wanted ViewInfo *view; } E2_NmFltDlgData; /*static void _e2_name_filter_dialog_item_toggle_cb (void) { gulong handler = g_signal_handler_find (menu_item, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, e2_name_filter_dialog_create_cb, NULL); g_signal_handler_block (menu_item, handler); gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item), !itemstate); g_signal_handler_unblock (menu_item, handler); } */ static void _e2_name_filter_dialog_ok (E2_NmFltDlgData *data) { const gchar *s = gtk_entry_get_text (GTK_ENTRY (data->pattern_entry)); if (*s == '\0') e2_output_print_error (_("Invalid filename pattern"), FALSE); else { //filter pattern is saved as utf, as required for matching, later // g_strlcpy (view->name_filter.pattern, s, // sizeof (view->name_filter.pattern)); //save cache data (actual, maybe != s) g_free (data->view->name_filter.patternptr); // data->view->name_filter.patternptr = g_strdup (view->name_filter.pattern); data->view->name_filter.patternptr = g_strdup (s); data->view->name_filter.invert_mask = GTK_TOGGLE_BUTTON (data->invert_check)->active; data->view->name_filter.case_sensitive = GTK_TOGGLE_BUTTON (data->case_sensitive_check)->active; // if (! itemstate) //we're staying filtered, so re-toggle the menu item // _e2_name_filter_dialog_item_toggle_cb (); //and conform the cached copy data->view->name_filter.active = TRUE; //show the results e2_toolbar_toggle_filter_button (data->view); e2_fileview_refilter_list (data->view); #ifdef E2_STATUS_DEMAND if (data->view == curr_view) e2_window_update_status_bar (NULL); #endif } } /** @brief dialog response callback @param dialog the permissions-dialog @param response the response for the clicked button @param data pointer to dialog data struct @return */ static void _e2_name_filter_dialog_response_cb (GtkDialog *dialog, gint response, E2_NmFltDlgData *data) { switch (response) { case GTK_RESPONSE_OK: gtk_widget_hide (data->dialog); _e2_name_filter_dialog_ok (data); //prevent toggle when dialog is destroyed // data->blocked = TRUE; break; case E2_RESPONSE_REMOVE: //update the cached copy of the state data-> view->name_filter.active = FALSE; gtk_widget_hide (data->dialog); //show the results e2_toolbar_toggle_filter_button (data->view); e2_fileview_refilter_list (data->view); #ifdef E2_STATUS_DEMAND if (data->view == curr_view) e2_window_update_status_bar (NULL); #endif //prevent toggle when dialog is destroyed // data->blocked = TRUE; break; // case GTK_RESPONSE_CANCEL: default: //the menu item will be re-toggled when dialog is destroyed break; } gtk_widget_destroy (data->dialog); DEALLOCATE (E2_NmFltDlgData, data); // gtk_main_quit (); return; } /** @brief handle Return keypresses in the size entry @param entry UNUSED the entry for the combo box @param rt pointer to dialog data struct @return */ static void _e2_name_filter_dialog_activated_cb (GtkEntry *entry, E2_NmFltDlgData *data) { _e2_name_filter_dialog_response_cb (GTK_DIALOG (data->dialog), GTK_RESPONSE_OK, data); } /** @brief create file name filter dialog The state of @a item when it arrives here is opposite to that shown in the menu, when clicked @param item the clicked checkbox widget in the fillters menu @param view data structure for the view to which the file list belongs @return */ void e2_name_filter_dialog_create_cb (GtkWidget *item, ViewInfo *view) { E2_NmFltDlgData *rt = ALLOCATE (E2_NmFltDlgData); CHECKALLOCATEDWARN (rt, return;); rt->view = view; //save local copies, for later use in other functions // rt->menu_item = item; // rt->itemstate = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM (item)); rt->dialog = e2_dialog_create (NULL, _("Display only the items named like:"), _("name filter"), _e2_name_filter_dialog_response_cb, rt); GtkWidget *vbox = GTK_DIALOG (rt->dialog)->vbox; rt->pattern_entry = e2_widget_add_entry (vbox, view->name_filter.patternptr, FALSE, FALSE); #ifdef E2_ASSISTED GtkWidget *label = (GtkWidget *) g_object_get_data (G_OBJECT (rt->dialog), "e2-dialog-label"); e2_widget_set_label_relations (GTK_LABEL (label), rt->pattern_entry); #endif //handle key-presses when the entry is focused g_signal_connect (G_OBJECT (rt->pattern_entry), "activate", G_CALLBACK (_e2_name_filter_dialog_activated_cb), rt); e2_widget_add_mid_label (vbox, _("example: *.c,*.h"), 0.0, FALSE, 0); GtkWidget *table = e2_widget_add_table (vbox, 1, 2, FALSE, FALSE, 0); rt->invert_check = e2_button_add_toggle_to_table (table, _("Invert"), view->name_filter.invert_mask, NULL, NULL, 0, 1, 0, 1); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif rt->invert_check, _("Show files that DO NOT match the given mask")); rt->case_sensitive_check = e2_button_add_toggle_to_table (table, _("Case sensitive"), view->name_filter.case_sensitive, NULL, NULL, 1, 2, 0, 1); //now the buttons // if (!rt->itemstate) if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item))) //user has just toggled off e2_dialog_add_defined_button (rt->dialog, &E2_BUTTON_REMOVE); //non-modal dialog e2_dialog_show (rt->dialog, app.main_window, 0, &E2_BUTTON_CANCEL, &E2_BUTTON_OK, NULL); // gtk_main (); } emelfm2-0.4.1/src/dialogs/e2_date_filter_dialog.c0000600000175000017500000002031210721376361020557 0ustar cairocairo/* $Id: e2_date_filter_dialog.c 748 2007-11-22 22:04:33Z tpgww $ Copyright (C) 2003-2007 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "emelfm2.h" //#define _XOPEN_SOURCE /* required for strptime in glibc-2 */ #include #include "e2_dialog.h" #include "e2_filelist.h" enum { MODIFIED_SINCE, MODIFIED_BEFORE, ACCESSED_SINCE, ACCESSED_BEFORE, CHANGED_SINCE, CHANGED_BEFORE, MAXDATECHOICES }; typedef struct _E2_DtFltDlgData { GtkWidget *dialog; GtkWidget *date_entry; GtkWidget *operation_combo; gint date_index; //the date format array index to use // GtkWidget *menu_item; //copy of pointer to clicked filters menu item // gboolean itemstate; // gboolean blocked = FALSE; //flag to prevent toggles when not wanted ViewInfo *view; } E2_DtFltDlgData; //these need to be in same order as the date options in the config dialog gchar *date_format [5] = { "%d/%m/%Y", //default "%d/%m/%Y", //standard "%m/%d/%Y", //american "%Y-%m-%d", //ISO "%x" //localised }; /*static void _e2_date_filter_dialog_item_toggle_cb (void) { gulong handler = g_signal_handler_find (menu_item, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, e2_date_filter_dialog_create_cb, NULL); g_signal_handler_block (menu_item, handler); gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item), !itemstate); g_signal_handler_unblock (menu_item, handler); } */ static void _e2_date_filter_dialog_ok (E2_DtFltDlgData *data) { struct tm tm_time; gchar *strf_withtime = g_strconcat (date_format [data->date_index] , " %T", NULL); gchar *date_string = g_strdup_printf ("%s 00:00:00", gtk_entry_get_text (GTK_ENTRY (data->date_entry))); strptime (date_string, strf_withtime, &tm_time); g_free (strf_withtime); g_free (date_string); data->view->date_filter.time = (time_t) mktime (&tm_time); gint index = gtk_combo_box_get_active (GTK_COMBO_BOX (data->operation_combo)); //see below for the strings provided to the combo switch (index) { case ACCESSED_SINCE: data->view->date_filter.op = GT; data->view->date_filter.time_type = ATIME; break; case ACCESSED_BEFORE: data->view->date_filter.op = LT; data->view->date_filter.time_type = ATIME; break; case MODIFIED_SINCE: data->view->date_filter.op = GT; data->view->date_filter.time_type = MTIME; break; case MODIFIED_BEFORE: data->view->date_filter.op = LT; data->view->date_filter.time_type = MTIME; break; case CHANGED_SINCE: data->view->date_filter.op = GT; data->view->date_filter.time_type = CTIME; break; case CHANGED_BEFORE: data->view->date_filter.op = LT; data->view->date_filter.time_type = CTIME; break; default: break; } // if (! itemstate) //we're staying filtered, so re-toggle the menu item // _e2_date_filter_dialog_item_toggle_cb (); //and conform the cached copy data->view->date_filter.active = TRUE; //show the results if (index > -1) { e2_toolbar_toggle_filter_button (data->view); e2_fileview_refilter_list (data->view); #ifdef E2_STATUS_DEMAND BGL? if (data->view == curr_view) { gdk_threads_leave (); e2_window_update_status_bar (NULL); gdk_threads_enter (); } #endif } } /** @brief dialog response callback @param dialog the permissions-dialog @param response the response for the clicked button @param data pointer to dialog data struct @return */ static void _e2_date_filter_dialog_response_cb (GtkDialog *dialog, gint response, E2_DtFltDlgData *data) { switch (response) { case GTK_RESPONSE_OK: gtk_widget_hide (data->dialog); _e2_date_filter_dialog_ok (data); //prevent toggle when dialog is destroyed // blocked = TRUE; break; case E2_RESPONSE_REMOVE: //update the cached copy of the state data->view->date_filter.active = FALSE; gtk_widget_hide (data->dialog); //show the results e2_toolbar_toggle_filter_button (data->view); e2_fileview_refilter_list (data->view); #ifdef E2_STATUS_DEMAND BGL? if (data->view == curr_view) e2_window_update_status_bar (NULL); #endif //prevent toggle when dialog is destroyed // blocked = TRUE; break; // case GTK_RESPONSE_CANCEL: default: //the menu item will be re-toggled when dialog is destroyed ?? break; } gtk_widget_destroy (data->dialog); DEALLOCATE (E2_DtFltDlgData, data); // gtk_main_quit (); } /** @brief handle activation ( keypresses) in the query entry @param entry UNUSED the entry widget for the combo box @param data pointer to dialog data struct @return */ static void _e2_date_filter_dialog_activated_cb (GtkEntry *entry, E2_DtFltDlgData *data) { _e2_date_filter_dialog_response_cb (GTK_DIALOG (data->dialog), GTK_RESPONSE_OK, data); } /** @brief create file date filter dialog The state of @a item when it arrives here is opposite to that shown in the menu, when clicked @param item the clicked checkbox widget in the fillters menu @param view data structure for the view to which the file list belongs @return */ void e2_date_filter_dialog_create_cb (GtkWidget *item, ViewInfo *view) { GtkWidget *hbox; gchar date_string[16]; gchar *utf; struct tm *tm_ptr; gint index; E2_DtFltDlgData *rt = ALLOCATE (E2_DtFltDlgData); CHECKALLOCATEDWARN (rt, return;); rt->view = view; //save local copies, for later use in other functions // rt->menu_item = item; // rt->itemstate = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM (item)); rt->dialog = e2_dialog_create (NULL, _("Display only the items:"), _("date filter"), _e2_date_filter_dialog_response_cb, rt); hbox = e2_widget_add_box (GTK_DIALOG (rt->dialog)->vbox, TRUE, E2_PADDING, FALSE, FALSE, E2_PADDING); rt->operation_combo = e2_combobox_add (hbox, FALSE, 0, NULL, NULL, NULL, E2_COMBOBOX_MENU_STYLE); //don't change the order of these - index value is used below and in callback gchar *date_choices [MAXDATECHOICES] = { _("modified since"), _("modified before"), _("accessed since"), _("accessed before"), _("changed since"), _("changed before") }; e2_combobox_append_history_counted (rt->operation_combo, MAXDATECHOICES, date_choices); switch (view->date_filter.time_type) { case MTIME: index = (view->date_filter.op == GT) ? MODIFIED_SINCE : MODIFIED_BEFORE; //0 = 1st combo string break; case ATIME: index = (view->date_filter.op == GT) ? ACCESSED_SINCE : ACCESSED_BEFORE; break; case CTIME: index = (view->date_filter.op == GT) ? CHANGED_SINCE : CHANGED_BEFORE; break; default: index = MODIFIED_SINCE; break; } gtk_combo_box_set_active (GTK_COMBO_BOX (rt->operation_combo), index) ; //get which date format to use rt->date_index = e2_option_int_get ("date-string"); if (rt->date_index > 4) rt->date_index = 0; //out of range, use default format (should never happen) tm_ptr = localtime (&(view->date_filter.time)); strftime (date_string, sizeof (date_string), date_format[rt->date_index], tm_ptr); utf = e2_utf8_from_locale (date_string); rt->date_entry = e2_widget_add_entry (hbox, utf, FALSE, FALSE); g_free (utf); gtk_widget_set_size_request (rt->date_entry, 120, 30); #ifdef E2_ASSISTED GtkWidget *label = (GtkWidget *) g_object_get_data (G_OBJECT (rt->dialog), "e2-dialog-label"); e2_widget_set_label_relations (GTK_LABEL (label), rt->date_entry); #endif //handle key-presses when the entry is focused g_signal_connect (G_OBJECT (rt->date_entry), "activate", G_CALLBACK (_e2_date_filter_dialog_activated_cb), rt); //now the buttons // if (!rt->itemstate) if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM (item))) //user has just toggled off e2_dialog_add_defined_button (rt->dialog, &E2_BUTTON_REMOVE); e2_dialog_show (rt->dialog, app.main_window, 0, &E2_BUTTON_CANCEL, &E2_BUTTON_OK, NULL); // gtk_main (); } emelfm2-0.4.1/src/dialogs/e2_edit_dialog.c0000600000175000017500000014701310776374631017242 0ustar cairocairo/* $Id: e2_edit_dialog.c 853 2008-04-07 10:38:17Z tpgww $ Copyright (C) 2006-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "emelfm2.h" #include #include "e2_dialog.h" #include "e2_view_dialog.h" #include "e2_textiter.h" #include "e2_task.h" //rates around the default blink rate will never actually blink! dunno why ... //so: MULTIPLIER >= 1.5, MULTIPLIER/DIVIDER <= 0.5 #define OVERWR_CURSOR_MULTIPLIER 2.0 #define INSERT_CURSOR_DIVIDER 4.0 #ifndef CURSOR_ON_MULTIPLIER #define CURSOR_ON_MULTIPLIER 0.66 #endif #ifndef CURSOR_OFF_MULTIPLIER #define CURSOR_OFF_MULTIPLIER 0.34 #endif //these variables can all be shared among all concurrent and sequential //view and edit dialogs extern gboolean case_sensitive; extern gboolean whole_words; extern gboolean search_backward; extern gboolean search_wrap; extern GList *find_history; static gboolean replace_all; static gboolean confirm_before; static gboolean do_next; static GList *replace_history = NULL; #ifdef E2_SPELLCHECK static GList *lang_history = NULL; #endif gint blinkmsec; //blink-cycle duration (msec) //keycodes corresponding to mnemonics for translated labels //these ones are shared with a view dialog extern guint find_keycode; extern guint hide_keycode; guint replace_keycode; static guint save_keycode; static guint undo_keycode; static guint redo_keycode; static void _e2_edit_dialog_dirty_change_cb (GtkTextBuffer *textbuffer, E2_ViewDialogRuntime *rt); static gboolean _e2_edit_dialog_reeditQ (E2_ActionTaskData *qed); /****************************/ /**** undo functionality ****/ /****************************/ #define INCLUDED_IN_PARENT #include "e2_edit_dialog_undo.c" #undef INCLUDED_IN_PARENT /***************************/ /**** editing functions ****/ /***************************/ #ifdef E2_SPELLCHECK static void _e2_edit_dialog_cancel_spell (E2_ViewDialogRuntime *rt) { gtkspell_detach (rt->spelldata); //this destroys the GtkSpell rt->spelldata = NULL; } #endif /** @brief save text selected in @a buffer This func is also used to save output pane selection @param buffer text buffer from which the text will be saved #ifdef E2_VFS @param spacedata pointer to namspace data #endif @param parent parent widget, dialog or main window @return */ void e2_edit_dialog_save_selected (GtkTextBuffer *buffer, #ifdef E2_VFS PlaceInfo *spacedata, #endif GtkWidget *parent) { #ifdef E2_VFSTMP check assumption that selector only works for current namespace #endif GtkTextIter start, end; //get this first, as the dialog unselects the text if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end)) { GtkWidget *dialog = gtk_file_chooser_dialog_new (NULL, GTK_WINDOW (parent), GTK_FILE_CHOOSER_ACTION_SAVE, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_SAVE, GTK_RESPONSE_OK, NULL); e2_dialog_setup_chooser (dialog, _("file save as"), //API doco is silent on whether g_get_home_dir() gets a localised string g_get_home_dir (), GTK_FILE_CHOOSER_ACTION_SAVE, FALSE, //hide-hidden FALSE, //single-selection GTK_RESPONSE_OK); //default response gint response; while ((response = gtk_dialog_run (GTK_DIALOG (dialog))) == E2_RESPONSE_USER1) {} if (response == GTK_RESPONSE_OK) { gchar *local = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); #ifdef E2_VFS VPATH ddata = { local, spacedata }; #endif #ifndef USE_GTK2_8 //check for o/w if (e2_option_bool_get ("confirm-overwrite") #ifdef E2_VFS && e2_fs_access2 (&ddata E2_ERR_NONE()) == 0) #else && e2_fs_access2 (local E2_ERR_NONE()) == 0) #endif { DialogButtons choice = e2_dialog_ow_check (NULL, local, NONE); if (choice != OK) { gtk_widget_destroy (dialog); gtk_text_buffer_select_range (buffer, &start, &end); g_free (local); return; } } #endif gchar *text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE); e2_fs_set_file_contents ( #ifdef E2_VFS &ddata, text, strlen (text), 0644 E2_ERR_NONE()); #else local, text, strlen (text), 0644 E2_ERR_NONE()); #endif g_free (text); g_free (local); } gtk_widget_destroy (dialog); gtk_text_buffer_select_range (buffer, &start, &end); } } /** @brief save currently-selected text @param menuitem UNUSED the selected widget, or NULL @param rt runtime struct to work on @return */ static void _e2_edit_dialog_savesel_cb (GtkWidget *menuitem, E2_ViewDialogRuntime *rt) { e2_edit_dialog_save_selected (rt->textbuffer, #ifdef E2_VFS rt->spacedata, #endif rt->dialog); } /** @brief save file @param menuitem UNUSED the selected widget, or NULL @param rt runtime struct to work on @return */ static void _e2_edit_dialog_save_cb (GtkWidget *menuitem, E2_ViewDialogRuntime *rt) { GtkTextIter start, end; gtk_text_buffer_get_bounds (rt->textbuffer, &start, &end); gchar *text = gtk_text_buffer_get_text (rt->textbuffer, &start, &end, FALSE); //restore line ends gint extras; if (rt->linebreak == CR+LF) extras = gtk_text_buffer_get_line_count (rt->textbuffer) + 1; //+1 = possible incomplete line else extras = 0; //irrelevant text = e2_utils_revert_line_ends (text, extras, rt->linebreak); //after external encoding conversion, or some fallback conversion, //we can't know the original encoding, so just save as UTF-8, and warn the user if (!g_str_equal (rt->charset, "UNKNOWN") && strstr (rt->charset, "UTF-8") == NULL) //conversion is actually needed { gsize bytes_old, bytes_new; GError *error = NULL; gchar *newtext; newtext = g_convert (text, -1, rt->charset, "UTF-8", &bytes_old, &bytes_new, &error); if (error == NULL) { g_free (text); text = newtext; } else { gchar *msg; if (newtext != NULL) g_free (newtext); switch (error->code) { case G_CONVERT_ERROR_ILLEGAL_SEQUENCE: msg = g_strdup_printf ( _("Cannot save %s in its original encoding %s. Reverting to UTF-8"), rt->localpath, rt->charset); e2_output_print_error (msg, TRUE); g_error_free (error); break; default: msg = g_strdup_printf ( _("Encoding conversion failed for %s"), rt->localpath); e2_output_print_error (msg, TRUE); e2_output_print_error (error->message, FALSE); g_error_free (error); rt->saved_ok = FALSE; return; } } } const gchar *usepath; //rt->localpath may be a replacement name, when "saving as" usepath = (rt->saveas) ? rt->newlocalpath : rt->localpath; #ifdef E2_VFS VPATH ddata = { usepath, rt->spacedata }; #endif if (e2_option_bool_get ("edit-save-backup") && !rt->saveas && !e2_fs_access ( #ifdef E2_VFS &ddata, W_OK E2_ERR_NONE())) #else usepath, W_OK E2_ERR_NONE())) #endif { gchar *saved = e2_utils_strcat (usepath, "~"); #ifdef E2_VFS VPATH sdata = { saved, rt->spacedata }; #endif gdk_threads_leave (); //downstream errors invoke local mutex locking #ifdef E2_VFS e2_task_backend_rename (&ddata, &sdata); //ignore any warning !! #else e2_task_backend_rename (usepath, saved); //ignore any warning !! #endif gdk_threads_enter (); g_free (saved); } //allow new file permissions to be the same as an old one, if any struct stat sb; gboolean replaced = !rt->saveas && #ifdef E2_VFS !e2_fs_stat (&ddata, &sb E2_ERR_NONE()); //through a link #else !e2_fs_stat (usepath, &sb); #endif rt->saved_ok = e2_fs_set_file_contents ( #ifdef E2_VFS &ddata, text, strlen (text), 0600 E2_ERR_NONE()); #else usepath, text, strlen (text), 0600); #endif g_free (text); if (rt->saved_ok) { mode_t perms = (replaced) ? sb.st_mode & ALLPERMS : 0644; #ifdef E2_VFS e2_fs_chmod (&ddata, perms E2_ERR_NONE()); #else e2_fs_chmod (usepath, perms); #endif // uid_t owner_id = ?; // gid_t group_id = ?; // e2_fs_chown (local, owner_id, group_id); if (g_str_equal (rt->charset, "UNKNOWN")) { e2_fs_error_simple ( _("Original encoding of '%s' is unknown, now saved as UTF-8"), #ifdef E2_VFS &ddata); #else usepath); #endif } gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (rt->textbuffer), FALSE); } } /** @brief save file with new name @param menuitem UNUSED the selected widget, or NULL @param rt runtime struct to work on @return */ static void _e2_edit_dialog_saveas_cb (GtkWidget *menuitem, E2_ViewDialogRuntime *rt) { #ifdef E2_VFSTMP check assumption that selector only works for current namespace #endif GtkWidget *dialog = gtk_file_chooser_dialog_new (NULL, GTK_WINDOW (rt->dialog), GTK_FILE_CHOOSER_ACTION_SAVE, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_SAVE, GTK_RESPONSE_OK, NULL); gchar *utf = F_FILENAME_FROM_LOCALE (rt->localpath); e2_dialog_setup_chooser (dialog, _("file save as"), utf, GTK_FILE_CHOOSER_ACTION_SAVE, FALSE, //hide-hidden FALSE, //single-selection GTK_RESPONSE_OK); //default response F_FREE (utf); gint response; while ((response = gtk_dialog_run (GTK_DIALOG (dialog))) == E2_RESPONSE_USER1) {} if (response == GTK_RESPONSE_OK) { gchar *local = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); #ifndef USE_GTK2_8 //check for O/W if not done by the dialog #ifdef E2_VFS VPATH ddata = { local, rt->spacedata }; #endif if (e2_option_bool_get ("confirm-overwrite") #ifdef E2_VFS && e2_fs_access2 (&ddata E2_ERR_NONE()) == 0) #else && e2_fs_access2 (local E2_ERR_NONE()) == 0) #endif { DialogButtons choice = e2_dialog_ow_check (NULL, local, NONE); if (choice != OK) { gtk_widget_destroy (dialog); g_free (local); return; } } #endif rt->saveas = TRUE; rt->newlocalpath = local; _e2_edit_dialog_save_cb (NULL, rt); rt->saveas = FALSE; if (rt->saved_ok) { g_free (rt->localpath); rt->localpath = local; //update the dialog label GtkWidget *label = g_object_get_data (G_OBJECT (rt->dialog), "e2-dialog-label"); gchar *utf = F_FILENAME_FROM_LOCALE (rt->localpath); gtk_label_set_text (GTK_LABEL (label), utf); F_FREE (utf); } } gtk_widget_destroy (dialog); } /** @brief reload edited file @param menuitem UNUSED the selected widget, or NULL @param rt runtime struct to work on @return */ static void _e2_edit_dialog_refresh_cb (GtkWidget *menuitem, E2_ViewDialogRuntime *rt) { if (!gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (rt->textbuffer))) return; //no point in reloading DialogButtons choice = e2_dialog_warning ( _("Reverting to saved version cannot be undone")); if (choice == OK) { #ifdef E2_VFS VPATH ddata = { rt->localpath, rt->spacedata }; #endif gdk_threads_leave (); #ifdef E2_VFS e2_view_dialog_read_text (&ddata, rt); //no failure test when refreshing #else e2_view_dialog_read_text (rt->localpath, rt); //no failure test when refreshing #endif gdk_threads_enter (); gtk_text_buffer_set_modified (rt->textbuffer, FALSE); } gtk_widget_grab_focus (rt->textview); } /** @brief undo change @param menuitem UNUSED the selected widget, or NULL @param rt runtime struct to work on @return */ static void _e2_edit_dialog_undo_cb (GtkWidget *menuitem, E2_ViewDialogRuntime *rt) { _e2edtdlg_undo_undo_one (rt); } #ifdef E2_REDO_ENABLED /** @brief redo change @param menuitem UNUSED the selected widget, or NULL @param rt runtime struct to work on @return */ static void _e2_edit_dialog_redo_cb (GtkWidget *menuitem, E2_ViewDialogRuntime *rt) { _e2edtdlg_undo_redo_one (rt); } #endif #ifdef E2_SPELLCHECK static GList *lang_history; /** @brief recheck all buffer spelling @param menuitem UNUSED the selected widget, or NULL @param rt runtime struct to work on @return */ static void _e2_edit_dialog_spellall_cb (GtkWidget *menuitem, E2_ViewDialogRuntime *rt) { gchar *lang = (lang_history == NULL) ? NULL : //start speller with default language (gchar *) lang_history->data; //or with last-chosen language GError *error = NULL; rt->spelldata = gtkspell_new_attach (GTK_TEXT_VIEW (rt->textview), lang, &error); if (rt->spelldata == NULL) { gchar *msg = g_strdup_printf ( _("Cannot initialize spell checking: %s"), error->message); e2_output_print_error (msg, TRUE); g_error_free (error); } else { //disable all gtkspell signal-callbacks g_signal_handlers_block_matched (G_OBJECT (rt->textview), G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, rt->spelldata); } } /** @brief cancel buffer spell checking @param menuitem UNUSED the selected widget, or NULL @param rt runtime struct to work on @return */ static void _e2_edit_dialog_unspellall_cb (GtkWidget *menuitem, E2_ViewDialogRuntime *rt) { gtkspell_detach (rt->spelldata); //this destroys the GtkSpell rt->spelldata = NULL; } /** @brief set spell-checker language @param menuitem UNUSED the selected widget, or NULL @param rt runtime struct to work on @return */ static void _e2_edit_dialog_spell_language_cb (GtkWidget *menuitem, E2_ViewDialogRuntime *rt) { gchar *lang; if (lang_history == NULL) { lang = (gchar *)g_getenv ("LANG"); //ok if NULL if (lang != NULL && g_str_equal (lang, "C")) lang = NULL; //CHECKME default used by speller backends } else lang = (gchar *)lang_history->data; DialogButtons choice = e2_dialog_combo_input (_("choose language"), _("Enter the spell-checker language (like en_CA):"), lang, 0, &lang_history, &lang); //re-use for entered value if (choice == OK) { GError *error = NULL; gtkspell_set_language (rt->spelldata, lang, &error); if (error != NULL) { gchar *msg = g_strdup_printf ( _("Cannot set speller language to %s (%s)"), lang, error->message); e2_output_print_error (msg, TRUE); g_error_free (error); } g_free (lang); } } #endif /** @brief toggle text wrapping @param menuitem UNUSED the selected widget, or NULL @param rt runtime struct to work on @return */ static void _e2_edit_dialog_wrap_cb (GtkWidget *menuitem, E2_ViewDialogRuntime *rt) { rt->textwrap = !rt->textwrap; gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (rt->textview), rt->textwrap ? GTK_WRAP_WORD: GTK_WRAP_NONE); } /** @brief hide search/replace bar @param menuitem UNUSED the selected widget, or NULL @param rt runtime struct to work on @return */ static void _e2_edit_dialog_hide_cb (GtkWidget *menuitem, E2_ViewDialogRuntime *rt) { gtk_widget_hide (rt->panel); rt->is_hidden = TRUE; gtk_widget_hide (rt->info_label); gtk_widget_hide (rt->findbtn); gtk_widget_hide (rt->replacebtn); //in case we last performed an incremental search ... GtkWidget *entry = GTK_BIN (rt->combo)->child; const gchar *choice = gtk_entry_get_text (GTK_ENTRY (entry)); if (*choice != '\0') { e2_combobox_activated_cb (entry, GINT_TO_POINTER (FALSE)); e2_list_update_history (choice, &find_history, NULL, 30, FALSE); } #ifdef E2_MARK_FINDS //turn off any highlihts e2_view_dialog_clear_hilites (rt); #endif //hide any selection NAH, might want it for editing // GtkTextIter iter; // if (gtk_text_buffer_get_selection_bounds (rt->textbuffer, &iter, NULL)) // gtk_text_buffer_select_range (rt->textbuffer, &iter, &iter); //now for the replacements ... entry = GTK_BIN (rt->combo2)->child; choice = gtk_entry_get_text (GTK_ENTRY (entry)); if (*choice != '\0') { e2_combobox_activated_cb (entry, GINT_TO_POINTER (FALSE)); e2_list_update_history (choice, &replace_history, NULL, 30, FALSE); } //focus on the text gtk_widget_grab_focus (rt->textview); } /** @brief perform find This is a menu-selection callback, and can also be called directly @param menuitem the activated menu widget, or NULL or 0x1 when called directly @param rt runtime data for the dialog @return */ static void _e2_edit_dialog_find_cb (GtkWidget *menuitem, E2_ViewDialogRuntime *rt) { if (!rt->is_hidden) //search panel is visible { //if a mod key is pressed, treat it specially //Ctrl = find first, Shift = temp reverse direction GdkModifierType mask = e2_utils_get_modifiers () & gtk_accelerator_get_default_mod_mask (); if (mask == GDK_SHIFT_MASK) { //temporary direction change rt->search_backward = !rt->search_backward; e2_view_dialog_search (FALSE, FALSE, rt); rt->search_backward = !rt->search_backward; } else //perform non-incremental search, from start|end if is pressed //in menu-initiated search, or direct-call with menuitem = 0x1 e2_view_dialog_search ( (menuitem != NULL && mask == GDK_CONTROL_MASK), FALSE, rt); } else { //show the search panel & related things if (GTK_WIDGET_VISIBLE (rt->replacebar)) gtk_widget_hide (rt->replacebar); gtk_widget_show (rt->panel); rt->is_hidden = FALSE; gtk_widget_show (rt->findbtn); GtkTextIter start_iter, end_iter; if (gtk_text_buffer_get_selection_bounds (rt->textbuffer, &start_iter, &end_iter)) { gchar *find_string = gtk_text_buffer_get_text (rt->textbuffer, &start_iter, &end_iter, FALSE); gtk_entry_set_text (GTK_ENTRY (GTK_BIN (rt->combo)->child), find_string); gtk_editable_select_region (GTK_EDITABLE (GTK_BIN (rt->combo)->child), 0, -1); printd (DEBUG, "_e2_edit_dialog_find_cb - non-incremental search"); e2_view_dialog_search (FALSE, FALSE, rt); g_free (find_string); } else e2_view_dialog_update_combo (rt->combo); //focus on the text entry gtk_widget_grab_focus (GTK_BIN (rt->combo)->child); } } /** @brief perform replacement @param menuitem UNUSED the selected widget, or NULL @param rt runtime struct to work on @return */ static void _e2_edit_dialog_replace_cb (GtkWidget *menuitem, E2_ViewDialogRuntime *rt) { if (!GTK_WIDGET_VISIBLE (rt->replacebtn)) gtk_widget_show (rt->replacebtn); if (!GTK_WIDGET_VISIBLE (rt->findbtn)) gtk_widget_show (rt->findbtn); if (!GTK_WIDGET_VISIBLE (rt->replacebar)) gtk_widget_show (rt->replacebar); if (!rt->is_hidden) //search panel is visible { GtkTextIter start, end; if (!gtk_text_buffer_get_selection_bounds (rt->textbuffer, &start, &end)) { //nothing selected, presumably after a replace that's just happened //lazy user, try for another match, like Ctrl F e2_view_dialog_search (FALSE, FALSE, rt); return; } GtkWidget *entry = GTK_BIN (rt->combo2)->child; const gchar *replace = gtk_entry_get_text (GTK_ENTRY (entry)); //do replacement(s) if (replace_all) { //ignore compiler warning about unitialized usage GtkWidget *dialog = NULL; //assignment for complier-warning prevention only DialogButtons choice; if (confirm_before) { dialog = e2_dialog_create (GTK_STOCK_DIALOG_QUESTION, _("Replace this one ?"), _("confirm"), e2_dialog_response_decode_cb, &choice); e2_dialog_add_defined_button (dialog, &E2_BUTTON_NOTOALL); e2_dialog_add_defined_button (dialog, &E2_BUTTON_NO); e2_dialog_add_defined_button (dialog, &E2_BUTTON_YES); e2_dialog_setup (dialog, rt->dialog); } while (e2_view_dialog_search (FALSE, FALSE, rt)) { if (confirm_before) { gtk_widget_show (dialog); gtk_main (); gtk_widget_hide (dialog); if (choice == CANCEL) continue; else if (choice == NO_TO_ALL) break; } gtk_text_buffer_delete_selection (rt->textbuffer, TRUE, TRUE); if (*replace != '\0') gtk_text_buffer_insert_interactive_at_cursor (rt->textbuffer, replace, -1, TRUE); } if (confirm_before) gtk_widget_destroy (dialog); } else if (gtk_text_buffer_get_selection_bounds (rt->textbuffer, &start, &end)) { gtk_text_buffer_delete_selection (rt->textbuffer, TRUE, TRUE); if (*replace != '\0') gtk_text_buffer_insert_interactive_at_cursor (rt->textbuffer, replace, -1, TRUE); if (do_next) e2_view_dialog_search (FALSE, FALSE, rt); } } else //first time, make the search panel visible { gtk_widget_show (rt->panel); rt->is_hidden = FALSE; e2_view_dialog_update_combo (rt->combo2); //CHECKME always empty the replace string ? GtkTextIter start_iter, end_iter; if (gtk_text_buffer_get_selection_bounds (rt->textbuffer, &start_iter, &end_iter)) { gchar *find_string = gtk_text_buffer_get_text (rt->textbuffer, &start_iter, &end_iter, FALSE); gtk_entry_set_text (GTK_ENTRY (GTK_BIN (rt->combo)->child), find_string); gtk_editable_select_region (GTK_EDITABLE (GTK_BIN (rt->combo)->child), 0, -1); printd (DEBUG, "_e2_edit_dialog_replace_cb - non-incremental search"); e2_view_dialog_search (FALSE, FALSE, rt); g_free (find_string); } else e2_view_dialog_update_combo (rt->combo); //focus on the text entry gtk_widget_grab_focus (GTK_BIN (rt->combo)->child); } } /** @brief construct and show edit-dialog context menu @param textview the textview widget where the click happened @param event_button which mouse button was clicked (0 for a menu key) @param event_time time that the event happened (0 for a menu key) @param rt runtime struct to work on @return */ static void _e2_edit_dialog_show_context_menu (GtkWidget *textview, guint event_button, gint event_time, E2_ViewDialogRuntime *rt) { gchar *item_name; GtkWidget *item; GtkWidget *menu = gtk_menu_new (); #ifdef E2_SPELLCHECK if (rt->spelldata != NULL) { //decide if we need a spellfix item in the menu GtkTextIter iter; gboolean atcursor = (event_button == 0); //TRUE if processing a menu-button press if (!atcursor) { GdkEvent *event = gtk_get_current_event (); if (event != NULL) //should never happen { if (event->type == GDK_BUTTON_PRESS) { gint buf_x, buf_y, x, y; x = ((GdkEventButton *) event)->x; y = ((GdkEventButton *) event)->y; gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (rt->textview), GTK_TEXT_WINDOW_TEXT, x, y, &buf_x, &buf_y); gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (rt->textview), &iter, buf_x, buf_y); } else atcursor = TRUE; //revert to using cursor position gdk_event_free (event); } else atcursor = TRUE; } if (atcursor) { //menu button was pressed, or error happened GtkTextMark *mark = gtk_text_buffer_get_insert (rt->textbuffer); gtk_text_buffer_get_iter_at_mark (rt->textbuffer, &iter, mark); } //use function from patched gtk-spell to get fixes menu, if any GtkWidget *submenu = gtkspell_get_suggestions_menu (rt->spelldata, &iter); if (submenu != NULL) { //word is mis-spelt item = e2_menu_add (menu, _("Spelling suggestions"), NULL, NULL, NULL, NULL); gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu); e2_menu_add_separator (menu); } } #endif item = e2_menu_add (menu, _("_Save"), GTK_STOCK_SAVE, _("Save the file"), _e2_edit_dialog_save_cb, rt); gtk_widget_set_sensitive (item, rt->is_dirty); e2_menu_add (menu, _("Save as.."), GTK_STOCK_SAVE_AS, //no available valid mnemonic _("Save the file with a new name"), _e2_edit_dialog_saveas_cb, rt); item = e2_menu_add (menu, _("Save se_lection.."), "save_selection_"E2IP".png", _("Save the selected text"), _e2_edit_dialog_savesel_cb, rt); gtk_widget_set_sensitive (item, gtk_text_buffer_get_selection_bounds (rt->textbuffer, NULL, NULL)); #ifdef USE_GTK2_10 gchar *tip = (gtk_text_buffer_get_has_selection (rt->textbuffer)) ? _("Print selected text") : _("Print file"); e2_menu_add (menu, _("_Print.."), GTK_STOCK_PRINT, tip, e2_view_dialog_print_cb, rt); #endif item = e2_menu_add (menu, _("R_efresh"), GTK_STOCK_REVERT_TO_SAVED, _("Reload the file being edited"), _e2_edit_dialog_refresh_cb, rt); gtk_widget_set_sensitive (item, rt->is_dirty); e2_menu_add_separator (menu); e2_menu_add (menu, _("_Find.."), GTK_STOCK_FIND, _("Find matching text"), _e2_edit_dialog_find_cb, rt); e2_menu_add (menu, _("_Replace.."), GTK_STOCK_FIND_AND_REPLACE, _("Find and replace matching text"), _e2_edit_dialog_replace_cb, rt); item = e2_menu_add (menu, _("_Hide"), GTK_STOCK_ZOOM_FIT, _("Hide the search options bar"), _e2_edit_dialog_hide_cb, rt); gtk_widget_set_sensitive (item, !rt->is_hidden); item = e2_menu_add (menu, _("_Undo"), GTK_STOCK_UNDO, _("Undo last change"), _e2_edit_dialog_undo_cb, rt); gtk_widget_set_sensitive (item, rt->undo_enabled); #ifdef E2_REDO_ENABLED item = e2_menu_add (menu, _("Re_do"), GTK_STOCK_REDO, _("Reverse last undo"), _e2_edit_dialog_redo_cb, rt); gtk_widget_set_sensitive (item, rt->redo_enabled); #endif #ifdef E2_SPELLCHECK if (rt->spelldata == NULL) e2_menu_add (menu, _("_Check spelling"), GTK_STOCK_SPELL_CHECK, _("Flag mis-spelt words"), _e2_edit_dialog_spellall_cb, rt); else e2_menu_add (menu, _("_Clear spellcheck"), GTK_STOCK_SPELL_CHECK, _("Remove all spell-check flags"), _e2_edit_dialog_unspellall_cb, rt); #endif e2_menu_add_separator (menu); #ifdef E2_SPELLCHECK item = e2_menu_add (menu, _("_Language.."), GTK_STOCK_INDEX, _("Set spell-checker language"), _e2_edit_dialog_spell_language_cb, rt); gtk_widget_set_sensitive (item, (rt->spelldata != NULL)); #endif item = e2_menu_add_check (menu, _("_Wrap"), rt->textwrap, _e2_edit_dialog_wrap_cb, rt); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif item, _("If activated, text in the window will be word-wrapped")); item_name = g_strconcat (_A(2),".",_A(32),NULL); e2_menu_add_action (menu, _("Se_ttings"), GTK_STOCK_PREFERENCES, _("Open the configuration dialog at the options page"), item_name, _C(11), //_("dialogs") NULL); g_free(item_name); g_signal_connect (G_OBJECT (menu), "selection-done", G_CALLBACK (e2_menu_destroy_cb), NULL); if (event_button == 0) gtk_menu_popup (GTK_MENU (menu), NULL, NULL, (GtkMenuPositionFunc) e2_view_dialog_set_menu_position, rt, event_button, event_time); else //this was a button-3 click gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, event_button, event_time); } /*****************/ /***** utils *****/ /*****************/ /** @brief store dialog button response General decoder not used as we have both Cancel and No buttons This expects a dialog paused with gtk_main() @param dialog UNUSED the dialog where the response was generated @param response the generated response number @param retval ptr to store for the response @return */ static void _e2_edit_dialog_response_decode_cb (GtkDialog *dialog, gint response, gint *retval) { *retval = response; gtk_main_quit (); } static void _e2_edit_dialog_destroy (E2_ViewDialogRuntime *rt, gboolean forced) { if (rt->is_dirty) { gint retval; GtkWidget *dialog = e2_dialog_create (GTK_STOCK_DIALOG_QUESTION, _("Save modified file ?"), _("confirm"), _e2_edit_dialog_response_decode_cb, &retval); //arrange for a specific retval (NO_TO_ALL) if the user presses Esc // e2_dialog_set_negative_response (dialog, E2_RESPONSE_USER1); e2_dialog_show (dialog, app.main_window, 0, &E2_BUTTON_CANCEL, &E2_BUTTON_NO, &E2_BUTTON_YES, NULL); gtk_main (); if (GTK_IS_WIDGET (dialog)) gtk_widget_destroy (dialog); switch (retval) { case GTK_RESPONSE_YES: _e2_edit_dialog_save_cb (NULL, rt); case GTK_RESPONSE_NO: break; default: if (!forced) return; //close-dialog or Escape-press just cancels the close, in effect break; } } if (rt->blink_init_id > 0) { g_source_remove (rt->blink_init_id); rt->blink_init_id = 0; } if (rt->blink_id > 0) { g_source_remove (rt->blink_id); rt->blink_id = 0; } GtkWidget *entry = GTK_BIN (rt->combo2)->child; const gchar *repl = gtk_entry_get_text (GTK_ENTRY (entry)); if (*repl != '\0') { e2_combobox_activated_cb (entry, GINT_TO_POINTER (FALSE)); e2_list_update_history (repl, &replace_history, NULL, 30, FALSE); } //if editing an output tab, need some specific cleanup GList *tmp; for (tmp = app.tabslist; tmp != NULL; tmp = tmp->next) { if (((E2_OutputTabRuntime*)tmp->data)->buffer == rt->textbuffer) { //we were editing an output pane buffer //that stays in existence, so we need to get rid of redundant callbacks g_signal_handlers_disconnect_by_func (G_OBJECT (rt->textbuffer), _e2edtdlg_insert_text_cb, rt); g_signal_handlers_disconnect_by_func (G_OBJECT (rt->textbuffer), _e2edtdlg_delete_range_cb, rt); g_signal_handlers_disconnect_by_func (G_OBJECT (rt->textbuffer), _e2edtdlg_begin_user_action_cb, rt); g_signal_handlers_disconnect_by_func (G_OBJECT (rt->textbuffer), _e2edtdlg_end_user_action_cb, rt); g_signal_handlers_disconnect_by_func (G_OBJECT (rt->textbuffer), _e2_edit_dialog_dirty_change_cb, rt); #ifdef E2_MARK_FINDS if (rt->is_lit) e2_view_dialog_clear_hilites (rt); //also clears callback #endif break; } } #ifdef E2_SPELLCHECK if (rt->spelldata != NULL) gtkspell_detach (rt->spelldata); //this frees GtkSpell data #endif e2_view_dialog_destroy (rt); } /** @brief toggle cursor visibility This is a timer callback @param textview the textview for the dialog @return TRUE (so the timer continues) if @a textview is not focused */ // FORCED CURSOR BLINKING STUFFS UP LARGER VERTICAL MOVES static gboolean _e2_edit_dialog_blink (E2_ViewDialogRuntime *rt) { // printd (DEBUG, "edit dialog cursor visibility toggle cb"); // gdk_threads_enter (); // gboolean blinkme = TRUE; //GTK_WIDGET_HAS_FOCUS (rt->dialog); // if (blinkme) // { // g_source_remove (blink_id); if (GTK_IS_WIDGET (rt->textview)) { //the dialog hasn't been closed between callbacks gboolean visible = gtk_text_view_get_cursor_visible (GTK_TEXT_VIEW (rt->textview)); gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (rt->textview), !visible); gint period = (visible) ? blinkmsec * CURSOR_OFF_MULTIPLIER: blinkmsec * CURSOR_ON_MULTIPLIER; if (rt->ow_mode) period /= INSERT_CURSOR_DIVIDER; //FIXME this timer needs to be cancellable at session-end rt->blink_id = g_timeout_add_full (G_PRIORITY_LOW, period, (GSourceFunc) _e2_edit_dialog_blink, rt, NULL); } // } // gdk_threads_leave (); // return !blinkme; //remove ourself if we're done // return TRUE; //already removed return FALSE; //remove ourself } /** @brief initialize cursor blinking This is an idle callback @param rt data struct for the dialog @return FALSE(so the idle callback is cancelled) */ static gboolean _e2_edit_dialog_blink_start (E2_ViewDialogRuntime *rt) { // printd (DEBUG, "Cursor blinking started"); if (rt->blink_id == 0) rt->blink_id = g_timeout_add_full (G_PRIORITY_HIGH_IDLE, blinkmsec * CURSOR_OFF_MULTIPLIER, (GSourceFunc) _e2_edit_dialog_blink, rt, NULL); rt->blink_init_id = 0; return FALSE; } /** @brief suspend cursor blinking @param rt data struct for the dialog @return */ static void _e2_edit_dialog_blink_stop (E2_ViewDialogRuntime *rt) { if (rt->blink_id > 0) { g_source_remove (rt->blink_id); rt->blink_id = 0; } gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (rt->textview), TRUE); //setup to go again when next idle rt->blink_init_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE, (GSourceFunc)_e2_edit_dialog_blink_start, rt, NULL); } /*********************/ /***** callbacks *****/ /*********************/ /** @brief text-buffer edited callback @param textbuffer the textbuffer which is now clean or dirty @param rt dialog runtime data struct @return */ static void _e2_edit_dialog_dirty_change_cb (GtkTextBuffer *textbuffer, E2_ViewDialogRuntime *rt) { rt->is_dirty = !rt->is_dirty; gtk_widget_set_sensitive (rt->savebtn, rt->is_dirty); } /** @brief menu-button press callback @param textview the textview widget where the press happened @param rt dialog runtime data struct @return TRUE always */ static gboolean _e2_edit_dialog_popup_menu_cb (GtkWidget *textview, E2_ViewDialogRuntime *rt) { _e2_edit_dialog_blink_stop (rt); gint event_time = gtk_get_current_event_time (); _e2_edit_dialog_show_context_menu (textview, 0, event_time, rt); return TRUE; } /** @brief general dialog key press callback @param textview UNUSED the widget where the button was pressed @param event gdk event data @param rt rt data for the dialog @return TRUE (stop other handlers) for recognised keys */ gboolean e2_edit_dialog_key_press_cb (GtkWidget *textview, GdkEventKey *event, E2_ViewDialogRuntime *rt) { _e2_edit_dialog_blink_stop (rt); guint mask = event->state & gtk_accelerator_get_default_mod_mask (); guint lower = (gdk_keyval_is_upper (event->keyval)) ? gdk_keyval_to_lower (event->keyval) : event->keyval; if (//g_ascii_isalpha (event->keyval) //&& (event->keyval < 0xF000 || event->keyval > 0xFFFF) //&& (mask & GDK_CONTROL_MASK || mask == GDK_MOD1_MASK)) { // the key is a letter and the modifier is Alt or a modifier is Ctrl if (lower == save_keycode) { if (rt->is_dirty) _e2_edit_dialog_save_cb (NULL, rt); return TRUE; } if (lower == find_keycode || lower == GDK_g) //special case again { #ifdef E2_SPELLCHECK if (rt->spelldata != NULL) _e2_edit_dialog_cancel_spell (rt); #endif rt->release_blocked = TRUE; //block anomalous key-release-event searches //trick cb into doing a from-start|end search for Ctrl f _e2_edit_dialog_find_cb ( (lower == find_keycode) ? NULL : GINT_TO_POINTER (1), rt); return TRUE; } else if (lower == replace_keycode) { #ifdef E2_SPELLCHECK if (rt->spelldata != NULL) _e2_edit_dialog_cancel_spell (rt); #endif _e2_edit_dialog_replace_cb (NULL, rt); return TRUE; } else if (lower == hide_keycode) { _e2_edit_dialog_hide_cb (NULL, rt); return TRUE; } else if (lower == undo_keycode) { if (rt->undo_enabled) _e2edtdlg_undo_undo_one (rt); return TRUE; } else if (lower == GDK_z) { //no Alt-z support if (mask == GDK_MOD1_MASK) return FALSE; if (rt->undo_enabled) _e2edtdlg_undo_undo_one (rt); return TRUE; } #ifdef E2_REDO_ENABLED else if (lower == redo_keycode) { if (rt->redo_enabled) _e2edtdlg_undo_redo_one (rt); return TRUE; } #endif } #ifdef E2_REDO_ENABLED else if (lower == GDK_z && mask == (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) { if (rt->redo_enabled) _e2edtdlg_undo_redo_one (rt); return TRUE; } #endif else if (lower == GDK_Insert) { //GTK_TEXT_VIEW (rt->textview)->overwrite_mode can get overwrite_mode state rt->ow_mode = !gtk_text_view_get_overwrite (GTK_TEXT_VIEW (rt->textview)); gtk_text_view_set_overwrite (GTK_TEXT_VIEW (rt->textview), rt->ow_mode); return TRUE; } // else if (lower == GDK_Delete) // { //workaround gtk behaviour // gtk_text_buffer_set_modified (rt->textbuffer, TRUE); // } rt->keyval = event->keyval; //save, for undo processing return FALSE; } /** @brief replace combo-box key press callback This ensures that a Return keypress behaves like Ctrl-r @param entry UNUSED the widget where the key was pressed @param event gdk event data @param rt rt data for the dialog @return TRUE if Return was pressed */ static gboolean _e2_edit_dialog_combokey_cb (GtkWidget *entry, GdkEventKey *event, E2_ViewDialogRuntime *rt) { switch (event->keyval) { case GDK_Return: _e2_edit_dialog_replace_cb (NULL, rt); return TRUE; //handle some other special cases too case GDK_g: case GDK_G: case GDK_z: return (e2_edit_dialog_key_press_cb (rt->textview, event, rt)); break; default: if (event->keyval == find_keycode || event->keyval == replace_keycode || event->keyval == hide_keycode) return (e2_edit_dialog_key_press_cb (rt->textview, event, rt)); else if (gdk_keyval_is_upper (event->keyval)) { guint lower = gdk_keyval_to_lower (event->keyval); if (lower == find_keycode || lower == replace_keycode || lower == hide_keycode) return (e2_edit_dialog_key_press_cb (rt->textview, event, rt)); } break; } return FALSE; } /** @brief mouse button press callback @param textview the widget where the button was pressed @param event gdk event data @param rt rt data for the dialog @return TRUE (stop other handlers) for btn 3 press, else FALSE (allow other handlers) */ static gboolean _e2_edit_dialog_button_press_cb (GtkWidget *textview, GdkEventButton *event, E2_ViewDialogRuntime *rt) { _e2_edit_dialog_blink_stop (rt); if (event->button == 3) { _e2_edit_dialog_show_context_menu (textview, 3, event->time, rt); return TRUE; } return FALSE; } /** @brief edit dialog response callback @param dialog the dialog where the response was triggered @param response the number assigned the activated widget @param view rt data for the dialog @return */ static void _e2_edit_dialog_response_cb (GtkDialog *dialog, gint response, E2_ViewDialogRuntime *rt) { printd (DEBUG, "response_cb (dialog:_,response:%d,rt:_)", response); switch (response) { case E2_RESPONSE_FIND: #ifdef E2_SPELLCHECK if (rt->spelldata != NULL) _e2_edit_dialog_cancel_spell (rt); #endif _e2_edit_dialog_find_cb (NULL, rt); break; case E2_RESPONSE_USER1: //replace #ifdef E2_SPELLCHECK if (rt->spelldata != NULL) _e2_edit_dialog_cancel_spell (rt); #endif _e2_edit_dialog_replace_cb (NULL, rt); break; case E2_RESPONSE_USER2: //save _e2_edit_dialog_save_cb (NULL, rt); break; #ifdef E2_SPELLCHECK case E2_RESPONSE_USER3: //cancel spelling _e2_edit_dialog_cancel_spell (rt); break; #endif default: // case GTK_RESPONSE_CLOSE: //if close button is clicked, it is not "forced" _e2_edit_dialog_destroy (rt, (response != GTK_RESPONSE_CLOSE)); break; } } #ifdef E2_TRANSIENTKEYS /** @brief function to setup default key-bindings for edit dialog This is just to provide placeholders, the actual bindings are meaningless @param set pointer to option data struct @return */ static void _e2_edit_dialog_keybindings (E2_OptionSet *set) { //the key name strings are parsed by gtk, and no translation is possible e2_option_tree_setup_defaults (set, g_strdup("keybindings=<"), //internal name //the category string(s) here need to match the binding name // g_strconcat(_C(17),"||||",NULL), //_("general" g_strconcat(_C(11),"||||",NULL), //_("dialogs" g_strconcat("\t",_A(39),"||||",NULL), //_("edit" g_strconcat("\t\t|u","||",_A(119),".",_A(120),"|Home",NULL), g_strconcat("\t\t|n","||",_A(119),".",_A(120),"|End",NULL), g_strdup(">"), NULL); } #endif /**********************/ /**** dialog setup ****/ /**********************/ /** @brief create and show edit dialog @a localpath may be full path, or at least the name, of item to be edited. If @a localpath is not an absolute path, curr_view->dir is prepended If @a srt is non-NULL, and there's a problem during setup, @a srt is set to NULL Expects BGL closed when we arrive here @param localpath localised string, or NULL if editing a buffer @param buf pointer to output pane tab textbuffer, or NULL id editing a file @param srt pointer to store for returned dialog data, or NULL @return */ static void _e2_edit_dialog_create (VPATH *localpath, GtkTextBuffer *buf, E2_ViewDialogRuntime **srt) { //init view runtime object (0's to ensure view-dialog things are NULL) E2_ViewDialogRuntime *rt = ALLOCATE0 (E2_ViewDialogRuntime); CHECKALLOCATEDWARN (rt, ) if (rt == NULL) { if (srt != NULL) *srt = NULL; return; } gint char_width, char_height; gboolean fileedit = (localpath != NULL); if (fileedit) { //edit file rt->textview = gtk_text_view_new (); // add the item's content gdk_threads_leave (); //any downstream error message does its own mutex management if (!e2_view_dialog_read_text (localpath, rt)) { gdk_threads_enter (); gtk_widget_destroy (rt->textview); DEALLOCATE (E2_ViewDialogRuntime, rt); if (srt != NULL) *srt = NULL; return; } gdk_threads_enter (); e2_view_dialog_set_font (&char_width, &char_height, rt); } else { //edit output pane buffer //CHECKME any benefit from a flag to distinguish output edtis ? const gchar *home = g_get_home_dir (); gchar *name = g_strconcat (_C(27), "-", _A(39), NULL); //_("output-edit") rt->localpath = g_build_filename (home, name, NULL); rt->charset = "UTF-8"; //the text is from our buffer g_free (name); //textview defaults to editable and cursor visible rt->textview = gtk_text_view_new_with_buffer (buf); rt->textbuffer = buf; //preserve appearance of output pane const gchar *fontname = e2_utils_get_output_font (); e2_widget_set_font (rt->textview, fontname); e2_widget_get_font_pixels (rt->textview, &char_width, &char_height); } rt->case_sensitive = case_sensitive; rt->whole_words = whole_words; rt->search_backward = search_backward; rt->search_wrap = search_wrap; #ifdef E2_MARK_FINDS rt->research = FALSE; e2_view_dialog_init_hilites (rt); #endif //dunno why this gets clobbered when threads used, but ... rt->charset = g_strdup (rt->charset); // gtk_text_buffer_set_modified (rt->textbuffer, FALSE); gtk_text_buffer_set_modified (rt->textbuffer, !fileedit); // rt->is_dirty = FALSE; not needed if FALSE set above rt->is_dirty = !fileedit; // rt->undo_list = NULL; #ifdef E2_REDO_ENABLED // rt->redo_list = NULL; #endif // rt->ui_tmp = NULL; // rt->seq_reserve = FALSE; //default value for ui->seq ?? gchar *utf = F_FILENAME_FROM_LOCALE (rt->localpath); rt->dialog = e2_dialog_create (NULL, utf, _("editing file"), _e2_edit_dialog_response_cb, rt); F_FREE (utf); gtk_window_set_type_hint (GTK_WINDOW (rt->dialog), GDK_WINDOW_TYPE_HINT_NORMAL); //override some default label properties GtkWidget *label = g_object_get_data (G_OBJECT (rt->dialog), "e2-dialog-label"); // gtk_label_set_line_wrap (GTK_LABEL (label), FALSE); gtk_label_set_selectable (GTK_LABEL (label), TRUE); e2_widget_add_separator (GTK_DIALOG (rt->dialog)->vbox, FALSE, 0); //create the view GtkWidget *sw = e2_widget_add_sw (GTK_DIALOG (rt->dialog)->vbox, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, TRUE, E2_PADDING); gtk_container_add (GTK_CONTAINER (sw), rt->textview); gtk_widget_show (rt->textview); // set view defaults rt->textwrap = e2_option_bool_get ("dialog-view-wrap"); gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (rt->textview), rt->textwrap ? GTK_WRAP_WORD : GTK_WRAP_NONE); rt->window_width = e2_option_int_get ("dialog-view-width"); rt->window_height = e2_option_int_get ("dialog-view-height"); if (!fileedit) { GtkTextMark *mark = gtk_text_buffer_get_insert (buf); gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (rt->textview), mark, 0.2, TRUE, 0.0, 0.5); } // rt->idle_id = 0; //init search runtime data rt->history = e2_list_copy_with_data (find_history); //CHECKME why copy these ? rt->rephistory = e2_list_copy_with_data (replace_history); //action_area is a GtkHButtonBox packed at the end of the dialog's vbox //ditto for dialog->separator //locate find-bar between those 2 rt->panel = e2_widget_get_box (TRUE, FALSE, 0); gtk_box_pack_end (GTK_BOX (GTK_DIALOG (rt->dialog)->vbox), rt->panel, FALSE, TRUE, E2_PADDING_XSMALL); gtk_box_reorder_child (GTK_BOX (GTK_DIALOG (rt->dialog)->vbox), rt->panel, 1); //add handlebox GtkWidget *hndlbox = gtk_handle_box_new (); gtk_handle_box_set_shadow_type (GTK_HANDLE_BOX (hndlbox), GTK_SHADOW_NONE); gtk_handle_box_set_handle_position (GTK_HANDLE_BOX (hndlbox), GTK_POS_LEFT); gtk_container_add (GTK_CONTAINER (rt->panel), hndlbox); gtk_widget_show (hndlbox); GtkWidget *vbox = gtk_vbox_new (FALSE, 0); gtk_widget_show (vbox); gtk_container_add (GTK_CONTAINER (hndlbox), vbox); //create search bar rt->sgroup = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); GtkWidget *hbox = e2_view_dialog_create_searchbar (rt); g_object_unref (G_OBJECT(rt->sgroup)); gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); //create (hidden) replace bar, with buttons-area same size as buttons in search bar //(so that the corresponding expandable entries are the same size) rt->replacebar = hbox = gtk_hbox_new (FALSE, 0); //add replace-string combo rt->combo2 = e2_combobox_add (hbox, TRUE, 0, NULL, NULL, &replace_history, E2_COMBOBOX_HAS_ENTRY | E2_COMBOBOX_FOCUS_ON_CHANGE); // gtk_widget_set_size_request (rt->combo2, 230, -1); // gtk_size_group_add_widget (group, rt->combo2); GtkWidget *entry = GTK_BIN (rt->combo2)->child; g_signal_connect_after (G_OBJECT (entry), "key-release-event", G_CALLBACK (_e2_edit_dialog_combokey_cb), rt); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif entry, _("Replacements")); //add replace-option buttons (mnemonics conform to search and action buttons) GtkWidget *hbox2 = e2_widget_add_box (hbox, FALSE, 0, FALSE, FALSE, 0); e2_button_add_toggle (hbox2, TRUE, do_next, _("r_epeat"),_("If activated, the next match will be sought after each replacement"), do_next, E2_PADDING_XSMALL, e2_view_dialog_toggled, &do_next); e2_button_add_toggle (hbox2, TRUE, replace_all, _("_all"), _("If activated, all matches will be replaced at once"), replace_all, E2_PADDING_XSMALL, e2_view_dialog_toggled, &replace_all); e2_button_add_toggle (hbox2, TRUE, confirm_before, _("co_nfirm"),_("If activated, confirmation will be sought when \"replacing all\""), confirm_before, E2_PADDING_XSMALL, e2_view_dialog_toggled, &confirm_before); gtk_size_group_add_widget (rt->sgroup, hbox2); gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); //now add things to the action-area //produce but don't yet show the "not found" warning gchar *labeltext = g_strconcat ("", _("not found"), "", NULL); rt->info_label = e2_widget_add_mid_label (GTK_DIALOG (rt->dialog)->action_area, labeltext, 0.0, TRUE, 0); //left-align the label gtk_button_box_set_child_secondary ( GTK_BUTTON_BOX (GTK_DIALOG (rt->dialog)->action_area), rt->info_label, TRUE); gtk_widget_hide (rt->info_label); labeltext = _("_Replace"); rt->replacebtn = e2_dialog_add_undefined_button (rt->dialog, GTK_STOCK_CONVERT, labeltext, E2_RESPONSE_USER1); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif rt->replacebtn, _("Replace this match")); gtk_widget_hide (rt->replacebtn); replace_keycode = e2_utils_get_mnemonic_keycode (labeltext); labeltext = _("_Find"); rt->findbtn = e2_dialog_add_undefined_button (rt->dialog, GTK_STOCK_FIND, labeltext, E2_RESPONSE_FIND); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif rt->findbtn, _("Find the next match")); gtk_widget_hide (rt->findbtn); find_keycode = e2_utils_get_mnemonic_keycode (labeltext); labeltext = _("_Save"); rt->savebtn = e2_dialog_add_undefined_button (rt->dialog, GTK_STOCK_SAVE, labeltext, E2_RESPONSE_USER2); //allow saving of a fresh output buffer gtk_widget_set_sensitive (rt->savebtn, !fileedit); save_keycode = e2_utils_get_mnemonic_keycode (labeltext); e2_dialog_add_defined_button (rt->dialog, &E2_BUTTON_CLOSE); // e2_dialog_set_responses (rt->dialog, E2_RESPONSE_FIND, GTK_RESPONSE_CLOSE); //make sure we always exit cleanly e2_dialog_set_negative_response (rt->dialog, GTK_RESPONSE_CLOSE); //some other keycodes are related only to context menu item labels ... hide_keycode = e2_utils_get_mnemonic_keycode (_("_Hide")); undo_keycode = e2_utils_get_mnemonic_keycode (_("_Undo")); #ifdef E2_REDO_ENABLED redo_keycode = e2_utils_get_mnemonic_keycode (_("Re_do")); #endif //this prevents a check button from being activated by keyboard // gtk_dialog_set_default_response (GTK_DIALOG (rt->dialog), E2_RESPONSE_FIND); gtk_window_resize (GTK_WINDOW (rt->dialog), char_width * rt->window_width, (char_height+3) * rt->window_height); //setup for cursor management GtkSettings *defs = gtk_widget_get_settings (rt->textview); if (defs != NULL) { g_object_get (defs, "gtk-cursor-blink-time", &blinkmsec, NULL); } else blinkmsec = 1200; //gtk default blinkmsec *= OVERWR_CURSOR_MULTIPLIER; rt->blink_init_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE, (GSourceFunc)_e2_edit_dialog_blink_start, rt, NULL); //initialize undo arrangements _e2edtdlg_undo_initialize (rt->textbuffer, rt); #ifdef E2_TRANSIENTKEYS //add dialog-specific key bindings, before the key-press callback //group name (must be freeable) // rt->key_binding = g_strconcat (_C(17), ".", _A(39), NULL); //_(general.edit rt->key_binding = g_strconcat (_C(11), ".", _A(39), NULL); //_(dialogs.edit e2_keybinding_register_transient (rt->key_binding, GTK_WIDGET (rt->textview), _e2_edit_dialog_keybindings); #endif g_signal_connect (G_OBJECT (rt->textbuffer), "modified-changed", G_CALLBACK (_e2_edit_dialog_dirty_change_cb), rt); g_signal_connect (G_OBJECT (rt->textview), "popup-menu", G_CALLBACK (_e2_edit_dialog_popup_menu_cb), rt); g_signal_connect (G_OBJECT (rt->textview), "button-press-event", G_CALLBACK (_e2_edit_dialog_button_press_cb), rt); g_signal_connect (G_OBJECT (rt->textview), "key-press-event", G_CALLBACK (e2_edit_dialog_key_press_cb), rt); if (srt != NULL) *srt = rt; //make rt available to re-edit func } /******************/ /***** action *****/ /******************/ /** @brief re-open file for editing, at the last-used location in the file This returns immediately, so if run in a Q-thread, that will end @param from the widget that was activated to initiate the action @param art runtime data for the action @return TRUE if the dialog was opened */ static gboolean _e2_edit_dialog_reopen (gpointer from, E2_ActionRuntime *art) { return (e2_task_do_task (E2_TASK_EDIT, art, from, _e2_edit_dialog_reeditQ, NULL)); } static gboolean _e2_edit_dialog_reeditQ (E2_ActionTaskData *qed) { //FIXME allow specification of a name //process the 1st selected item in active pane GPtrArray *names = qed->names; E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata; //".." entries filtered when names compiled //for name comparisons, we need the full path gchar *local = e2_utils_strcat (qed->currdir, (*iterator)->filename); #ifdef E2_VFS VPATH ddata = { local, qed->currspace }; #endif E2_ViewDialogRuntime *vrt = NULL; gdk_threads_enter (); #ifdef E2_VFS _e2_edit_dialog_create (&ddata, NULL, &vrt); #else _e2_edit_dialog_create (local, NULL, &vrt); #endif gdk_threads_leave (); if (vrt != NULL) { gdk_threads_enter (); e2_view_dialog_show_atlast #ifdef E2_VFS (&ddata, vrt); #else (local, vrt); #endif gdk_threads_leave (); g_free (local); return TRUE; } g_free (local); return FALSE; } /******************/ /***** public *****/ /******************/ /** @brief create and show edit dialog @a filename may not have any path, or an absolute path. In those cases, curr_view->dir will be prepended This returns immediately, so if run in a Q-thread, that will end Expects BGL closed @param localpath localised string which has at least the name of item to be processed, or NULL @param buf pointer to a GtkTextBuffer to edit an output pane tab, or NULL @return TRUE if the dialog was created */ gboolean e2_edit_dialog_create (VPATH *localpath, GtkTextBuffer *buf) { //there is no async thread protection here, as this func can be called from //different contexts - any protection is done by the caller E2_ViewDialogRuntime *vrt; if (localpath != NULL || buf != NULL) { _e2_edit_dialog_create (localpath, buf, &vrt); if (vrt != NULL) { WAIT_FOR_EVENTS_SLOWLY e2_dialog_show (vrt->dialog, app.main_window, E2_DIALOG_DONT_SHOW_ALL, NULL); gtk_window_present (GTK_WINDOW (vrt->dialog)); return TRUE; } } return FALSE; } /** @brief register action related to edit dialog @return */ void e2_edit_dialog_actions_register (void) { #ifdef E2_SPELLCHECK //HACK while we're at it, init some session parameters e2_cache_list_register ("language-history", &lang_history); #endif gchar *action_name = g_strconcat(_A(5),".",_A(40),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_edit_dialog_reopen, NULL, FALSE); } /** @brief register config option related to edit dialog @return */ void e2_edit_dialog_options_register (void) { gchar *group_name = g_strconcat(_C(11),":",_A(39),NULL); //_("dialogs:edit" //first some options that may, but probably won't, change during the session e2_option_bool_register ("edit-save-backup", group_name, _("backup when saving"), _("When saving an edited file, an existing file with the same name will be renamed"), NULL, FALSE, E2_OPTION_FLAG_BASIC | E2_OPTION_FLAG_FREEGROUP); } emelfm2-0.4.1/src/dialogs/e2_edit_dialog_undo.c0000600000175000017500000003617410643544426020266 0ustar cairocairo/* $Id: e2_edit_dialog_undo.c 469 2007-07-06 22:58:30Z tpgww $ This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/dialogs/e2_edit_dialog_undo.c @brief undo functions for emelfm2 internal text-editor This file is #included in e2_edit_dialog.c (i.e. this won't rebuild unless that is rebuilt) */ /* This file has code imported from leafpad 0.8.7 Copyright (C) 2004-2006 Tarot Osuji Plus sprinkling of stuff from GtkSourceview's undo manager */ //prevent building of separate object file #ifdef INCLUDED_IN_PARENT /*********************/ /***** utilities *****/ /*********************/ /** @brief set flag for menu item (and keypress) sensitivity @param state TRUE to enable un-doing @return */ static void _e2edtdlg_set_undo_enabled (gboolean state, E2_ViewDialogRuntime *rt) { rt->undo_enabled = state; } #ifdef E2_REDO_ENABLED /** @brief set flag for menu item (and keypress) sensitivity @param state TRUE to enable re-doing @return */ static void _e2edtdlg_set_redo_enabled (gboolean state, E2_ViewDialogRuntime *rt) { rt->redo_enabled = state; } #endif /** @brief cleanup all @a info_list @param info_list list of UndoInfo's to be cleared (can be NULL) @return NULL */ static GList *_e2edtdlg_undo_clear_info_list (GList *info_list) { GList *iterator; for (iterator = info_list; iterator != NULL; iterator = iterator->next) { g_free (((E2_UndoInfo *)iterator->data)->str); DEALLOCATE (E2_UndoInfo, iterator->data); } g_list_free (info_list); return NULL; } /** @brief change the "sequence" flag of the last item in the undo list @param seq the new value of the flag @return */ static void _e2edtdlg_undo_set_sequency (gboolean seq, E2_ViewDialogRuntime *rt) { printd (DEBUG,"undo_set_sequency: %d", seq); if (g_list_length (rt->undo_list)) ((E2_UndoInfo *)g_list_last(rt->undo_list)->data)->seq = seq; } /** @brief change the "next sequence" flag @return */ //CHECKME needed ? /*static void _e2edtdlg_undo_set_sequency_reserve (void) { seq_reserve = TRUE; } */ /** @brief create and fill an undo data item, and add it to the undo list @param buffer the text buffer being processed @param command code for INS, DEL, BS @param start index into @a buffer of the start of the change @param end index into @a buffer of the end of the change @param str the text that comprises the change @return */ static void _e2edtdlg_undo_append_undo_info (GtkTextBuffer *buffer, gchar command, guint start, guint end, gchar *str, E2_ViewDialogRuntime *rt) { printd (DEBUG,"undo_cb: %d %s (%d-%d)", command, str, start, end); E2_UndoInfo *ui = ALLOCATE (E2_UndoInfo); CHECKALLOCATEDWARN (ui, return;) ui->command = command; ui->start = start; ui->end = end; // ui->seq = FALSE; ui->seq = rt->seq_reserve; //what is this ?? STATIC or not ? ui->str = str; rt->seq_reserve = FALSE; rt->undo_list = g_list_append (rt->undo_list, ui); } /** @brief log a change ready for it to be undone @param buffer the text buffer being processed @param command code for INS, DEL, BS @param start index into @a buffer of the start of the change @param end index into @a buffer of the end of the change @return */ static void _e2edtdlg_undo_create_undo_info (GtkTextBuffer *buffer, gchar command, guint start, guint end, E2_ViewDialogRuntime *rt) { GtkTextIter start_iter, end_iter; gboolean seq_flag = FALSE; gchar *str; gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start); gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end); str = gtk_text_buffer_get_text (buffer, &start_iter, &end_iter, FALSE); if (rt->undo_gstr->len) { //we have something in the sequence-store GString, so //check if this change extends the sequence if ((end - start == 1) && (command == rt->ui_tmp->command)) { switch (rt->keyval) { case GDK_BackSpace: if (end == rt->ui_tmp->start) seq_flag = TRUE; break; case GDK_Delete: if (start == rt->ui_tmp->start) seq_flag = TRUE; break; case GDK_Tab: case GDK_space: if (start == rt->ui_tmp->end) seq_flag = TRUE; break; default: if (start == rt->ui_tmp->end) if (rt->keyval && (rt->keyval < 0xF000 || rt->keyval > 0xFFFF)) switch (rt->prev_keyval) { case GDK_Return: case GDK_Tab: case GDK_space: break; default: seq_flag = TRUE; } } } if (seq_flag) { //we're extending the sequence, update the sequence-store switch (command) { case BS: rt->undo_gstr = g_string_prepend(rt->undo_gstr, str); rt->ui_tmp->start--; break; default: rt->undo_gstr = g_string_append(rt->undo_gstr, str); rt->ui_tmp->end++; } rt->prev_keyval = rt->keyval; _e2edtdlg_set_undo_enabled (TRUE, rt); #ifdef E2_REDO_ENABLED //CHECKME why do these when extending a sequence ? rt->redo_list = _e2edtdlg_undo_clear_info_list (rt->redo_list); _e2edtdlg_set_redo_enabled (FALSE, rt); #endif return; } //this one doesn't extend the sequence, so that's ended now //update the undo list from the sequence-store _e2edtdlg_undo_append_undo_info (buffer, rt->ui_tmp->command, rt->ui_tmp->start, rt->ui_tmp->end, g_strdup (rt->undo_gstr->str), rt); //and clear the sequence-store rt->undo_gstr = g_string_erase (rt->undo_gstr, 0, -1); } //begin a new change-data process if (!rt->keyval && rt->prev_keyval) _e2edtdlg_undo_set_sequency (TRUE, rt); if (end - start == 1 && (rt->keyval && (rt->keyval < 0xF000 || rt->keyval > 0xFFFF || rt->keyval == GDK_BackSpace || rt->keyval == GDK_Delete || rt->keyval == GDK_Tab))) { rt->ui_tmp->command = command; rt->ui_tmp->start = start; rt->ui_tmp->end = end; rt->undo_gstr = g_string_erase (rt->undo_gstr, 0, -1); g_string_append (rt->undo_gstr, str); } else _e2edtdlg_undo_append_undo_info (buffer, command, start, end, g_strdup(str), rt); _e2edtdlg_set_undo_enabled (TRUE, rt); #ifdef E2_REDO_ENABLED //after a change is logged, we can undo it, but not yet redo it rt->redo_list = _e2edtdlg_undo_clear_info_list (rt->redo_list); _e2edtdlg_set_redo_enabled (FALSE, rt); #endif rt->prev_keyval = rt->keyval; rt->keyval = 0; } /** @brief move undo information from sequence-buffer to the changes list @param buffer the text buffer being processed @return */ static void _e2edtdlg_undo_flush_sequence_buffer (GtkTextBuffer *buffer, E2_ViewDialogRuntime *rt) { if (rt->undo_gstr->len) { _e2edtdlg_undo_append_undo_info (buffer, rt->ui_tmp->command, rt->ui_tmp->start, rt->ui_tmp->end, g_strdup (rt->undo_gstr->str), rt); rt->undo_gstr = g_string_erase (rt->undo_gstr, 0, -1); } } /** @brief sync gtk's "buffer-dirty" flag with the cumulative un/re-do's @param buffer the text buffer being processed @return */ static void _e2edtdlg_undo_sync_modified_status (GtkTextBuffer *buffer, E2_ViewDialogRuntime *rt) { gboolean flag = (rt->changes_count == g_list_length (rt->undo_list)); printd (DEBUG,"sync status flag = %s (%d - %d)", (flag) ? "TRUE" : "FALSE", rt->changes_count, g_list_length (rt->undo_list)); if (gtk_text_buffer_get_modified (buffer) == flag) { gtk_text_buffer_set_modified (buffer, !flag); printd (DEBUG,"gtk buffer flag changed"); } } /*********************/ /***** callbacks *****/ /*********************/ /** @brief "insert-text" callback which logs inserted text @param buffer the text buffer being processed @param iter iter after the end of the insertion @param str the inserted string @param len UNUSED byte-length of @a str @param rt data struct for the dialog @return */ static void _e2edtdlg_insert_text_cb (GtkTextBuffer *buffer, GtkTextIter *iter, gchar *str, gint len, E2_ViewDialogRuntime *rt) { printd (DEBUG,"insert-text cb: %s", str); guint start, end; end = gtk_text_iter_get_offset (iter); start = end - g_utf8_strlen (str, -1); _e2edtdlg_undo_create_undo_info (buffer, INS, start, end, rt); } /** @brief "delete-range" callback which logs a deleted range of text @param buffer the text buffer being processed @param start_iter iter for start of deleted range @param end_iter iter for end of deleted range @param rt data struct for the dialog @return */ static void _e2edtdlg_delete_range_cb (GtkTextBuffer *buffer, GtkTextIter *start_iter, GtkTextIter *end_iter, E2_ViewDialogRuntime *rt) { printd (DEBUG,"delete-range cb"); guint start, end; gchar command; start = gtk_text_iter_get_offset (start_iter); end = gtk_text_iter_get_offset (end_iter); if (rt->keyval == GDK_BackSpace) command = BS; else command = DEL; _e2edtdlg_undo_create_undo_info (buffer, command, start, end, rt); } /** @brief "begin-user-action" callback unblocks "insert-text" and "delete-range" callbacks @param buffer the text buffer being processed @return */ static void _e2edtdlg_begin_user_action_cb (GtkTextBuffer *buffer, E2_ViewDialogRuntime *rt) { printd (DEBUG,"begin-user-action(unblock_func): keyval = 0x%X", rt->keyval); g_signal_handlers_unblock_by_func (G_OBJECT (buffer), _e2edtdlg_insert_text_cb, rt); g_signal_handlers_unblock_by_func (G_OBJECT (buffer), _e2edtdlg_delete_range_cb, rt); } /** @brief "end-user-action" callback blocks "insert-text" and "delete-range" callbacks @param buffer the text buffer being processed @return */ static void _e2edtdlg_end_user_action_cb (GtkTextBuffer *buffer, E2_ViewDialogRuntime *rt) { printd (DEBUG,"end-user-action(block_func)"); g_signal_handlers_block_by_func (G_OBJECT (buffer), _e2edtdlg_insert_text_cb, rt); g_signal_handlers_block_by_func (G_OBJECT (buffer), _e2edtdlg_delete_range_cb, rt); } /******************/ /***** public *****/ /******************/ /** @brief initialize undo arrangements @param buffer the text buffer being edited @return */ static void _e2edtdlg_undo_initialize (GtkTextBuffer *buffer, E2_ViewDialogRuntime *rt) { if (rt->ui_tmp == NULL) { rt->ui_tmp = ALLOCATE0 (E2_UndoInfo) CHECKALLOCATEDWARN (rt->ui_tmp, return); } else memset (rt->ui_tmp, 0, sizeof(E2_UndoInfo)); rt->ui_tmp->command = INS; if (rt->undo_gstr == NULL) rt->undo_gstr = g_string_new (""); else rt->undo_gstr = g_string_erase (rt->undo_gstr, 0, -1); rt->undo_list = _e2edtdlg_undo_clear_info_list (rt->undo_list); _e2edtdlg_set_undo_enabled (FALSE, rt); #ifdef E2_REDO_ENABLED rt->redo_list = _e2edtdlg_undo_clear_info_list (rt->redo_list); _e2edtdlg_set_redo_enabled (FALSE, rt); #endif rt->seq_reserve = FALSE; rt->changes_count = 0; rt->prev_keyval = 0; g_signal_connect_after (G_OBJECT (buffer), "insert-text", G_CALLBACK (_e2edtdlg_insert_text_cb), rt); g_signal_connect (G_OBJECT (buffer), "delete-range", G_CALLBACK (_e2edtdlg_delete_range_cb), rt); g_signal_connect_after (G_OBJECT (buffer), "begin-user-action", G_CALLBACK (_e2edtdlg_begin_user_action_cb), rt); g_signal_connect(G_OBJECT (buffer), "end-user-action", G_CALLBACK (_e2edtdlg_end_user_action_cb), rt); _e2edtdlg_end_user_action_cb (buffer, rt); } /** @brief undo a single change @param buffer the text buffer being processed @return TRUE if any undo was completed successfully */ static gboolean _e2edtdlg_undo_undo_one (E2_ViewDialogRuntime *rt) { GtkTextIter start_iter, end_iter; E2_UndoInfo *ui; GtkTextBuffer *buffer = rt->textbuffer; _e2edtdlg_undo_flush_sequence_buffer (buffer, rt); if (g_list_length (rt->undo_list)) { // undo_block_signal(buffer); ui = g_list_last (rt->undo_list)->data; gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, ui->start); switch (ui->command) { case INS: gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, ui->end); gtk_text_buffer_delete (buffer, &start_iter, &end_iter); break; default: gtk_text_buffer_insert(buffer, &start_iter, ui->str, -1); } #ifdef E2_REDO_ENABLED rt->redo_list = g_list_append (rt->redo_list, ui); #endif rt->undo_list = g_list_delete_link (rt->undo_list, g_list_last (rt->undo_list)); #ifdef E2_REDO_ENABLED printd (DEBUG,"edit_undo_cb: undo left = %d, redo left = %d", g_list_length(rt->undo_list), g_list_length (rt->redo_list)); #else printd (DEBUG,"edit_undo_cb: undo left = %d", g_list_length (rt->undo_list)); #endif // undo_unblock_signal(buffer); if (g_list_length (rt->undo_list)) { if (((E2_UndoInfo *)g_list_last (rt->undo_list)->data)->seq) return TRUE; } else _e2edtdlg_set_undo_enabled (FALSE, rt); #ifdef E2_REDO_ENABLED _e2edtdlg_set_redo_enabled (TRUE, rt); #endif if (ui->command == DEL) gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, ui->start); gtk_text_buffer_place_cursor (buffer, &start_iter); GtkTextMark *mark = gtk_text_buffer_get_mark (buffer, "insert"); gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (rt->textview), mark, 0.05, FALSE, 0,0); } _e2edtdlg_undo_sync_modified_status (buffer, rt); return FALSE; } /** @brief undo everything @param buffer the text buffer being processed @return */ /* UNUSED static void _e2edtdlg_undo_all (E2_ViewDialogRuntime *rt) { while (_e2edtdlg_undo_undo_one (rt)) {}; } */ #ifdef E2_REDO_ENABLED /** @brief redo a single change @param buffer the text buffer being processed @return TRUE if a redo happened */ static gboolean _e2edtdlg_undo_redo_one (E2_ViewDialogRuntime *rt) { GtkTextIter start_iter, end_iter; E2_UndoInfo *ri; GtkTextBuffer *buffer = rt->textbuffer; if (g_list_length (rt->redo_list)) { // _e2edtdlg_undo_block_signal (buffer); ri = g_list_last (rt->redo_list)->data; gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, ri->start); switch (ri->command) { case INS: gtk_text_buffer_insert (buffer, &start_iter, ri->str, -1); break; default: gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, ri->end); gtk_text_buffer_delete(buffer, &start_iter, &end_iter); } rt->undo_list = g_list_append (rt->undo_list, ri); rt->redo_list = g_list_delete_link (rt->redo_list, g_list_last (rt->redo_list)); printd (DEBUG,"edit_redo_cb: undo left = %d, redo left = %d", g_list_length (rt->undo_list), g_list_length (rt->redo_list)); // _e2edtdlg_undo_unblock_signal (buffer); if (ri->seq) { _e2edtdlg_undo_set_sequency (TRUE, rt); return TRUE; } if (!g_list_length(rt->redo_list)) _e2edtdlg_set_redo_enabled (FALSE, rt); _e2edtdlg_set_undo_enabled (TRUE, rt); gtk_text_buffer_place_cursor (buffer, &start_iter); GtkTextMark *mark = gtk_text_buffer_get_mark (buffer, "insert"); gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (rt->textview), mark, 0.05, FALSE, 0,0); } _e2edtdlg_undo_sync_modified_status (buffer, rt); return FALSE; } /** @brief redo everything @param buffer the text buffer being processed @return */ /* UNUSED static void _e2edtdlg_undo_redo_all (E2_ViewDialogRuntime *rt) { while (_e2edtdlg_undo_redo_one (rt)) {}; } */ #endif //def E2_REDO_ENABLED #endif //def INCLUDED_IN_PARENT emelfm2-0.4.1/src/dialogs/e2_dialog.h0000600000175000017500000001336311010340377016220 0ustar cairocairo/* $Id: e2_dialog.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2003-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/dialogs/e2_dialog.h @brief dialog utilities header file This file is the header for the dialog utility functions. */ #ifndef __E2_DIALOG_H__ #define __E2_DIALOG_H__ #include "emelfm2.h" #include "e2_about_dialog.h" #include "e2_config_dialog.h" //#include "e2_mkdir_dialog.h" #include "e2_select_image_dialog.h" #include "e2_view_dialog.h" //button signals from here #include "e2_button.h" #include "e2_fileview.h" //this supports one-time dialog setup (not yet implemented) //#define LATESHOW typedef enum { E2_DIALOG_RUNMODAL =1, E2_DIALOG_NONMODAL =1<<1, //not implemented E2_DIALOG_FREE =1<<2, E2_DIALOG_DONT_SHOW_ALL=1<<3, E2_DIALOG_THREADED =1<<4, //set to do threads_enter/leave around showing the dialog } E2_DialogFlags; typedef enum { OK = 1, CANCEL = 1 << 1, APPLY = 1 << 2, APPLY_TO_ALL= 1 << 3, YES = 1 << 4, YES_TO_ALL = 1 << 5, NO = 1 << 6, NO_TO_ALL = 1 << 7 } DialogButtons; typedef enum { NONE = 0, YESALL = 1, NOALL = 1 << 1, BOTHALL= 1 << 2 } OW_ButtonFlags; enum { E2_TYPE_CANCELLED, E2_TYPE_HANDLED, E2_TYPE_NOTHANDLED }; void e2_dialog_response_decode_cb (GtkDialog *dialog, gint response, DialogButtons *dialog_result); gboolean e2_dialog_cancel_cb (GtkWidget *widget, GtkWidget *dialog); gboolean e2_dialog_key_neg_cb (GtkWidget *widget, GdkEventKey *event, gpointer data); gboolean e2_dialog_key_press_cb (GtkWidget *widget, GdkEventKey *event, GtkWidget *dialog); void e2_dialog_show_cb (GtkWidget *dialog, GtkScrolledWindow *scrolled); void e2_dialog_show_notebook_cb (GtkWidget *dialog, GtkNotebook *book); /* tag PASSWORDINPUT DialogButtons e2_dialog_password_input (gchar* window_title, gchar *prompt, gchar **input); */ //generic dialog for line input DialogButtons e2_dialog_line_input (gchar* window_title, gchar *prompt, gchar *suggestion, OW_ButtonFlags extras, gboolean select_text, gchar **input); DialogButtons e2_dialog_combo_input (gchar* window_title, gchar *prompt, gchar *suggestion, OW_ButtonFlags extras, GList **history_list, gchar **input); //overwrite check dialog DialogButtons e2_dialog_ow_check (gchar *slocal, gchar *dlocal, OW_ButtonFlags extras); DialogButtons e2_dialog_warning (gchar *prompt); GtkWidget *e2_dialog_slow (gchar *prompt_type, gchar *tip_type, gpointer response_func, gpointer data); void e2_dialog_setup_chooser (GtkWidget *dialog, const gchar *title, const gchar *startpath, GtkFileChooserAction action, gboolean showhidden, gboolean multi, gint defresponse); //extern gboolean e2_dialog_key_cb (GtkWidget *widget, GdkEventKey *event, gpointer data); void e2_dialog_setup (GtkWidget *dialog, GtkWidget *parent); gint e2_dialog_show (GtkWidget *dialog, GtkWidget *parent, E2_DialogFlags flags, E2_Button *button, ...); gint e2_dialog_run (GtkWidget *dialog, GtkWidget *parent, E2_DialogFlags flags); //GtkWidget *get_dialog (GtkWidget *parent, GtkMessageType type, gchar *label_text); GtkWidget *e2_dialog_create (const gchar *stock, const gchar *label_text, const gchar *title, gpointer callback, gpointer data); GtkWidget *e2_dialog_add_sw (GtkWidget *dialog); GtkWidget *e2_dialog_add_defined_button (GtkWidget *dialog, E2_Button *button); GtkWidget *e2_dialog_add_undefined_button (GtkWidget *dialog, gchar *stock, gchar *label, gint response); GtkWidget *e2_dialog_add_button_custom ( GtkWidget *dialog, gboolean is_default, E2_Button *buttontype, gchar *tip, void *callback, void *data); GtkWidget *e2_dialog_add_undefined_button_custom ( GtkWidget *dialog, gboolean is_default, guint response, gchar *label, gchar *stock, gchar *tip, void *callback, void *data); GtkWidget *e2_dialog_add_toggle_button (GtkWidget *dialog, gboolean value, gchar *label, gchar *tip, gint response); GtkWidget *e2_dialog_add_check_button (GtkWidget *dialog, gboolean value, gchar *label, gchar *tip, gint response); void e2_dialog_attach_menu (GtkWidget *dialog, GtkWidget *menu); //void e2_dialog_set_positive_response (GtkWidget *dialog, gint response); void e2_dialog_set_negative_response (GtkWidget *dialog, gint response); //void e2_dialog_set_responses (GtkWidget *dialog, gint pos, gint neg); void e2_dialog_resize (GtkWidget *dialog, gfloat factor); void e2_dialog_resize_with_sw (GtkWidget *dialog, GtkWidget *scrolled); void e2_dialog_set_cursor (GtkWidget *dialog, GdkCursorType type); void e2_dialog_options_register (void); //dialog setup functions from other files void e2_name_filter_dialog_create_cb (GtkWidget *item, ViewInfo *view); void e2_size_filter_dialog_create_cb (GtkWidget *item, ViewInfo *view); void e2_date_filter_dialog_create_cb (GtkWidget *item, ViewInfo *view); void e2_select_items_byname_dialog_create (GtkWidget *item); gint e2_filetype_dialog_create (VPATH *localpath, gboolean text, gboolean ambig, gboolean newtype); DialogButtons e2_file_info_dialog_run (VPATH *localpath, gboolean multi); void e2_filetype_dialog_edit_create (gchar *category); void e2_opendir_dialog_create (ViewInfo *view, gchar *oldpath, gchar **newpath); #endif //ndef __E2_DIALOG_H__ emelfm2-0.4.1/src/dialogs/e2_about_dialog.c0000600000175000017500000002002310776374631017416 0ustar cairocairo/* $Id: e2_about_dialog.c 853 2008-04-07 10:38:17Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "emelfm2.h" #include #include "e2_dialog.h" #include "e2_about_dialog.h" /*****************/ /*** callbacks ***/ /*****************/ /** @brief about-dialog response-signal callback @param dialog the about-dialog @param response UNUSED the response code generated by the dialog @param data pointer to data for the dialog @return */ static void _e2_about_dialog_response_cb (GtkDialog *dialog, gint response, E2_Sextet *data) { gchar *command; switch (response) { case E2_RESPONSE_USER1: //read button click if (data->d == data->a) //check whether a file-headiong is used command = g_strconcat(_A(5),".",_A(101), NULL); //file.view else command = g_strconcat(_A(5),".",_A(103), NULL); //file.view_at e2_action_run_simple (command, (gchar *)data->d); g_free (command); break; default: //cleanup the strings constructed when dialog set up F_FREE (data->a); g_free (data->b); g_free (data->c); e2_utils_sextet_destroy (data); gtk_widget_destroy (GTK_WIDGET (dialog)); break; } } /** @brief the notebook's "switch-page" signal callback Visisbility of view-file buttons is changed as appropriate @param notebook UNUSED the notebook widget @param page UNUSED the notebook page which is now focused @param page_num the index of the new page @param dialog_data pointer to dialog data @return */ static void _e2_about_dialog_tabchange_cb (GtkNotebook *notebook, GtkNotebookPage *page, guint page_num, E2_Sextet *dialog_data) { switch (page_num) { case 1: //contribs dialog_data->d = dialog_data->a; //set the string to use for viewing gtk_widget_show (dialog_data->f); break; case 2: //usage dialog_data->d = dialog_data->b; gtk_widget_show (dialog_data->f); break; case 3: //commands dialog_data->d = dialog_data->c; gtk_widget_show (dialog_data->f); break; default: // case 0: //main gtk_widget_hide (dialog_data->f); break; } } /** @brief create and show tabbed about-dialog @return the dialog widget, or NULL in case of a problem */ static GtkWidget *_e2_about_dialog_create (void) { E2_Sextet *dialog_data = e2_utils_sextet_new (); if (dialog_data == NULL) return NULL; gchar *localiconsdir = e2_utils_get_icons_path (FALSE); gchar *localpath = g_build_filename (localiconsdir, BINNAME"_48.png", NULL); GtkWidget *dialog = e2_dialog_create (localpath, "" PROGNAME " " VERSION RELEASE "", _("help"), _e2_about_dialog_response_cb, dialog_data); e2_dialog_set_negative_response (dialog, GTK_RESPONSE_CLOSE); gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE); //looks better, with notebook g_free (localiconsdir); g_free (localpath); GtkWidget *notebook = e2_widget_add_notebook (GTK_DIALOG(dialog)->vbox, TRUE, 0, NULL, NULL); gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_TOP); //info tab (auto scrollbars set when shown) GtkWidget *vbox = e2_widget_add_notebook_page (/*dialog, */notebook, _("about"), GTK_POLICY_NEVER); gchar *public = g_markup_escape_text (COPYRIGHT, -1); gchar *utf1 = e2_utf8_from_locale (BUILDSTAMP); // gchar *utf2 = e2_utf8_from_locale (BUILDINFO); gchar *message = g_strdup_printf ( // _("A GTK+2 file manager\n\n" // "implementing a two-pane design\n\n" _("An \"orthodox\" file manager for GTK+2\n\n" "Copyright © %s\n\n" //© = © "This program is licensed under the terms of the General Public License " "and comes with ABSOLUTELY NO WARRANTY\n\n" "This binary was compiled on %s\n" "using %s and GTK+%d.%d.%d"), public, utf1, BUILDINFO, GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION // public, BUILDSTAMP, BUILDINFO, GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION ); GtkWidget *label = e2_widget_add_mid_label (vbox, message, 0.5, FALSE, 0); gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_CENTER); // gtk_label_set_selectable (GTK_LABEL (label), TRUE); //convenience only BAD, selects text when tab is changed g_free (public); g_free (utf1); // g_free (utf2); g_free (message); gchar *fmsg = _("The file\n%s\ngives details."); //contribs tab (auto scrollbars set when shown) vbox = e2_widget_add_notebook_page (/*dialog, */notebook, _("contributors"), GTK_POLICY_NEVER); //assume no escaping needed in DOC_DIR gchar *helpfile = F_FILENAME_FROM_LOCALE (DOC_DIR G_DIR_SEPARATOR_S "CREDITS"); gchar *message2 = g_strdup_printf (fmsg, helpfile); message = g_strconcat ( _("This program is based on emelFM, developed by Michael Clark.\n\n" "Contributions have been made by many friends."), " ", message2, NULL); label = e2_widget_add_mid_label (vbox, message, 0.0, FALSE, 0); gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT); // gtk_label_set_selectable (GTK_LABEL (label), TRUE); g_free (message2); g_free (message); dialog_data->a = helpfile; //F_FREE this later //usage tab (auto scrollbars set when shown) gchar *title = _("usage"); //this is not yet? translated in the helpfile heading vbox = e2_widget_add_notebook_page (/*dialog, */notebook, title, GTK_POLICY_NEVER); helpfile = e2_option_str_get ("usage-help-doc"); message = g_strdup_printf (fmsg, helpfile); //same filename for usage and commands tabs label = e2_widget_add_mid_label (vbox, message, 0.0, FALSE, 0); //assumes no escaping needed gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT); // gtk_label_set_selectable (GTK_LABEL (label), TRUE); dialog_data->b = g_strconcat (helpfile," [usage]",NULL); //remember, for cleanup when finished //commands tab (auto scrollbars set when shown) title = _("commands"); vbox = e2_widget_add_notebook_page (/*dialog, */notebook, title, GTK_POLICY_NEVER); label = e2_widget_add_mid_label (vbox, message, 0.0, FALSE, 0); gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT); // gtk_label_set_selectable (GTK_LABEL (label), TRUE); g_free (message); dialog_data->c = g_strconcat (helpfile," [commands]",NULL); //remember, for cleanup when finished //data->f is read-button widget dialog_data->f = e2_dialog_add_undefined_button_custom (dialog, FALSE, E2_RESPONSE_USER1, _("_View"), "view_"E2IP".png", _("Read the file"), NULL, NULL); //update all srolled wingows' policies after sizing g_signal_connect (G_OBJECT (GTK_DIALOG (dialog)), "show", G_CALLBACK (e2_dialog_show_notebook_cb), GTK_NOTEBOOK (notebook)); g_signal_connect (G_OBJECT (notebook), "switch-page", G_CALLBACK (_e2_about_dialog_tabchange_cb), dialog_data); // e2_dialog_resize (dialog, 1.8); //guess what is big enough for usual fonts e2_dialog_show (dialog, app.main_window, 0, &E2_BUTTON_CLOSE, NULL); return dialog; } /******************/ /***** action *****/ /******************/ /** @brief about-dialog action to create and show dialog @param from the button, menu item etc which was activated @param art action runtime data @return TRUE always */ static gboolean _e2_about_dialog_open (gpointer from, E2_ActionRuntime *art) { GtkWidget *dialog = _e2_about_dialog_create (); return (dialog != NULL); } /******************/ /***** public *****/ /******************/ /** @brief register about-dialog action @return */ void e2_about_dialog_actions_register (void) { gchar *action_name = g_strconcat(_A(3),".",_A(25),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_about_dialog_open, NULL, FALSE); } emelfm2-0.4.1/src/e2_task.c0000600000175000017500000031244610776374631014322 0ustar cairocairo/* $Id: e2_task.c 853 2008-04-07 10:38:17Z tpgww $ Copyright (C) 2003-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /* TODO 25/10/04 some functions do not need TreeRowReferences (as of now, none do - no on-the-fly store content changes) when stability confirmed, move surrounding dis/enable refresh to e2_dialog_ow_check() itself ? */ #include "e2_task.h" #include #include #include #include "e2_dialog.h" #include "e2_option.h" #include "e2_ownership_dialog.h" #include "e2_filelist.h" #include "e2_filetype.h" GList *open_history = NULL; //history list for open-with dialog extern gint refresh_refcount; pthread_mutex_t task_mutex = PTHREAD_MUTEX_INITIALIZER; //PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP not needed pthread_t athreadID = 0; //action thread ID, 0 when stopped pthread_t mthreadID = 0; //monitor thread ID, 0 when stopped //pointers for passing customised data to Q store func GPtrArray *names_array; //this array will not be copied for Q data gchar *srcdir_local; //localised source dir, not copied for Q data gchar *destdir_local; //ditto for dest static gpointer _e2_task_progress_monitor (E2_TaskRuntime *rt); static gboolean _e2_task_move (E2_ActionTaskData *qed, gchar *trashpath); static gboolean _e2_task_copyQ (E2_ActionTaskData *qed); static gboolean _e2_task_copy_asQ (E2_ActionTaskData *qed); static gboolean _e2_task_trashitQ (E2_ActionTaskData *qed); static gboolean _e2_task_moveQ (E2_ActionTaskData *qed); static gboolean _e2_task_move_asQ (E2_ActionTaskData *qed); static gboolean _e2_task_symlinkQ (E2_ActionTaskData *qed); static gboolean _e2_task_symlink_asQ (E2_ActionTaskData *qed); static gboolean _e2_task_deleteQ (E2_ActionTaskData *qed); static gboolean _e2_task_renameQ (E2_ActionTaskData *qed); static gboolean _e2_task_permissionsQ (E2_ActionTaskData *qed); static gboolean _e2_task_ownershipQ (E2_ActionTaskData *qed); static gboolean _e2_task_file_infoQ (E2_ActionTaskData *qed); static gboolean _e2_task_viewQ (E2_ActionTaskData *qed); static gboolean _e2_task_editQ (E2_ActionTaskData *qed); static gboolean _e2_task_openQ (E2_ActionTaskData *qed); static gboolean _e2_task_open_withQ (E2_ActionTaskData *qed); //CHECKME need this ?static gboolean _e2_task_open_inotherQ (E2_ActionTaskData *qed); #ifdef E2_INCLIST /** @brief @param ref pointer to reference in current view @return */ static void _e2_task_treeview_line_remove (GtkTreeRowReference *ref) { GtkTreeIter iter; GtkTreePath *path = gtk_tree_row_reference_get_path (ref); if (path != NULL && gtk_tree_model_get_iter (curr_view->model, &iter, path)) gtk_list_store_remove (curr_view->store, &iter); } /** @brief @param ref pointer to reference in current view @param newname utf-8 string with replacement name for an item @return */ static void _e2_task_treeview_line_rename (GtkTreeRowReference *ref, gchar *newname) { gchar *savename; gchar *local = F_FILENAME_TO_LOCALE (newname); if (e2_fs_is_dir3 (local E2_ERR_NONE())) savename = g_strconcat (newname, G_DIR_SEPARATOR_S, NULL); else savename = newname; GtkTreeIter iter; GtkTreePath *path = gtk_tree_row_reference_get_path (ref); if (path != NULL && gtk_tree_model_get_iter (curr_view->model, &iter, path)) gtk_list_store_set (curr_view->store, &iter, FILENAME, savename, -1); F_FREE (local); if (savename != newname) g_free (savename); } //FIXME add fn to change permissions field in a row #endif //def E2_INCLIST /***********************/ /** support functions **/ /***********************/ /** @brief find the data for a running or paused task with pid @a id This does not distinguish between commands and actions @param pid the id of the process we're looking for @return the data struct for the task, or NULL if not found */ E2_TaskRuntime *e2_task_find_running_task (glong pid) { E2_TaskRuntime *rt; GList *member; pthread_mutex_lock (&task_mutex); member = app.taskhistory; pthread_mutex_unlock (&task_mutex); while (member != NULL) { rt = (E2_TaskRuntime *)member->data; if (rt != NULL && rt->pid == pid && (rt->status == E2_TASK_RUNNING || rt->status == E2_TASK_PAUSED)) return rt; pthread_mutex_lock (&task_mutex); member = member->next; pthread_mutex_unlock (&task_mutex); } return NULL; } /** @brief find the pid of the last-started child process that's still running This doesn't distinguish between commands and actions @param anytab TRUE to find a match in any tab, FALSE in the currently-displayed tab @return pointer to task data or NULL if not found */ E2_TaskRuntime *e2_task_find_last_running_child (gboolean anytab) { E2_TaskRuntime *rt; GList *member; pthread_mutex_lock (&task_mutex); member = g_list_last (app.taskhistory); pthread_mutex_unlock (&task_mutex); //backwards scan, don't bother locking at end of loop for (; member != NULL; member = member->prev) { rt = (E2_TaskRuntime *)member->data; if (rt != NULL && rt->status == E2_TASK_RUNNING && !rt->action && (anytab || rt->current_tab == &app.tab)) return rt; } return NULL; } /** @brief setup task data for a command or action This is called for a command just commenced (async) or about to commence (sync) or for a file-action about to be commenced or queued. Task data is added to the task Q if not already there, and initialised as appropriate for @a mode. @a mode = one of: E2_TASKTYPE_ASYNC when called from _e2_command_run_async() E2_TASKTYPE_SYNC when called from _e2_command_run_sync() E2_TASKTYPE_ACTION when called from e2_task_enqueue_task() @param pid the id of the process we're starting (0 if not yet known) @param mode task type enumerator @param command executed command string or NULL for an action @return pointer to command data struct, or NULL if this fn is busy, or 1 on error */ E2_TaskRuntime *e2_task_set_data (glong pid, E2_TaskDataType mode, gchar *command) { static gboolean blocked = FALSE; if (blocked) return NULL; else blocked = TRUE; E2_TaskRuntime *rt = e2_task_find_running_task (pid); if (rt == NULL) { printd (DEBUG, "e2_task_set_data (pid:%d,mode:%d,command:%s)", pid, mode, command); rt = ALLOCATE (E2_TaskRuntime); //FIXME never deallocated CHECKALLOCATEDWARN (rt, ) if (rt == NULL) { printd (WARN, "not enough memory for task history data"); blocked = FALSE; return GINT_TO_POINTER (1); } if (mode == E2_TASKTYPE_ASYNC || mode == E2_TASKTYPE_SYNC) { rt->action = FALSE; #ifndef E2_NEW_COMMAND rt->pid = pid; #endif rt->ex.command.command = g_strdup (command); //E2_VFSTMPOK rt->ex.command.currdir = g_strdup (curr_view->dir); #ifdef E2_COMMANDQ rt->ex.command.othrdir = g_strdup (other_view->dir); # ifdef E2_VFS rt->ex.command.currspace = curr_view->spacedata; rt->ex.command.othrspace = other_view->spacedata; # endif rt->ex.command.names = ; //active pane selected items array rt->ex.command.othernames = ; //inactive pane selected items array // rt->ex.command.range = SET EXTERNALLY; #endif #ifndef E2_NEW_COMMAND if (mode == E2_TASKTYPE_ASYNC) { rt->pidstr = g_strdup_printf ("%ld", rt->pid); // rt->status = E2_TASK_RUNNING; see below } else //(mode == E2_TASKTYPE_SYNC) { //pid will be -ve, a fake assigned by e2, not the os, as we never //get the real one, nor ever get opportunity to kill the sync process rt->pidstr = g_strdup_printf ("s%ld", -pid); } rt->status = E2_TASK_RUNNING; #endif } else if (mode == E2_TASKTYPE_ACTION) { rt->action = TRUE; rt->status = E2_TASK_NONE; //prevent immediate Q processing //setup for on-the-fly status changes coz often just rt->ex.action is known rt->ex.action.status = &rt->status; } else { printd (WARN, "tried to register a process with bad mode flag"); DEALLOCATE (E2_TaskRuntime, rt); blocked = FALSE; return GINT_TO_POINTER (1); } //setup for foreground running rt->current_tab = &app.tab; rt->background_tab = curr_tab; pthread_mutex_lock (&task_mutex); app.taskhistory = g_list_append (app.taskhistory, rt); pthread_mutex_unlock (&task_mutex); } else { //found an entry with matching pid printd (DEBUG, "tried to re-register a process"); blocked = FALSE; return GINT_TO_POINTER (1); } blocked = FALSE; return rt; } /** @brief get name(s) of item(s) to process, from @a arg @a arg comprises (notionally at least) one or more concatenated filenames or filepaths, with or without quotes, to be processed. Such string is converted into array form, with source path set from the first item. @param arg utf8 string (action rt data) @param path store for source path (if any) retrieved from 1st item in @a arg @return array of E2_SelectedItemInfo's, or NULL if nothing relevant is available */ static GPtrArray *_e2_task_get_names (gchar *arg, gchar **path) { GPtrArray *names; E2_SelectedItemInfo *seldata; gint len; gchar *s, *name, *local; gchar *item = e2_utils_get_first_arg (arg, FALSE); if (item == NULL) return NULL; //check first argument for a source path s = g_path_get_dirname (item); if (!g_str_equal (s, ".")) { if (g_str_equal (s, G_DIR_SEPARATOR_S)) *path = g_strdup (G_DIR_SEPARATOR_S); else { local = F_FILENAME_TO_LOCALE (s); *path = e2_utils_strcat (local, G_DIR_SEPARATOR_S); F_FREE (local); } } g_free (item); g_free (s); //now get names of all items names = g_ptr_array_new (); s = arg; while (s != NULL) { item = e2_utils_get_first_arg (s, FALSE); if (item == NULL) break; seldata = ALLOCATE (E2_SelectedItemInfo); //CHECKME brackets inside macro CHECKALLOCATEDWARN (seldata, g_ptr_array_free (names, TRUE);g_free (item);return NULL;); name = g_path_get_basename (item); local = F_FILENAME_TO_LOCALE (name); //strip any trailer len = strlen (local); if (*(local+len-1) == G_DIR_SEPARATOR) *(local+len-1) = '\0'; g_strlcpy (seldata->filename, local, sizeof (seldata->filename)); g_ptr_array_add (names, seldata); g_free (name); F_FREE (local); //point after the current argument len = strlen (item); //this might be not quite correct (preceeding whitespace or quotes) s += len; s = e2_utils_find_whitespace (s); g_free (item); } return names; } /** @brief thread function to process tasks-pending queue Data are (in general) not cleaned when a task is finished - so we have a history. The selection names are cleared (lot of storage & not yet used for history purposes) Any completion-report function is called with BGL open/off ! @param data UNUSED ptr to data assigned when thread created @return NULL */ static gpointer _e2_task_processQ (gpointer data) { e2_utils_block_thread_signals (); //block all allowed signals to this thread //this is a workaround for systems that run this thread before the //thread-creation function sets athreadID //but don't adjust athreadID yet pthread_t thisID = pthread_self (); E2_TaskRuntime *rt; E2_ActionTaskData *qed; GList *member; pthread_mutex_lock (&task_mutex); //FIXME move the scan-start position to ignore completed items member = app.taskhistory; pthread_mutex_unlock (&task_mutex); while (member != NULL) { rt = (E2_TaskRuntime *)member->data; qed = &rt->ex.action; if (rt->action && rt->status == E2_TASK_QUEUED) { if (e2_option_bool_get ("task-timeout-checks")) { //create progress monitor thread, providing a mechanism to abort //slow activities // qed->timelimit = time (NULL) + e2_option_int_get ("task-timeout-interval"); // printd (DEBUG,"task period is %d seconds", data->timeout); pthread_attr_t attr; //we don't use glib thread funcs, as we may need to kill threads pthread_attr_init (&attr); pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); if (pthread_create (&mthreadID, &attr, (gpointer) _e2_task_progress_monitor, rt) == 0) { printd (DEBUG,"monitor-thread (ID=%lu) started", mthreadID); rt->dialog = NULL; } else { //FIXME message to user printd (WARN,"monitor-thread-create error!"); mthreadID = 0; } } else mthreadID = 0; rt->pid = (glong) thisID; //all tasks in the Q will get same pid rt->pidstr = g_strdup_printf ("%lu", (gulong) thisID); gboolean (*actionfunc) (E2_ActionTaskData *) = qed->taskfunc; rt->status = E2_TASK_RUNNING; //FIXME enable downstream status change for pauses // printd (DEBUG,"queued action starts"); //do it qed->result = (*actionfunc) (qed); rt->status = E2_TASK_COMPLETED; //promptly signal completion to monitor thread if (mthreadID > 0) { pthread_mutex_lock (&task_mutex); if (rt->dialog != NULL) { gdk_threads_enter (); gtk_widget_destroy (rt->dialog); gdk_threads_leave (); rt->dialog = NULL; //not really necessary } pthread_mutex_unlock (&task_mutex); pthread_cancel (mthreadID); printd (DEBUG,"monitor-thread (ID=%lu) stopped by action-thread", mthreadID); mthreadID = 0; } //report as required (BGL open) if (qed->callback != NULL && qed->callback != e2_task_refresh_lists) { gboolean (*cb) (gboolean) = qed->callback; (*cb) (qed->result); } //setup refresh Q as required if (qed->callback == e2_task_refresh_lists) { e2_task_refresh_lists (qed); } //just 1 cleanup if (qed->names != NULL) e2_fileview_clean_selected (qed->names); } pthread_mutex_lock (&task_mutex); member = member->next; pthread_mutex_unlock (&task_mutex); } pthread_mutex_lock (&task_mutex); if (athreadID == 0) //we're finishing before the thread-starter has returned athreadID = -1; //set quick-finish signal for the starter else athreadID = 0; //just clear it pthread_mutex_unlock (&task_mutex); printd (DEBUG, "task-Q-process-thread finished"); return NULL; } /** @brief timer callback to initiate a queued-task thread with BGL definitely off/open @param rt pointer to data for added task, or NULL when just restarting @return FALSE to stop the timer */ static gboolean _e2_task_run_processQ (E2_TaskRuntime *rt) { pthread_attr_t attr; pthread_attr_init (&attr); pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); pthread_mutex_lock (&task_mutex); gboolean not_running = (athreadID == 0); pthread_mutex_unlock (&task_mutex); if (not_running) { //NOTE in some systems the created thread will run and maybe even finish, //before this func returns !! pthread_t thisID; if (pthread_create (&thisID, &attr, (gpointer) _e2_task_processQ, NULL) == 0) //no data ? { pthread_mutex_lock (&task_mutex); if (athreadID == -1) //the task thread has finished already { athreadID = 0; printd (DEBUG,"Q-process-thread (ID=%lu) finished immediately", (gulong) thisID); } else { athreadID = thisID; printd (DEBUG,"Q-process-thread (ID=%lu) started", (gulong) thisID); } pthread_mutex_unlock (&task_mutex); } else { /* gdk_threads_enter (); e2_output_print_error (_("Cannot create thread to run task"), FALSE); gdk_threads_leave (); */ printd (WARN,"Q-process-thread creation error!"); } } else if (rt != NULL) //adding a task { //task start may be deferred, advise user #ifdef E2_COMMANDQ gchar *s, *p; if (rt->action) { s = rt->ex.action.action->name; p = NULL; } else { s = rt->ex.command.command; p = e2_utils_find_whitespace (s); if (p != NULL) *p = '\0'; } #else gchar *s = rt->ex.action.action->name; #endif gchar *msg = g_strdup_printf (_("%s added to tasks queue"), s); gdk_threads_enter (); e2_output_print (&app.tab, msg, NULL, TRUE, "blue", "bold", NULL); gdk_threads_leave (); g_free (msg); #ifdef E2_COMMANDQ if (p != NULL) *p = ' '; //too bad if it was a tab before #endif //if message not visible, make a noise if (app.window.output_paned_ratio > 0.99) e2_utils_beep (); } return FALSE; } /** @brief thread function to process immediate task Data are (in general) not cleaned when a task is finished - so we have a history. The selection names are cleared (lot of storage & not yet used for history purposes) @param rt ptr to data struct for the task to be performed @return NULL */ static gpointer _e2_task_processnow (E2_TaskRuntime *rt) { e2_utils_block_thread_signals (); //block all allowed signals to this thread rt->pid = (glong) pthread_self (); rt->pidstr = g_strdup_printf ("%lu", (gulong)rt->pid); E2_ActionTaskData *qed = &rt->ex.action; gboolean (*actionfunc) (E2_ActionTaskData *) = qed->taskfunc; rt->status = E2_TASK_RUNNING; // printd (DEBUG,"immediate action starts"); //do it qed->result = (*actionfunc) (qed); rt->status = E2_TASK_COMPLETED; //report as required if (qed->callback != NULL && qed->callback != e2_task_refresh_lists) { gboolean (*cb) (gboolean) = qed->callback; (*cb) (qed->result); } //setup refresh Q as required if (qed->callback == e2_task_refresh_lists) { e2_task_refresh_lists (qed); } //just 1 cleanup if (qed->names != NULL) e2_fileview_clean_selected (qed->names); printd (DEBUG,"immediate-action-thread (ID=%lu) finished", rt->pid); return NULL; } /** @brief timer callback to initiate an immediate-task thread with BGL definitely off/open @param rt pointer to task data struct @return FALSE to stop the timer */ static gboolean _e2_task_run_processnow (E2_TaskRuntime *rt) { //no glib thread funcs here, as we generally need the option to cancel thread(s) pthread_t threadID; pthread_attr_t attr; pthread_attr_init (&attr); pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); if (pthread_create (&threadID, &attr, (gpointer) _e2_task_processnow, rt) == 0) printd (DEBUG,"immediate-action-thread (ID=%lu) started", (gulong) threadID); else { /* gdk_threads_enter (); e2_output_print_error (_("Cannot create thread to run task"), FALSE); gdk_threads_leave (); */ printd (WARN,"action-thread creation error!"); } return FALSE; } /** @brief terminate execution of current or all queued tasks @param all TRUE to terminate pending tasks as well as current @return */ void e2_task_abort (gboolean all) { //shutdown any monitor thread if (mthreadID > 0) { pthread_cancel (mthreadID); mthreadID = 0; } //shutdown any action thread if (athreadID > 0) { pthread_cancel (athreadID); //clear ID after finished checking all tasks } //cleanups gboolean killchildren = !e2_option_bool_get ("command-persist"); E2_TaskRuntime *rt; GList *member; //Q-threads are stopped, no need for mutex for (member = app.taskhistory; member != NULL; member = member->next) { rt = (E2_TaskRuntime *)member->data; if (rt != NULL && (rt->status == E2_TASK_RUNNING || rt->status == E2_TASK_PAUSED)) { if (!rt->action) { if (killchildren) kill (-rt->pid, SIGTERM); //cleanup commands /*else allowing running children to continue is consistent with other FM's, but the child's stdin, stdout, stderr will be disabled FIND A WAY TO REVERT CHILD'S STDIO FD'S TO DEFAULTS 0,1,2 */ } else if (rt->pid != (glong) athreadID) { //action may still be running in an immediate task thread pthread_cancel ((pthread_t) rt->pid); } rt->status = E2_TASK_INCOMPLETE; if (rt->action) { E2_ActionTaskData *qed = &rt->ex.action; qed->result = FALSE; if (qed->callback != NULL && qed->callback != e2_task_refresh_lists) { gboolean (*cb) (gboolean) = qed->callback; (*cb) (FALSE); } if (rt->dialog != NULL) //too-slow dialog still in play gtk_widget_destroy (rt->dialog); //CHECKME gdk mutex state ? //just 1 cleanup e2_fileview_clean_selected (qed->names); } } } athreadID = 0; e2_filelist_reset_refresh (); if (all) { //make sure Q is clear e2_command_clear_pending (NULL, NULL); } else { //restart action thread on next item in Q, if any g_timeout_add_full (G_PRIORITY_HIGH, 0, (GSourceFunc) _e2_task_run_processQ, NULL, NULL); } } /** @brief thread function to block while a task is running or paused @param status ptr to store of monitored status-flag @return NULL */ /*static gpointer _e2_task_watch_status (E2_TaskRuntime *rt) { e2_utils_block_thread_signals (); //block all allowed signals to this thread while (rt->status == E2_TASK_RUNNING || rt->status == E2_TASK_PAUSED) { usleep (66000); //15Hz checks for status-change } return NULL; } */ /** @brief wait for a task to complete, and return its completion status This is for task-functions that must return the actual task status There is no timeout check performed here. @param rt ptr to data for the task to monitor, NULL to use task at end of Q @return TRUE if the task completed successfully */ static gboolean _e2_task_wait (E2_TaskRuntime *rt) { if (rt == NULL) { GList *member; pthread_mutex_lock (&task_mutex); member = g_list_last (app.taskhistory); pthread_mutex_unlock (&task_mutex); if (member != NULL) rt = (E2_TaskRuntime *)member->data; } if (rt == NULL) return FALSE; /* pthread_t wthreadID; if (pthread_create (&wthreadID, NULL, (gpointer) _e2_task_watch_status, rt) == 0) printd (DEBUG,"watch-thread (ID=%lu) started", wthreadID); else { //FIXME warn the user printd (WARN,"watch-thread creation error!"); return FALSE; } pthread_join (wthreadID, NULL); */ //poll for status-change (15Hz to miss other timer callbacks) //FIXME do this with less impact, handle failure while (rt->status == E2_TASK_RUNNING || rt->status == E2_TASK_PAUSED) usleep (66000); gboolean retval; if (rt->status == E2_TASK_COMPLETED) retval = (rt->action) ? rt->ex.action.result : (rt->ex.command.exit == 0); else retval = FALSE; printd (DEBUG,"wait finished, task status is %s", (retval) ? "true" : "false"); return retval; } /** @brief callback for responses from too-slow-dialog @a dialog This approach eliminates gtk_main() (which hates being aborted) from the dialog @param dialog the dialog from which the response was initiated @param response the response enumerator @param rt task-data specified when the callback was connected @return */ static void _e2_task_progress_monitor_slow_response_cb (GtkDialog *dialog, gint response, E2_TaskRuntime *rt) { switch (response) { case GTK_RESPONSE_NO: //abort the operation case E2_RESPONSE_YESTOALL: //no more reminders pthread_mutex_lock (&task_mutex); if (rt->dialog != NULL) //race-management { gtk_widget_destroy (rt->dialog); //== (GTK_WIDGET(dialog) // WAIT_FOR_EVENTS; //make the window repaint work rt->dialog = NULL; } pthread_mutex_unlock (&task_mutex); if (mthreadID > 0) //race-management { pthread_cancel (mthreadID); //if the racy action-thread ends about now, signal to it that it //does not need to abort this thread mthreadID = 0; } if (response == GTK_RESPONSE_NO //not just cancelling reminders && athreadID > 0) //operation still not finished { //shutdown action thread pthread_cancel (athreadID); athreadID = 0; rt->status = E2_TASK_INCOMPLETE; E2_ActionTaskData *qed = &rt->ex.action; qed->result = FALSE; gchar *msg = g_strdup_printf (_("%s operation incomplete - time limit exceeded"), rt->ex.action.action->name); e2_output_print_error (msg, TRUE); //refreshing may be disabled or not, depending on the task //and its embedded dialog(s) //so reset the refresh process after this interruption e2_filelist_reset_refresh (); e2_window_clear_status_message (); if (qed->callback != NULL && qed->callback != e2_task_refresh_lists) { gboolean (*cb) (gboolean) = qed->callback; //conform BGL state to main thread when that's doing a report gdk_threads_leave (); (*cb) (FALSE); gdk_threads_enter (); } //setup refresh Q as required if (qed->callback == e2_task_refresh_lists) { e2_task_refresh_lists (qed); } //just 1 cleanup e2_fileview_clean_selected (qed->names); //restart action thread on next item in Q, if any g_timeout_add_full (G_PRIORITY_HIGH, 0, (GSourceFunc) _e2_task_run_processQ, NULL, NULL); } default: // case GTK_RESPONSE_YES: //keep waiting pthread_mutex_lock (&task_mutex); if (rt->dialog != NULL) //race-management { gtk_widget_hide (rt->dialog); // WAIT_FOR_EVENTS; //make the hide work } pthread_mutex_unlock (&task_mutex); break; } } /** @brief thread function to enforce timeout on processing a queued task Expects valid athreadID for the action thread @param rt ptr to data for the currently-running task @return NULL */ static gpointer _e2_task_progress_monitor (E2_TaskRuntime *rt) { e2_utils_block_thread_signals (); //block all allowed signals to this thread //on some systems this func may be called before the thread-creation //function sets mthreadID, but that should not matter ... rt->dialog = NULL; while (TRUE) //loop until user kills this thread (via dialog) or the //action-thread kills this thread { gchar *msg; // gboolean refreshed = FALSE; gint seconds = e2_option_int_get ("task-timeout-interval"); if (seconds < 1) //should never happen, but ... seconds = 1; //1 Hz check for thread stopped while (seconds > 0) { sleep (1); if (rt->status == E2_TASK_RUNNING) seconds--; } //if the task is still paused, probably a dialog is showing, //and we don't want to interfere with that (BUT potential hang ?!) while (rt->status == E2_TASK_PAUSED) sleep (1); //if we get to here, it's timed out if (athreadID > 0) { printd (DEBUG,"action-thread (ID=%lu) timed out", (gulong) athreadID); if (rt->dialog == NULL) { //once-only, create the dialog msg = g_strdup_printf (_("The current operation (%s)"), rt->ex.action.action->name); rt->dialog = e2_dialog_slow (msg, _("operation"), _e2_task_progress_monitor_slow_response_cb, rt); g_free (msg); } //if dialog was hidden from last iteration, show it again if (rt->dialog != NULL && !GTK_WIDGET_VISIBLE (rt->dialog)) { gdk_threads_enter (); gtk_widget_show (rt->dialog); gtk_window_present (GTK_WINDOW (rt->dialog)); gdk_threads_leave (); } } } return NULL; //should never get to here } /** @brief initialise immediate or queued task If @a pane_data is TRUE, for file.* actions the current-pane selected items are logged only if art->data is NULL or "" If @a pane_data is FALSE, static pointers: names_array, srcdir_local, destdir_local are used instead of defaults @param type code indicating the type of task to be performed @param art pointer to action runtime data struct sent to the action @param from the widget activated to trigger the action, possibly NULL @param taskfunc function to be called to perform the task @param callback function to be called when task completed (may be NULL) @param immediate TRUE to run the task in its own thread, FALSE to Q the task @param pane_data TRUE to create array of items to process @return TRUE if setup was successful */ gboolean e2_task_run_task (E2_TaskType type, E2_ActionRuntime *art, gpointer from, gpointer taskfunc, gpointer callback, gboolean immediate, gboolean pane_data) { GPtrArray *names; gchar *specific_path = NULL; if (pane_data) { if (g_str_has_prefix (art->action->name, _A(5))) { //this is an action potentially applying to selected item(s) if (art->data != NULL && *((gchar *)art->data) != '\0') //use items in argument string names = _e2_task_get_names ((gchar *)art->data, &specific_path); else //no specific data, use selected items names = e2_fileview_get_selected (curr_view); if (names == NULL) return FALSE; } else //e.g. command.mkdir names = NULL; } else //use supplied names array names = names_array; E2_TaskRuntime *rt = NULL; //keep trying this until data set fn not busy //the real pid is set elsewhere, when the task is performed while ((rt = e2_task_set_data (0, E2_TASKTYPE_ACTION, NULL)) == NULL); if (rt == GINT_TO_POINTER (1)) { //FIXME error message for user //FIXME names_aray leak return FALSE; } E2_ActionTaskData *qed = &rt->ex.action; qed->tasktype = type; qed->result = FALSE; //all item-name strings are localized if (pane_data) { qed->currdir = (specific_path == NULL) ? D_FILENAME_TO_LOCALE (curr_view->dir) : specific_path; qed->othrdir = D_FILENAME_TO_LOCALE (other_view->dir); } else { qed->currdir = srcdir_local; qed->othrdir = destdir_local; } #ifdef E2_VFS qed->currspace = curr_view->spacedata; qed->othrspace = other_view->spacedata; #endif qed->names = names; qed->action = art->action; qed->rt_data = (art->data == NULL) ? NULL : g_strdup (art->data); qed->initiator = (GtkWidget *)from; qed->taskfunc = taskfunc; qed->callback = callback; if (immediate) { //process this one independently g_timeout_add_full (G_PRIORITY_HIGH, 0, (GSourceFunc) _e2_task_run_processnow, rt, NULL); } else { //process as part of Q rt->status = E2_TASK_QUEUED; //enable processing by a running thread //Q-thread needs to be started with BGL off g_timeout_add_full (G_PRIORITY_HIGH, 0, (GSourceFunc) _e2_task_run_processQ, rt, NULL); } return TRUE; } /** @brief refresh both filelists This is primarily for post-task filelists update. Both displayed filelists will be refreshed as soon as any in-progress re-list (cd or refresh) is completed @param qed pointer to data struct for the task being processed @return */ void e2_task_refresh_lists (E2_ActionTaskData *qed) { // printd (DEBUG, "initiate refresh of both filelists"); #ifdef E2_FAM gchar *utf; utf = F_FILENAME_FROM_LOCALE (qed->currdir); e2_filelist_request_refresh (utf, FALSE); F_FREE (utf); utf = F_FILENAME_FROM_LOCALE (qed->othrdir); e2_filelist_request_refresh (utf, TRUE); F_FREE (utf); #else e2_filelist_check_dirty (GINT_TO_POINTER (1)); #endif } /** @brief refresh content of both pane views Downstream functions expect gtk's BGL to be closed @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if the request was logged */ gboolean e2_task_refresh (gpointer from, E2_ActionRuntime *art) { #ifdef E2_FAM # ifdef E2_VFSTMP gboolean withother = other_view->spacedata == NULL || other_view->spacedata->monitored; gboolean retval = (curr_view->spacedata == NULL || curr_view->spacedata->monitored) ? e2_filelist_request_refresh (curr_view->dir, !withother) : TRUE; if (withother) retval = e2_filelist_request_refresh (other_view->dir, TRUE) && retval; # else //2 tests, to lessen race due to dir-swap during first call gboolean retval = e2_filelist_request_refresh (curr_view->dir, FALSE); retval = e2_filelist_request_refresh (other_view->dir, TRUE) || retval; # endif return retval; #else e2_filelist_check_dirty (GINT_TO_POINTER (1)); return TRUE; #endif } /** @brief show statusline message about task-in-progress @return */ void e2_task_advise (void) { e2_window_show_status_message (_("File operation in progress")); } /** @brief get temp item name if needed @param localpath localised path of item to check @return pointer to name string, = @a path, or newly-allocated replacement */ gchar *e2_task_tempname (const gchar *localpath) { struct stat dest_sb; E2_ERR_DECLARE #ifdef E2_VFS VPATH data = { localpath, NULL }; if (e2_fs_lstat (&data, &dest_sb E2_ERR_PTR()) && E2_ERR_IS (ENOENT)) #else if (e2_fs_lstat (localpath, &dest_sb E2_ERR_PTR()) && E2_ERR_IS (ENOENT)) #endif { E2_ERR_CLEAR return (gchar*)localpath; } E2_ERR_CLEAR return (e2_utils_get_tempname (localpath)); } /** @brief dialog response callback @param dialog the dialog which generated the response signal @param response the response enumerator @param dialog_result pointer to store for @a response @return */ static void _e2_task_response_cb (GtkDialog *dialog, gint response, gint *dialog_result) { *dialog_result = response; gtk_main_quit (); } /** @brief helper function for checking recursive merge item-count This is a callback for a treewalk function @param local_name path of item reported by the walker, localised string @param statbuf pointer to struct stat with data about @a local_name @param status code from the walker, indicating what type of item is being reported @param count pointer to store for items count @return E2TW_CONTINUE until @a count is > 1, then E2TW_STOP */ static E2_TwResult _e2_task_count_twcb (VPATH *local_name, const struct stat *statbuf, E2_TwStatus status, guint *count) { E2_TwResult result; switch (status) { case E2TW_F: //not directory or link case E2TW_SL: //symbolic link case E2TW_SLN: //symbolic link naming non-existing file (*count)++; result = (*count > 1) ? E2TW_STOP : E2TW_CONTINUE ; break; default: result = E2TW_CONTINUE; break; } return result; } /** @brief check whether a recursive merge task will process multiple items This counts only non-dirs, i.e ignores dirs that are missing from the destination tree Assumes BGL is open @param sdir_local path of dir containing items to be processed, localised string @param names_local array of localised item-names @return TRUE if multiple items will be processed */ static gboolean _e2_task_count_recursive (VPATH *sdir_local, GPtrArray *names_local) { guint count, items = 0; #ifdef E2_VFS VPATH sdata; sdata.spacedata = sdir_local->spacedata; #endif GString *src = g_string_sized_new (1024); E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names_local->pdata; for (count=0; count < names_local->len; count++, iterator++) { //if (! #ifdef E2_VFS g_string_printf (src, "%s%s", VPSTR(sdir_local), (*iterator)->filename); sdata.localpath = src->str; e2_fs_tw (&sdata, _e2_task_count_twcb, &items, -1, #else g_string_printf (src, "%s%s", sdir_local, (*iterator)->filename); e2_fs_tw (src->str, _e2_task_count_twcb, &items, -1, #endif E2TW_PHYS | E2TW_NODIR E2_ERR_NONE()); //) //{ //FIXME handle error //} if (items > 1) break; } return (items > 1); } /** @brief check whether a move|copy task would put a dir into any of its own descendants @param srcpath local path of source item @param destpath local path of source item Links can't be checked as the name doesn't exist yet, but its parent does @return TRUE if the operation is NOT ok */ static gboolean _e2_task_circular ( #ifdef E2_VFS VPATH *sdata, VPATH *ddata #else const gchar *srcpath, const gchar *destpath #endif ) { #ifdef E2_VFS if (sdata->space_data != ddata->space_data || !e2_fs_is_dir3 (sdata E2_ERR_NONE())) return FALSE; #else if (!e2_fs_is_dir3 (srcpath E2_ERR_NONE())) return FALSE; #endif //get real paths of dirs #ifdef E2_VFSTMP FIXME #else gchar *real_src = g_strdup (srcpath); e2_fs_walk_link (&real_src); if (!g_str_has_suffix (real_src, G_DIR_SEPARATOR_S)) { gchar *freeme = real_src; real_src = g_strconcat (real_src, G_DIR_SEPARATOR_S, NULL); g_free (freeme); } gchar *real_dest = g_strdup (destpath); e2_fs_walk_link (&real_dest); #endif gboolean retval = (strncmp (real_src, real_dest, strlen (real_src)) == 0); if (retval) e2_output_print_error (_("Operation not permitted - Circular directories"), FALSE); g_free (real_src); g_free (real_dest); return retval; } /************************/ /**** task functions ****/ /************************/ /** @brief copy selected items from active to inactive pane Overwrite checking is performed, if that option is enabled The actual operation is performed by a separate back-end function @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if task completed successfully, else FALSE */ gboolean e2_task_copy (gpointer from, E2_ActionRuntime *art) { // printd (DEBUG, "task: copy"); return (e2_task_enqueue_task (E2_TASK_COPY, art, from, _e2_task_copyQ, e2_task_refresh_lists)); } static gboolean _e2_task_copyQ (E2_ActionTaskData *qed) { // printd (DEBUG, "task: copyQ"); if (g_str_equal (qed->currdir, qed->othrdir)) { //display some message ?? return FALSE; } GPtrArray *names = qed->names; gchar *curr_local = qed->currdir; gchar *other_local = qed->othrdir; #ifdef E2_VFS VPATH sdata = { NULL, qed->currspace }; VPATH ddata = { NULL, qed->othrspace }; #endif E2_FileTaskMode mode = GPOINTER_TO_INT (qed->action->data); GString *src = g_string_sized_new (1024); GString *dest = g_string_sized_new (1024); guint count; E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata; gboolean check = e2_option_bool_get ("confirm-overwrite") #ifdef E2_VFS && qed->currspace == qed->othrspace #endif ; //setup for tailoring over-write dialog gboolean multisrc; if (!check) multisrc = FALSE; else { #ifdef E2_VFS sdata.localpath = curr_local; #endif multisrc = (mode & E2_FTM_MERGE) ? #ifdef E2_VFS _e2_task_count_recursive (&sdata, names) #else _e2_task_count_recursive (curr_local, names) #endif : names->len > 1; } OW_ButtonFlags extras = (multisrc) ? BOTHALL : NONE; //no window desensitize ... e2_task_advise (); #ifdef E2_REFRESH_DEBUG printd (DEBUG, "disable refresh, copy task"); #endif e2_filelist_disable_refresh (); //prevent pauses in the copy process for (count=0; count < names->len; count++, iterator++) { //".." entries filtered when names compiled; g_string_printf (src, "%s%s", curr_local, (*iterator)->filename); //separator comes with dir g_string_printf (dest, "%s%s", other_local, (*iterator)->filename); #ifdef E2_VFS sdata.localpath = src->str; ddata.localpath = dest->str; if (_e2_task_circular (&sdata, &ddata)) continue; if ((!(mode & E2_FTM_MERGE) //normal copy || !e2_fs_is_dir3 (&sdata E2_ERR_NONE()) || !e2_fs_is_dir3 (&ddata E2_ERR_NONE()) //not merging 2 dirs ) && check && e2_fs_access2 (&ddata E2_ERR_NONE()) == 0 ) #else if (_e2_task_circular (src->str, dest->str)) continue; if ((!(mode & E2_FTM_MERGE) //normal copy || !e2_fs_is_dir3 (src->str E2_ERR_NONE()) || !e2_fs_is_dir3 (dest->str E2_ERR_NONE()) //not merging 2 dirs ) && check && e2_fs_access2 (dest->str E2_ERR_NONE()) == 0 ) #endif { #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, copy dialog"); #endif e2_filelist_enable_refresh (); //allow updates while we wait gdk_threads_enter (); *qed->status = E2_TASK_PAUSED; DialogButtons result = e2_dialog_ow_check (src->str, dest->str, extras); *qed->status = E2_TASK_RUNNING; gdk_threads_leave (); #ifdef E2_REFRESH_DEBUG printd (DEBUG, "disable refresh, copy dialog"); #endif e2_filelist_disable_refresh (); switch (result) { case YES_TO_ALL: check = FALSE; case OK: if (mode & E2_FTM_MERGE) { //pass the check flag to backend, in case there is no //confirmation at top level if (check) mode |= E2_FTM_CHECK; else mode &= ~E2_FTM_CHECK; } e2_task_backend_copy #ifdef E2_VFS (&sdata, &ddata, mode); #else (src->str, dest->str, mode); #endif //update local copy of check flag if ((mode & E2_FTM_MERGE) && check && !(mode & E2_FTM_CHECK)) check = FALSE; case CANCEL: break; default: result = NO_TO_ALL; break; } if (result == NO_TO_ALL) break; } else //file doesn't exist, or don't care { if (mode & E2_FTM_MERGE) { //pass the check flag to backend, in case there is no //confirmation at top level if (check) mode |= E2_FTM_CHECK; else mode &= ~E2_FTM_CHECK; } e2_task_backend_copy #ifdef E2_VFS (&sdata, &ddata, mode); #else (src->str, dest->str, mode); #endif if ((mode & E2_FTM_MERGE) && check && !(mode & E2_FTM_CHECK)) check = FALSE; } } g_string_free (src, TRUE); g_string_free (dest, TRUE); e2_window_clear_status_message (); #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, copy task"); #endif e2_filelist_enable_refresh (); // printd (DEBUG, "task: copyQ FINISHED"); return TRUE; } /** @brief copy selected items from active to inactive pane, with renaming Overwrite checking is performed, if that option is enabled The actual operation is performed by a separate back-end function @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if task completed successfully, else FALSE */ gboolean e2_task_copy_as (gpointer from, E2_ActionRuntime *art) { return (e2_task_enqueue_task (E2_TASK_COPYAS, art, from, _e2_task_copy_asQ, e2_task_refresh_lists)); } static gboolean _e2_task_copy_asQ (E2_ActionTaskData *qed) { GPtrArray *names = qed->names; gchar *curr_local = qed->currdir; gchar *other_local = qed->othrdir; #ifdef E2_VFS VPATH sdata = { NULL, qed->currspace }; VPATH ddata = { NULL, qed->othrspace }; #endif GString *prompt = g_string_sized_new (NAME_MAX+64); GString *src = g_string_sized_new (1024); GString *dest = g_string_sized_new (1024); gchar *converted, *new_name, *public; gboolean check = e2_option_bool_get ("confirm-overwrite") #ifdef E2_VFS && qed->currspace == qed->othrspace #endif ; guint count; E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata; //setup for tailoring over-write dialog gboolean multisrc = (check) ? names->len > 1 : FALSE; // setup for showing stop button in rename dialog OW_ButtonFlags extras = (multisrc)? NOALL : NONE; #ifdef E2_REFRESH_DEBUG printd (DEBUG, "disable refresh, copy as task"); #endif e2_filelist_disable_refresh (); e2_task_advise (); for (count=0; count < names->len; count++, iterator++) { //".." entries filtered when names compiled converted = F_FILENAME_FROM_LOCALE ((*iterator)->filename); public = g_markup_escape_text (converted, -1); g_string_printf (prompt, "%s: %s", _("Enter new name for"), public); g_free (public); DialogButtons result = 0; //FIXME = SPEED UP DIALOG CREATION OUTSIDE LOOP ? #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, copy as dialog"); #endif e2_filelist_enable_refresh (); //allow updates while we wait gdk_threads_enter (); *qed->status = E2_TASK_PAUSED; DialogButtons result2 = e2_dialog_line_input (_("copy"), prompt->str, converted, extras, FALSE, &new_name); *qed->status = E2_TASK_RUNNING; gdk_threads_leave (); #ifdef E2_REFRESH_DEBUG printd (DEBUG, "disable refresh, copy as dialog"); #endif F_FREE (converted); e2_filelist_disable_refresh (); if (result2 == OK) { g_string_printf (src, "%s%s", curr_local, (*iterator)->filename); //separator comes with dir converted = F_FILENAME_TO_LOCALE (new_name); g_string_printf (dest, "%s%s", other_local, converted); g_free (new_name); F_FREE (converted); if (strcmp(src->str, dest->str) == 0) continue; #ifdef E2_VFS sdata.localpath = src->str; ddata.localpath = dest->str; if (_e2_task_circular (&sdata, &ddata)) continue; if (check && e2_fs_access2 (&ddata E2_ERR_NONE()) == 0) #else if (_e2_task_circular (src->str, dest->str)) continue; if (check && e2_fs_access2 (dest->str E2_ERR_NONE()) == 0) #endif { #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, copy as dialog"); #endif e2_filelist_enable_refresh (); //allow updates while we wait gdk_threads_enter (); *qed->status = E2_TASK_PAUSED; result = e2_dialog_ow_check (NULL, dest->str, extras); *qed->status = E2_TASK_RUNNING; gdk_threads_leave (); e2_filelist_disable_refresh (); if (result == OK) e2_task_backend_copy #ifdef E2_VFS (&sdata, &ddata, E2_FTM_NORMAL); #else (src->str, dest->str, E2_FTM_NORMAL); #endif /* else if (result == YES_TO_ALL) { do something smart about multiple-renames } */ else if (result == NO_TO_ALL) break; } else e2_task_backend_copy #ifdef E2_VFS (&sdata, &ddata, E2_FTM_NORMAL); #else (src->str, dest->str, E2_FTM_NORMAL); #endif } else if (result2 == NO_TO_ALL) break; } g_string_free (prompt,TRUE); g_string_free (src,TRUE); g_string_free (dest,TRUE); e2_window_clear_status_message (); #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, copy as task"); #endif e2_filelist_enable_refresh (); return TRUE; } /** @brief move selected items from active pane to trash location THIS IS OUT-OF-DATE Selected item(s) are moved to the last-used trash dir, which will be: if it exists, folder named '.Trash' in current dir, or if it exists, ~/.Trash, or if it exists, config dir/Trash, or not moved, and error msg is printed No overwrite checking is performed. The actual operation is performed by a separate back-end function @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if task completed successfully, else FALSE */ gboolean e2_task_trashit (gpointer from, E2_ActionRuntime *art) { return (e2_task_enqueue_task (E2_TASK_TRASH, art, from, _e2_task_trashitQ, e2_task_refresh_lists)); } static gboolean _e2_task_trashitQ (E2_ActionTaskData *qed) { // printd (DEBUG, "task: trash"); const gchar *trashpath = e2_utils_get_trash_path (); if (trashpath == NULL //do nothing if we're in the local trash place already || (g_str_equal (trashpath, curr_view->dir) #ifdef E2_VFS && curr_view->spacedata == NULL #endif )) { //display some message return FALSE; } else return (_e2_task_move (qed, (gchar *) trashpath)); } /** @brief move selected items from active to inactive pane Overwrite checking is performed, if that option is enabled The actual operation is performed by a separate back-end function @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if task completed successfully, else FALSE */ gboolean e2_task_move (gpointer from, E2_ActionRuntime *art) { return (e2_task_enqueue_task (E2_TASK_MOVE, art, from, _e2_task_moveQ, e2_task_refresh_lists)); } static gboolean _e2_task_moveQ (E2_ActionTaskData *qed) { // printd (DEBUG, "task: move"); if (g_str_equal (qed->currdir, qed->othrdir)) { //display some message return FALSE; } else return (_e2_task_move (qed, NULL)); } /** @brief move selected items @param from the button, menu item etc which was activated @param art action runtime data @param trashpath string with path to trash dir, or NULL if moving to inactive pane @return TRUE if task completed successfully, else FALSE */ static gboolean _e2_task_move (E2_ActionTaskData *qed, gchar *trashpath) { GPtrArray *names = qed->names; GString *src = g_string_sized_new (1024); GString *dest = g_string_sized_new (1024); gboolean trash = (trashpath != NULL); //curr_view->dir, other_view->dir are utf-8 gchar *curr_local = qed->currdir; gchar *other_local = (trash) ? F_FILENAME_TO_LOCALE (trashpath) : //CHECKME trailer qed->othrdir; #ifdef E2_VFS VPATH sdata = { NULL, qed->currspace }; VPATH ddata; ddata.spacedata = (trash) ? NULL : qed->othrspace; //local trash only #endif guint count; E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata; gboolean check = (trash) ? FALSE : e2_option_bool_get ("confirm-overwrite") #ifdef E2_VFS && qed->currspace == qed->othrspace #endif ; //setup for tailoring over-write dialog gboolean multisrc = (check) ? names->len > 1 : FALSE; OW_ButtonFlags extras = (multisrc) ? BOTHALL : NONE; e2_task_advise (); e2_filelist_disable_refresh (); //avoid pauses in the move process for (count=0; count < names->len; count++, iterator++) { //".." entries filtered when names compiled g_string_printf (src, "%s%s", curr_local, (*iterator)->filename); //separator comes with dir g_string_printf (dest, "%s%s", other_local, (*iterator)->filename); #ifdef E2_VFS sdata.localpath = src->str; ddata.localpath = dest->str; if (_e2_task_circular (&sdata, &ddata)) continue; if (trash && e2_fs_access2 (&ddata E2_ERR_NONE()) == 0) #else if (_e2_task_circular (src->str, dest->str)) continue; if (trash && e2_fs_access2 (dest->str E2_ERR_NONE()) == 0) #endif { //trash without destroying existing file there gchar *tlocal = e2_utils_get_tempname (dest->str); g_string_assign (dest, tlocal); g_free (tlocal); } #ifdef E2_VFS ddata.localpath = dest->str; if (check && e2_fs_access2 (&ddata E2_ERR_NONE()) == 0) #else if (check && e2_fs_access2 (dest->str E2_ERR_NONE()) == 0) #endif { e2_filelist_enable_refresh (); //allow updates while we wait gdk_threads_enter (); *qed->status = E2_TASK_PAUSED; DialogButtons result = e2_dialog_ow_check (src->str, dest->str, extras); *qed->status = E2_TASK_RUNNING; gdk_threads_leave (); e2_filelist_disable_refresh (); switch (result) { case YES_TO_ALL: check = FALSE; case OK: e2_task_backend_move #ifdef E2_VFS (&sdata, &ddata); #else (src->str, dest->str); #endif /* full compliance with fdo trash spec requires adding to $trash/info directory an “information file” for every file and directory in $trash/files. This file MUST have exactly the same name as the file or directory in $trash/files, plus the extension “.trashinfo” The format of this file is like: [Trash Info] Path=foo/bar/meow.bow-wow DeletionDate=20040831T22:32:08 The path string is localised (as used by the filesystem, with characters escaped as in URLs (as defined by RFC 2396, section 2). The date string is the date and time when the item was trashed, in YYYY-MM-DDThh:mm:ss format (see RFC 3339). The time zone is the user's (or filesystem's) local time. */ case CANCEL: break; default: result = NO_TO_ALL; break; } if (result == NO_TO_ALL) break; } else //file doesn't exist, or don't care { e2_task_backend_move #ifdef E2_VFS (&sdata, &ddata); #else (src->str, dest->str); #endif } } g_string_free (src, TRUE); g_string_free (dest, TRUE); e2_window_clear_status_message (); e2_filelist_enable_refresh (); return TRUE; } /** @brief move selected items from active to inactive pane, with renaming Overwrite checking is performed, if that option is enabled The actual operation is performed by a separate back-end function @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if task completed successfully, else FALSE */ gboolean e2_task_move_as (gpointer from, E2_ActionRuntime *art) { return (e2_task_enqueue_task (E2_TASK_MOVEAS, art, from, _e2_task_move_asQ, e2_task_refresh_lists)); } static gboolean _e2_task_move_asQ (E2_ActionTaskData *qed) { GPtrArray *names = qed->names; gchar *curr_local = qed->currdir; gchar *other_local = qed->othrdir; #ifdef E2_VFS VPATH sdata = { NULL, qed->currspace }; VPATH ddata = { NULL, qed->othrspace }; #endif GString *prompt = g_string_sized_new (NAME_MAX+64); GString *src = g_string_sized_new (1024); GString *dest = g_string_sized_new (1024); gchar *converted, *new_name, *public; gboolean check = e2_option_bool_get ("confirm-overwrite") #ifdef E2_VFS && qed->currspace == qed->othrspace #endif ; guint count; E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata; gboolean multisrc = (check) ? names->len > 1 : FALSE; OW_ButtonFlags extras = (multisrc)? NOALL : NONE; e2_task_advise (); e2_filelist_disable_refresh (); for (count=0; count < names->len; count++, iterator++) { //".." entries filtered when names compiled converted = F_FILENAME_FROM_LOCALE ((*iterator)->filename); public = g_markup_escape_text (converted, -1); g_string_printf (prompt, "%s: %s", _("Enter new name for"), public); g_free (public); DialogButtons result = 0; e2_filelist_enable_refresh (); //allow updates while we wait gdk_threads_enter (); *qed->status = E2_TASK_PAUSED; DialogButtons result2 = e2_dialog_line_input (_("move"), prompt->str, converted, extras, FALSE, &new_name); *qed->status = E2_TASK_RUNNING; gdk_threads_leave (); F_FREE (converted); e2_filelist_disable_refresh (); if (result2 == OK) { g_string_printf (src, "%s%s", curr_local, (*iterator)->filename); //separator comes with dir converted = F_FILENAME_TO_LOCALE (new_name); g_string_printf (dest, "%s%s", other_local, converted); g_free (new_name); F_FREE (converted); if (strcmp (src->str, dest->str) == 0) continue; #ifdef E2_VFS sdata.localpath = src->str; ddata.localpath = dest->str; if (_e2_task_circular (&sdata, &ddata)) continue; if (check && e2_fs_access2 (&ddata E2_ERR_NONE()) == 0) #else if (_e2_task_circular (src->str, dest->str)) continue; if (check && e2_fs_access2 (dest->str E2_ERR_NONE()) == 0) #endif { e2_filelist_enable_refresh (); //allow updates while we wait gdk_threads_enter (); *qed->status = E2_TASK_PAUSED; result = e2_dialog_ow_check (NULL, dest->str, extras); *qed->status = E2_TASK_RUNNING; gdk_threads_leave (); e2_filelist_enable_refresh (); if (result == OK) { e2_task_backend_move #ifdef E2_VFS (&sdata, &ddata); #else (src->str, dest->str); #endif } /* else if (result == YES_TO_ALL) { do something smart about multiple-renames ?? } */ else if (result == NO_TO_ALL) break; } else { e2_task_backend_move #ifdef E2_VFS (&sdata, &ddata); #else (src->str, dest->str); #endif } } else if (result2 == NO_TO_ALL) { // result = NO_TO_ALL; break; } } g_string_free (prompt,TRUE); g_string_free (src,TRUE); g_string_free (dest,TRUE); e2_window_clear_status_message (); e2_filelist_enable_refresh (); return TRUE; } /** @brief symlink selected items in active pane to the inactive pane Overwrite checking is performed, if that option is enabled The actual operation is performed by a separate back-end function @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if task completed successfully, else FALSE */ gboolean e2_task_symlink (gpointer from, E2_ActionRuntime *art) { return (e2_task_enqueue_task (E2_TASK_LINK, art, from, _e2_task_symlinkQ, e2_task_refresh_lists)); } static gboolean _e2_task_symlinkQ (E2_ActionTaskData *qed) { if (g_str_equal (qed->currdir, qed->othrdir)) { //display some message return FALSE; } GPtrArray *names = qed->names; gchar *curr_local = qed->currdir; gchar *other_local = qed->othrdir; #ifdef E2_VFS VPATH sdata = { NULL, qed->currspace }; VPATH ddata = { NULL, qed->othrspace }; #endif GString *src = g_string_sized_new (1024); GString *dest = g_string_sized_new (1024); guint count; E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata; gboolean check = e2_option_bool_get ("confirm-overwrite") #ifdef E2_VFS && qed->currspace == qed->othrspace #endif ; //setup for tailoring over-write dialog gboolean multisrc = (check) ? names->len > 1 : FALSE; OW_ButtonFlags extras = (multisrc) ? BOTHALL : NONE; //no window desensitize ... e2_task_advise (); #ifdef E2_REFRESH_DEBUG printd (DEBUG, "disable refresh, copy task"); #endif e2_filelist_disable_refresh (); //prevent pauses in the copy process for (count=0; count < names->len; count++, iterator++) { //".." entries filtered when names compiled; g_string_printf (src, "%s%s", curr_local, (*iterator)->filename); //separator comes with dir g_string_printf (dest, "%s%s", other_local, (*iterator)->filename); #ifdef E2_VFS sdata.locapath = src->str; ddata.localpath = dest->str; // if (_e2_task_circular (&sdata, &ddata)) FIXME parent of name // continue; if (check && e2_fs_access2 (&ddata E2_ERR_NONE()) == 0) #else // if (_e2_task_circular (src->str, dest->str)) // continue; if (check && e2_fs_access2 (dest->str E2_ERR_NONE()) == 0) #endif { e2_filelist_enable_refresh (); //allow updates while we wait gdk_threads_enter (); *qed->status = E2_TASK_PAUSED; DialogButtons result = e2_dialog_ow_check (src->str, dest->str, extras); *qed->status = E2_TASK_RUNNING; gdk_threads_leave (); e2_filelist_disable_refresh (); switch (result) { case YES_TO_ALL: check = FALSE; case OK: e2_task_backend_link #ifdef E2_VFS (&sdata, &ddata); #else (src->str, dest->str); #endif case CANCEL: break; default: result = NO_TO_ALL; break; } if (result == NO_TO_ALL) break; } else //file doesn't exist, or don't care { e2_task_backend_link #ifdef E2_VFS (&sdata, &ddata); #else (src->str, dest->str); #endif } } g_string_free (src, TRUE); g_string_free (dest, TRUE); e2_window_clear_status_message (); e2_filelist_enable_refresh (); return TRUE; } /** @brief symlink selected items in active pane to the inactive pane, with renaming Overwrite checking is performed, if that option is enabled The actual operation is performed by a separate back-end function @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if task completed successfully, else FALSE */ gboolean e2_task_symlink_as (gpointer from, E2_ActionRuntime *art) { return (e2_task_enqueue_task (E2_TASK_LINKAS, art, from, _e2_task_symlink_asQ, e2_task_refresh_lists)); } static gboolean _e2_task_symlink_asQ (E2_ActionTaskData *qed) { GPtrArray *names = qed->names; gchar *curr_local = qed->currdir; gchar *other_local = qed->othrdir; #ifdef E2_VFS VPATH sdata = { NULL, qed->currspace }; VPATH ddata = { NULL, qed->othrspace }; #endif GString *prompt = g_string_sized_new (NAME_MAX+64); GString *src = g_string_sized_new (1024); GString *dest = g_string_sized_new (1024); gchar *converted, *new_name, *public; gboolean check = e2_option_bool_get ("confirm-overwrite") #ifdef E2_VFS && qed->currspace == qed->othrspace #endif ; guint count; E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata; //setup for tailoring over-write dialog gboolean multisrc = (check) ? names->len > 1 : FALSE; // setup for showing stop button in rename dialog OW_ButtonFlags extras = (multisrc)? NOALL : NONE; e2_filelist_disable_refresh (); e2_task_advise (); for (count=0; count < names->len; count++, iterator++) { //".." entries filtered when names compiled converted = F_FILENAME_FROM_LOCALE ((*iterator)->filename); public = g_markup_escape_text (converted, -1); g_string_printf (prompt, "%s: %s", _("Enter new name for"), public); g_free (public); DialogButtons result = 0; e2_filelist_enable_refresh (); //allow updates while we wait gdk_threads_enter (); *qed->status = E2_TASK_PAUSED; DialogButtons result2 = e2_dialog_line_input (_("link"), prompt->str, converted, extras, FALSE, &new_name); *qed->status = E2_TASK_RUNNING; gdk_threads_leave (); F_FREE (converted); e2_filelist_disable_refresh (); if (result2 == OK) { g_string_printf (src, "%s%s", curr_local, (*iterator)->filename); //separator comes with dir converted = F_FILENAME_TO_LOCALE (new_name); g_string_printf (dest, "%s%s", other_local, converted); g_free (new_name); F_FREE (converted); if (strcmp(src->str, dest->str) == 0) continue; #ifdef E2_VFS sdata.localpath = src->str; ddata.localpath = dest->str; // if (_e2_task_circular (&sdata, &ddata)) FIXME parent of name // continue; if (check && e2_fs_access2 (&ddata E2_ERR_NONE()) == 0) #else // if (_e2_task_circular (src->str, dest->str)) // continue; if (check && e2_fs_access2 (dest->str E2_ERR_NONE()) == 0) #endif { #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, copy as dialog"); #endif e2_filelist_enable_refresh (); //allow updates while we wait gdk_threads_enter (); *qed->status = E2_TASK_PAUSED; result = e2_dialog_ow_check (NULL, dest->str, extras); *qed->status = E2_TASK_RUNNING; gdk_threads_leave (); e2_filelist_disable_refresh (); if (result == OK) { e2_task_backend_link #ifdef E2_VFS (&sdata, &ddata); #else (src->str, dest->str); #endif } /* else if (result == YES_TO_ALL) { do something smart about multiple-renames } */ else if (result == NO_TO_ALL) break; } else { e2_task_backend_link #ifdef E2_VFS (&sdata, &ddata); #else (src->str, dest->str); #endif } } else if (result2 == NO_TO_ALL) { break; } } g_string_free (prompt,TRUE); g_string_free (src,TRUE); g_string_free (dest,TRUE); e2_window_clear_status_message (); e2_filelist_enable_refresh (); return TRUE; } /** @brief delete selected items in the active pane Confirmation is sought, if that option is enabled The actual operation is performed by a separate back-end function @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if task completed successfully, else FALSE */ gboolean e2_task_delete (gpointer from, E2_ActionRuntime *art) { return (e2_task_enqueue_task (E2_TASK_DELETE, art, from, _e2_task_deleteQ, e2_task_refresh_lists)); } static gboolean _e2_task_deleteQ (E2_ActionTaskData *qed) { printd (DEBUG, "task: delete"); GPtrArray *names = qed->names; gchar *curr_local = qed->currdir; #ifdef E2_VFS VPATH sdata = { NULL, qed->currspace }; #endif GString *prompt = g_string_sized_new (NAME_MAX + 64); GString *src = g_string_sized_new (NAME_MAX); gchar *utf, *public; guint count; E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata; gboolean check = e2_option_bool_get ("confirm-delete"); gboolean multisrc = (check) ? names->len > 1 : FALSE; gint result = 0; e2_filelist_disable_refresh (); e2_task_advise (); for (count=0; count < names->len; count++, iterator++) { //".." entries filtered when names compiled g_string_printf (src, "%s%s", curr_local, (*iterator)->filename); if (check) { utf = F_DISPLAYNAME_FROM_LOCALE ((*iterator)->filename); public = g_markup_escape_text (utf, -1); g_string_printf (prompt, _("Are you sure you want to delete %s?"), public); F_FREE (utf); g_free (public); GtkWidget *dialog = e2_dialog_create (GTK_STOCK_DIALOG_QUESTION, prompt->str, _("confirm"), _e2_task_response_cb, &result); if (multisrc) e2_dialog_set_negative_response (dialog, E2_RESPONSE_NOTOALL); else { E2_BUTTON_NOTOALL.showflags |= E2_BTN_GREY; E2_BUTTON_YESTOALL.showflags |= E2_BTN_GREY; } E2_BUTTON_CANCEL.showflags |= E2_BTN_DEFAULT; E2_BUTTON_OK.showflags &= ~E2_BTN_DEFAULT; #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, delete dialog"); #endif e2_filelist_enable_refresh (); //allow updates while we wait gdk_threads_enter (); *qed->status = E2_TASK_PAUSED; e2_dialog_show (dialog, app.main_window, 0, &E2_BUTTON_NOTOALL, &E2_BUTTON_YESTOALL, &E2_BUTTON_CANCEL, &E2_BUTTON_OK, NULL); gtk_main (); gtk_widget_destroy (dialog); *qed->status = E2_TASK_RUNNING; gdk_threads_leave (); #ifdef E2_REFRESH_DEBUG printd (DEBUG, "disable refresh, delete dialog"); #endif e2_filelist_disable_refresh (); switch (result) { case E2_RESPONSE_YESTOALL: check = FALSE; case GTK_RESPONSE_OK: #ifdef E2_VFS sdata.localpath = src->str; e2_task_backend_delete (&sdata); #else e2_task_backend_delete (src->str); #endif case GTK_RESPONSE_CANCEL: break; // case E2_RESPONSE_NOTOALL: default: result = E2_RESPONSE_NOTOALL; break; } } else //no checking { #ifdef E2_VFS sdata.localpath = src->str; e2_task_backend_delete (&sdata); #else e2_task_backend_delete (src->str); #endif } if (result == E2_RESPONSE_NOTOALL) break; } g_string_free (prompt, TRUE); g_string_free (src, TRUE); e2_window_clear_status_message (); e2_filelist_enable_refresh (); return TRUE; } /** @brief delete all items from trash location There are no warnings for any deletion, here Assumes BGL is closed on arrival @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if task completed successfully, else FALSE */ gboolean e2_task_trashempty (gpointer from, E2_ActionRuntime *art) { // printd (DEBUG, "task: empty trash"); const gchar *trashpath = e2_utils_get_trash_path (); //has trailer if (trashpath != NULL) { GList *entries, *member; gchar *dlocal, *localpath, *itemname; e2_filelist_disable_refresh (); dlocal = F_FILENAME_TO_LOCALE ((gchar *)trashpath); #ifdef E2_VFS VPATH ddata; ddata.spacedata = NULL; //only local spaces for trash #endif #ifdef E2_VFS ddata.localpath = dlocal; gdk_threads_leave (); entries = (GList *)e2_fs_dir_foreach (&ddata, #else gdk_threads_leave (); entries = (GList *)e2_fs_dir_foreach (dlocal, #endif E2_DIRWATCH_NO, //trash is local, fast read NULL, NULL, NULL E2_ERR_NONE()); if (E2DREAD_FAILED (entries)) { gdk_threads_enter (); //FIXME warn user about error e2_filelist_enable_refresh (); F_FREE (dlocal); return FALSE; } for (member = entries; member != NULL; member=member->next) { itemname = (gchar *)member->data; if (!g_str_equal (itemname, "..")) { localpath = e2_utils_strcat (dlocal, itemname); #ifdef E2_VFS ddata.localpath = localpath; #endif gdk_threads_leave (); //downstream error messages manage BGL locally #ifdef E2_VFS e2_task_backend_delete (&ddata); #else e2_task_backend_delete (localpath); #endif gdk_threads_enter (); g_free (localpath); } g_free (itemname); } if (entries != NULL) g_list_free (entries); F_FREE (dlocal); //there may also be a fdo-compliant data dir to clean localpath = g_strrstr (trashpath, "/files"); //no need for utf8 scan if (localpath != NULL) //should always be TRUE { gsize len = localpath - trashpath + sizeof(gchar); //include the separator gchar *dup = g_strndup (trashpath, len); gchar *sp = g_strconcat (dup, "info"G_DIR_SEPARATOR_S, NULL); //no translation needed dlocal = F_FILENAME_TO_LOCALE (sp); #ifdef E2_VFS ddata.localpath = dlocal; if (!e2_fs_access (&ddata, W_OK E2_ERR_NONE())) #else if (!e2_fs_access (dlocal, W_OK E2_ERR_NONE())) #endif { #ifdef E2_VFS entries = (GList *)e2_fs_dir_foreach (&ddata, #else entries = (GList *)e2_fs_dir_foreach (dlocal, #endif E2_DIRWATCH_NO, //trash is local, fast read NULL, NULL, NULL E2_ERR_NONE()); if (!E2DREAD_FAILED (entries)) { for (member = entries; member != NULL; member=member->next) { itemname = (gchar *)member->data; if (!g_str_equal (itemname, "..")) { localpath = e2_utils_strcat (dlocal, itemname); #ifdef E2_VFS ddata.localpath = localpath; #endif #ifdef E2_VFS e2_task_backend_delete (&ddata); #else e2_task_backend_delete (localpath); //self-manages BGL #endif g_free (localpath); } g_free (itemname); } if (entries != NULL) g_list_free (entries); } } g_free (dup); g_free (sp); F_FREE (dlocal); } gdk_threads_enter (); e2_filelist_enable_refresh (); return TRUE; } //FIXME warn user about error return FALSE; } /** @brief rename selected items in active pane to the inactive pane Overwrite checking is performed, if that option is enabled The actual operation is performed by a separate back-end function @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if task completed successfully, else FALSE */ gboolean e2_task_rename (gpointer from, E2_ActionRuntime *art) { return (e2_task_enqueue_task (E2_TASK_RENAME, art, from, _e2_task_renameQ, e2_task_refresh_lists)); } static gboolean _e2_task_renameQ (E2_ActionTaskData *qed) { GPtrArray *names = qed->names; gchar *curr_local = qed->currdir; #ifdef E2_VFS VPATH sdata = { NULL, qed->currspace }; VPATH ddata = { NULL, qed->currspace }; VPATH tdata = { NULL, qed->currspace }; #endif GString *prompt = g_string_sized_new (NAME_MAX+64); GString *src = g_string_sized_new (1024); GString *dest = g_string_sized_new (1024); gchar *converted, *new_name, *public; gboolean check = e2_option_bool_get ("confirm-overwrite"); guint count; E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata; //setup for tailoring over-write dialog gboolean multisrc = (check) ? names->len > 1 : FALSE; // setup for showing stop button in rename dialog OW_ButtonFlags extras = (multisrc)? NOALL : NONE; e2_filelist_disable_refresh (); e2_task_advise (); for (count=0; count < names->len; count++, iterator++) { //".." entries filtered when names compiled converted = F_FILENAME_FROM_LOCALE ((*iterator)->filename); public = g_markup_escape_text (converted, -1); g_string_printf (prompt, "%s: %s", _("Enter new name for"), public); g_free (public); DialogButtons result = 0; e2_filelist_enable_refresh (); //allow updates while we wait gdk_threads_enter (); *qed->status = E2_TASK_PAUSED; DialogButtons result2 = e2_dialog_line_input (_("rename"), prompt->str, converted, extras, FALSE, &new_name); *qed->status = E2_TASK_RUNNING; gdk_threads_leave (); e2_filelist_disable_refresh (); if (result2 == OK) { if (g_str_equal (converted, new_name)) { g_free (new_name); F_FREE (converted); continue; } g_string_printf (src, "%s%s", curr_local, (*iterator)->filename); //separator comes with dir gchar *local = F_FILENAME_TO_LOCALE (new_name); g_string_printf (dest, "%s%s", curr_local, local); F_FREE (local); #ifdef E2_VFS sdata.localpath = src->str; ddata.localpath = dest->str; gboolean exists = !e2_fs_access2 (&ddata E2_ERR_NONE()); #else gboolean exists = !e2_fs_access2 (dest->str E2_ERR_NONE()); #endif if (exists) { //maybe new name exists, or maybe it's just the old name with some different case //FIXME use curr_view->case_sensitive - but view may have altered now gchar *old_name_lc = g_utf8_casefold (converted, -1); gchar *new_name_lc = g_utf8_casefold (new_name, -1); gboolean case_change = g_str_equal (old_name_lc, new_name_lc); g_free (old_name_lc); g_free (new_name_lc); if (case_change) { //we have just a case-difference that looks the same to the kernel //do interim name change to check, & also to ensure case-change happens gchar *tempname = e2_utils_get_tempname (src->str); // if (e2_task_backend_rename (slocal, tempname) // && e2_task_backend_rename (tempname, dlocal)) #ifdef E2_VFS tdata.localpath = tempname; if (e2_task_backend_rename (&sdata, &tdata)) #else if (e2_task_backend_rename (src->str, tempname)) #endif { #ifdef E2_VFS exists = !e2_fs_access2 (&ddata E2_ERR_NONE()); #else exists = !e2_fs_access2 (dest->str E2_ERR_NONE()); #endif if (exists) { //the new name really exists //revert and process normally #ifdef E2_VFS e2_task_backend_rename (&tdata, &sdata); #else e2_task_backend_rename (tempname, src->str); #endif g_free (tempname); } else { //the former detection was fake, just do the rest of the rename //CHECKME this may be irrelevant #ifdef E2_VFS e2_task_backend_rename (&tdata, &ddata); #else e2_task_backend_rename (tempname, dest->str); #endif /*#ifdef E2_INCLIST _e2_task_treeview_line_rename ((*iterator)->ref, NOTFREEDnew_name); #else gtk_tree_selection_unselect_path (selection, (*iterator)->path); #endif */ g_free (tempname); g_free (new_name); F_FREE (converted); continue; } } else { #ifdef E2_VFS sdata.localpath = (*iterator)->filename; #endif e2_fs_error_simple (_("Cannot rename %s"), #ifdef E2_VFS &sdata); #else (*iterator)->filename); #endif g_free (tempname); g_free (new_name); F_FREE (converted); continue; } } } if (check && exists) { e2_filelist_enable_refresh (); //allow updates while we wait gdk_threads_enter (); *qed->status = E2_TASK_PAUSED; result = e2_dialog_ow_check (src->str, dest->str, extras); *qed->status = E2_TASK_RUNNING; gdk_threads_leave (); e2_filelist_disable_refresh (); if (result == OK) { #ifdef E2_VFS e2_task_backend_rename (&sdata, &ddata); #else e2_task_backend_rename (src->str, dest->str); #endif } else if (result == NO_TO_ALL) { g_free (new_name); F_FREE (converted); break; } } else { #ifdef E2_VFS e2_task_backend_rename (&sdata, &ddata); #else e2_task_backend_rename (src->str, dest->str); #endif } g_free (new_name); F_FREE (converted); } else { F_FREE (converted); if (result2 == NO_TO_ALL) break; } } g_string_free (prompt,TRUE); g_string_free (src,TRUE); g_string_free (dest,TRUE); e2_window_clear_status_message (); e2_filelist_enable_refresh (); return TRUE; } /** @brief show/change permission of selected items in active pane The actual operation is performed by a separate back-end function Directories are treated separately, to support recursive changes to directories only View is not updated immediately as the change does not trigger an auto-refresh FIXME @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if task completed successfully, else FALSE */ gboolean e2_task_permissions (gpointer from, E2_ActionRuntime *art) { return (e2_task_enqueue_task (E2_TASK_CHMOD, art, from, _e2_task_permissionsQ, e2_task_refresh_lists)); } static gboolean _e2_task_permissionsQ (E2_ActionTaskData *qed) { // printd (DEBUG, "task: permissions"); GPtrArray *names = qed->names; gchar *curr_local = qed->currdir; #ifdef E2_VFS VPATH sdata; sdata.spacedata = qed->currspace; #endif gchar *modestring = NULL; guint count; gint myuid = -1; gboolean multisrc = names->len > 1; gboolean all = FALSE; E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata; GString *path = g_string_sized_new (1024); /* out-of-loop setup = FIXME GtkWidget *dialog; dialog = e2_permissions_dialog_setup (info, &recurse); e2_dialog_add_buttons_simple (dialog, buttons ..., NULL); */ #ifdef E2_REFRESH_DEBUG printd (DEBUG, "disable refresh, permissions task"); #endif e2_filelist_disable_refresh (); e2_task_advise (); for (count=0; count < names->len; count++, iterator++) { DialogButtons choice; E2_RecurseType recurse; gboolean permission; //".." entries filtered when names compiled //FIXME for single-setup: instead of the following, adjust file details in dialog, reset default button g_string_printf (path, "%s%s", curr_local, (*iterator)->filename); //separator comes with dir #ifdef E2_VFS sdata.localpath = path->str; #endif if (all) { //check if we have permission to change this item //this tests _all_ W permissions, access() is not so sophisticated ... #ifdef E2_VFS permission = e2_fs_check_write_permission (&sdata E2_ERR_NONE()); #else permission = e2_fs_check_write_permission (path->str E2_ERR_NONE()); #endif if (permission) choice = OK; else { #ifdef E2_VFS sdata.localpath = (*iterator)->filename; #endif e2_fs_error_simple ( _("You do not have authority to change permission(s) of %s"), #ifdef E2_VFS &sdata); #else (*iterator)->filename); #endif choice = CANCEL; } } else { #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, permissions dialog"); #endif e2_filelist_enable_refresh (); //allow updates while we wait *qed->status = E2_TASK_PAUSED; #ifdef E2_VFS choice = e2_permissions_dialog_run (&sdata, #else choice = e2_permissions_dialog_run (path->str, #endif &modestring, &recurse, &permission, multisrc); *qed->status = E2_TASK_RUNNING; #ifdef E2_REFRESH_DEBUG printd (DEBUG, "disable refresh, permissions dialog"); #endif e2_filelist_disable_refresh (); } switch (choice) { case YES_TO_ALL: all = TRUE; myuid = getuid (); //do this once, for speed choice = OK; case OK: if (permission && modestring != NULL) { #ifdef E2_INCLIST if (e2_task_backend_chmod (path->str, modestring, recurse)) { //FIXME update line in treeview } #else # ifdef E2_VFS sdata.localpath = path->str; # endif # ifdef E2_FAM # ifdef E2_VFS e2_task_backend_chmod (&sdata, modestring, recurse); # else e2_task_backend_chmod (path->str, modestring, recurse); # endif # else # ifdef E2_VFS if (e2_task_backend_chmod (&sdata, modestring, recurse)) { if (sdata.spacedata == NULL) { sdata.localpath = qed->currdir; e2_fs_touchnow (&sdata E2_ERR_NONE()); } } # else if (e2_task_backend_chmod (path->str, modestring, recurse)) //make the file-list refresher notice successful change e2_fs_touchnow (qed->currdir E2_ERR_NONE()); # endif # endif #endif if (!all) { g_free (modestring); modestring = NULL; } } case CANCEL: break; default: choice = NO_TO_ALL; // break flag; break; } if (choice == NO_TO_ALL) break; } if (modestring != NULL) g_free (modestring); g_string_free (path, TRUE); e2_window_clear_status_message (); #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, permissions task"); #endif e2_filelist_enable_refresh (); return TRUE; } /** @brief show/change user and/or group of selected items in active pane The actual operation is performed by a separate back-end function @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if task completed successfully, else FALSE */ gboolean e2_task_ownership (gpointer from, E2_ActionRuntime *art) { return (e2_task_enqueue_task (E2_TASK_CHOWN, art, from, _e2_task_ownershipQ, e2_task_refresh_lists)); } static gboolean _e2_task_ownershipQ (E2_ActionTaskData *qed) { GPtrArray *names = qed->names; gchar *curr_local = qed->currdir; #ifdef E2_VFS VPATH sdata; sdata.spacedata = qed->currspace; #endif GString *path = g_string_sized_new (1024); guint count; DialogButtons choice; gint myuid = -1; E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata; gboolean multisrc = names->len > 1; gboolean all = FALSE; e2_filelist_disable_refresh (); e2_task_advise (); for (count=0; count < names->len; count++, iterator++) { gboolean permission; uid_t owner_id; gid_t group_id; gboolean recurse; //".." entries filtered when names compiled g_string_printf (path, "%s%s", curr_local, (*iterator)->filename); //separator comes with dir #ifdef E2_VFS sdata.localpath = path->str; #endif if (all) { //check if we have permission to change this item permission = e2_fs_check_write_permission ( #ifdef E2_VFS &sdata E2_ERR_NONE()); #else path->str E2_ERR_NONE()); #endif if (!permission) { #ifdef E2_VFS sdata.localpath = (*iterator)->filename; #endif e2_fs_error_simple ( _("You do not have authority to change owner(s) of %s"), #ifdef E2_VFS &sdata); #else (*iterator)->filename); #endif } choice = (permission) ? OK : CANCEL; } else { owner_id=-1; //CHECKME = should these be set outside loop ? group_id=-1; permission = TRUE; recurse = FALSE; //leave dialog open as long as possible to prevent 'shadow' prior to screen refresh e2_filelist_enable_refresh (); //allow updates while we wait *qed->status = E2_TASK_PAUSED; choice = e2_ownership_dialog_run ( #ifdef E2_VFS &sdata, #else path->str, #endif &owner_id, &group_id, &recurse, &permission, multisrc); *qed->status = E2_TASK_RUNNING; e2_filelist_disable_refresh (); } switch (choice) { case YES_TO_ALL: all = TRUE; myuid = getuid (); choice = OK; case OK: if (permission) { // printd (DEBUG, "ownership task: ok"); // printd (DEBUG, "new owner id is: %d", owner_id); #ifdef E2_INCLIST if (e2_task_backend_chown (path->str, owner_id, group_id, recurse)) { //FIXME update line in treeview } #else # ifdef E2_FAM # ifdef E2_VFS e2_task_backend_chown (&sdata, owner_id, group_id, recurse); # else e2_task_backend_chown (path->str, owner_id, group_id, recurse); # endif # else # ifdef E2_VFS if (e2_task_backend_chown (&sdata, owner_id, group_id, recurse)) { if (sdata.spacedata == NULL) { sdata.localpath = qed->currdir; e2_fs_touchnow (&sdata E2_ERR_NONE()); } } # else if (e2_task_backend_chown (path->str, owner_id, group_id, recurse)) //make the file-list refresher notice successful change e2_fs_touchnow (qed->currdir E2_ERR_NONE()); # endif # endif #endif } case CANCEL: break; default: choice = NO_TO_ALL; // break flag; break; } if (choice == NO_TO_ALL) break; } g_string_free (path,TRUE); e2_window_clear_status_message (); e2_filelist_enable_refresh (); return TRUE; } /** @brief execute the operation specified from the drag menu, on all dragged items It is assumed all items have the same source path, @a s_local. Nothing will be done if @a s_local and @a d_local are the same. Item overwrite checks are performed if that option is in force. Processing of dropped items stops if any problem occurs This is called from inside gtk's BGL @param type code for function to be performed on dragged items @param s_local source path, localised string with trailing / @param d_local destination path, localised string with trailing / @param names_local array of localised item names to process with @a func @return TRUE if the items were all processed successfully (this must be synchronous) */ gboolean e2_task_drop (E2_TaskType type, gchar *s_local, gchar *d_local, GPtrArray *names_local) { // printd (DEBUG, "callback: drop task"); if (strcmp (s_local, d_local) == 0) return FALSE; //store the data for use by setup function //these data are not copied, and so must not be cleared in the calling function srcdir_local = s_local; destdir_local = d_local; names_array = names_local; gchar *action_name; gboolean result; gpointer taskfunc; switch (type) { case E2_TASK_LINK: action_name = g_strconcat (_A(5),".",_A(91),NULL); taskfunc = _e2_task_symlinkQ; break; case E2_TASK_MOVE: action_name = g_strconcat (_A(5),".",_A(58),NULL); taskfunc = _e2_task_moveQ; break; default: // case E2_TASK_COPY: action_name = g_strconcat (_A(5),".",_A(33),NULL); taskfunc = _e2_task_copyQ; break; } E2_Action *action = e2_action_get (action_name); E2_ActionRuntime *art = e2_action_pack_runtime (action, NULL, NULL); result = e2_task_run_task (type, art, NULL, taskfunc, e2_task_refresh_lists, FALSE, FALSE); g_free (action_name); e2_action_free_runtime (art); if (!result) return FALSE; result = _e2_task_wait (NULL); return result; } /** @brief change 'other' pane to be the same as 'current' pane Downstream function expects BGL to be closed @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ gboolean e2_task_sync_dirs (gpointer from, E2_ActionRuntime *art) { e2_pane_change_dir (other_pane, curr_pane->path); //FIXME Q this return TRUE; } /** @brief show info about each select item in active pane NB the active pane directory is not necessarily the current file system dir, so the path is added to the filename FIXME on this ? @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if task completed successfully, else FALSE */ gboolean e2_task_file_info (gpointer from, E2_ActionRuntime *art) { return (e2_task_do_task (E2_TASK_INFO, art, from, _e2_task_file_infoQ, NULL)); } static gboolean _e2_task_file_infoQ (E2_ActionTaskData *qed) { GPtrArray *names = qed->names; gchar *curr_local = qed->currdir; #ifdef E2_VFS VPATH sdata; sdata.spacedata = qed->currspace; #endif guint count; gboolean multi; E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata; e2_filelist_disable_refresh (); //prevent access-related refresh for (count=0; count < names->len; count++, iterator++) { //".." entries filtered when names compiled gchar *localpath = e2_utils_strcat (curr_local, (*iterator)->filename); #ifdef E2_VFS sdata.localpath = localpath; if (!e2_fs_access2 (&sdata E2_ERR_NONE())) #else if (!e2_fs_access2 (localpath E2_ERR_NONE())) #endif { multi = (names->len > 1 && count < (names->len - 1)); *qed->status = E2_TASK_PAUSED; #ifdef E2_VFS DialogButtons choice = e2_file_info_dialog_run (&sdata, multi); #else DialogButtons choice = e2_file_info_dialog_run (localpath, multi); #endif *qed->status = E2_TASK_RUNNING; if (multi && (choice == NO_TO_ALL)) break; } g_free (localpath); } e2_filelist_enable_refresh (); return TRUE; } /** @brief view content of nomimated file or 1st selected item in active pane ATM assume that non-local files can also be edited The actual operation is performed by internal or extenal viewer, depending on the option specfied @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if task completed successfully, else FALSE */ gboolean e2_task_view (gpointer from, E2_ActionRuntime *art) { return (e2_task_do_task (E2_TASK_VIEW, art, from, _e2_task_viewQ, NULL)); } static gboolean _e2_task_viewQ (E2_ActionTaskData *qed) { // printd (DEBUG, "task: view"); gboolean retval; GPtrArray *names = qed->names; if (names == NULL) return FALSE; E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata; //".." entries filtered when names compiled gchar *local = e2_utils_strcat (qed->currdir, (*iterator)->filename); #ifdef E2_VFS VPATH sdata = { local, qed->currspace }; #endif //grab mutex in case of error or other output message gdk_threads_enter (); #ifdef E2_VFS retval = e2_task_backend_view (&sdata); #else retval = e2_task_backend_view (local); #endif gdk_threads_leave (); g_free (local); return retval; } /** @brief edit nomimated file or 1st selected item in active pane ATM assume that non-local files can also be edited @param action_data ptr to data assigned when action struct created at session start @param rt_data ptr to data sent to this command, NULL to act on selection @param from the button, menu item etc which was activated @return TRUE if task completed successfully, else FALSE */ gboolean e2_task_edit (gpointer from, E2_ActionRuntime *art) { return (e2_task_do_task (E2_TASK_EDIT, art, from, _e2_task_editQ, NULL)); } static gboolean _e2_task_editQ (E2_ActionTaskData *qed) { // printd (DEBUG, "task: edit"); gboolean retval; GPtrArray *names = qed->names; if (names == NULL) return FALSE; E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata; //".." entries filtered when names compiled gchar *local = e2_utils_strcat (qed->currdir, (*iterator)->filename); if (e2_option_bool_get ("use-external-editor")) { gchar *editor = e2_option_str_get ("command-editor"); if (*editor != '\0') { gchar *edit_this = F_FILENAME_FROM_LOCALE (local); gchar *command = e2_utils_replace_name (editor, edit_this); if (command == NULL) command = g_strdup_printf ("%s '%s'", editor, edit_this); //grab mutex in case of error or other output message gdk_threads_enter (); gint res = e2_command_run (command, E2_COMMAND_RANGE_DEFAULT #ifdef E2_COMMANDQ , TRUE #endif ); gdk_threads_leave (); F_FREE (edit_this); g_free (command); retval = (res == 0); } else retval = FALSE; } else { #ifdef E2_VFS VPATH sdata = { local, qed->currspace }; #endif gdk_threads_enter (); retval = e2_edit_dialog_create ( #ifdef E2_VFS &sdata, NULL); #else local, NULL); #endif gdk_threads_leave (); } g_free (local); return retval; } /** @brief perform the default action for nominated item, or the first selected item in active pane @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if task completed successfully, else FALSE */ gboolean e2_task_open (gpointer from, E2_ActionRuntime *art) { //need to support opening a specified item, maybe with path != %d if (from == (gpointer) curr_view->treeview || from == (gpointer) other_view->treeview) { ViewInfo *view = (from == (gpointer) curr_view->treeview) ? curr_view : other_view; FileInfo *infoptr = e2_fileview_get_selected_first_local (view, TRUE); if (infoptr == NULL) return FALSE; //parse ".." here (for cd ".." and similar), as ".." is excluded from //normal selections if (g_str_equal (infoptr->filename, "..") // && gtk_tree_selection_count_selected_rows (view->selection) == 1 ) { if (art->data != NULL) g_free (art->data); //E2_VFSTMPOK art->data = g_strdup (view->dir); gint len = strlen ((gchar *)art->data); if (len > 1) { *((gchar *)art->data + len - 1) = '\0'; //strip trailing separator gchar *s = strrchr ((gchar *)art->data, G_DIR_SEPARATOR); if (s != NULL) { if (s > (gchar *)art->data) *s = '\0'; else //at start of string, so parent is root dir *(s+sizeof(gchar)) = '\0'; } } } } #ifdef E2_VFSTMP FIXME may be any space if a specific item was nominated #endif return (e2_task_do_task (E2_TASK_OPEN, art, from, _e2_task_openQ, NULL)); } static gboolean _e2_task_openQ (E2_ActionTaskData *qed) { // printd (DEBUG, "task: open"); gboolean retval; GPtrArray *names = qed->names; if (names == NULL) return FALSE; E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata; //".." entries filtered when names compiled, unless ".." is the only item selected //when opening a specific item, the statbuf will not be completed gchar *local = e2_utils_strcat (qed->currdir, (*iterator)->filename); #ifdef E2_VFS VPATH sdata = { local, qed->currspace }; # ifdef E2_VFSTMP FIXME may be any space if a specific item was nominated # endif #endif gdk_threads_enter (); #ifdef E2_VFS retval = e2_task_backend_open (&sdata, TRUE); #else retval = e2_task_backend_open (local, TRUE); #endif gdk_threads_leave (); g_free (local); return retval; } /** @brief open selected item(s) in active pane with a chosen application If the specified command does not include "%f", all selected items are concatenated, and sent as a single argument to the application. Otherwise, the entered command is simply executed @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if task completed successfully, else FALSE */ gboolean e2_task_open_with (gpointer from, E2_ActionRuntime *art) { #ifdef E2_VFSTMP FIXME may be any space if a specific item was nominated #endif return (e2_task_do_task (E2_TASK_OPENWITH, art, from, _e2_task_open_withQ, NULL)); } static gboolean _e2_task_open_withQ (E2_ActionTaskData *qed) { gchar *command, *defcmd, *ext, *localpath; GPtrArray *names = qed->names; if (names == NULL) return FALSE; //determine a suggested command, the default for first-selected item E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata; #ifdef E2_VFS //".." entries filtered when names compiled localpath = e2_utils_strcat (qed->currdir, (*iterator)->filename); VPATH sdata = { localpath, qed->currspace }; //checking a virtual dir in the process of being mounted may cause bad delay //so we fake a rough guess at what is intended ... //FIXME how ?? (also needed in _e2_pane_change_dir()) printd (DEBUG, "NEED fake check whether %s is a directory", localpath); if ( // 1 || e2_fs_is_dir3 (&sdata E2_ERR_NONE())) #else //".." entries filtered when names compiled localpath = e2_utils_strcat (qed->currdir, (*iterator)->filename); if (e2_fs_is_dir3 (localpath E2_ERR_NONE())) #endif ext = _(""); #ifdef E2_VFS else if (!e2_fs_access (&sdata, X_OK E2_ERR_NONE())) #else else if (!e2_fs_access (localpath, X_OK E2_ERR_NONE())) #endif ext = _(""); else { ext = strchr ((*iterator)->filename, '.'); //assumes '.' is ascii if (ext == NULL //no extension || ext == (*iterator)->filename) //hidden file { #ifdef E2_VFS if (e2_fs_is_text (&sdata E2_ERR_NONE())) #else if (e2_fs_is_text (localpath E2_ERR_NONE())) #endif ext = "txt"; //too bad if this is not a recognised text extension in filetypes else ext = NULL; //clear value from hidden file } else { do { //skip leading dot "." ext++; //NCHR(ext); ascii '.'. always single char if (*ext == '\0') { ext = NULL; break; } if (e2_filetype_get_actions (ext) != NULL) break; } while ((ext = strchr (ext, '.')) != NULL); //always ascii '.', don't need g_utf8_strchr() } } g_free (localpath); command = (ext == NULL) ? NULL : (gchar*)e2_filetype_get_default_action (ext); //maybe NULL if (command != NULL) { defcmd = strchr (command, '@'); if (defcmd != NULL) defcmd++; else defcmd = command; //exclude commands which start by opening another dialog if (g_str_has_prefix (defcmd, "%{") && strchr (defcmd+2, '}') != NULL) defcmd = NULL; } else defcmd = NULL; gdk_threads_enter (); *qed->status = E2_TASK_PAUSED; DialogButtons result = e2_dialog_combo_input (_("open with"), _("Enter command:"), defcmd, 0, &open_history, &command); *qed->status = E2_TASK_RUNNING; gdk_threads_leave (); gboolean retval; if (result == OK) { gchar *freeme; if (strstr (command, "%f") == NULL && strstr (command, "%p") == NULL) { freeme = command; command = g_strconcat (command, " %f", NULL); g_free (freeme); } //E2_VFSTMP how to specify namespace in command arg(s) ? gchar *utf = F_FILENAME_FROM_LOCALE (qed->currdir); freeme = command; command = e2_utils_replace_name2 (command, utf, names, FALSE); F_FREE (utf); if (command == NULL) { g_free (freeme); return FALSE; } //grab mutex in case of error or other output message gdk_threads_enter (); #ifdef E2_COMMANDQ gint res = e2_command_run (command, E2_COMMAND_RANGE_DEFAULT, TRUE); #else gint res = e2_command_run (command, E2_COMMAND_RANGE_DEFAULT); #endif gdk_threads_leave (); retval = (res == 0); g_free (command); } else retval = FALSE; return retval; } /** @brief open directory which is currently selected, in the other pane Downstream function expects BGL to be closed @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ gboolean e2_task_open_in_other_pane (gpointer from, E2_ActionRuntime *art) { //handle case of specific data instead of selection?? //FIXME do this with localised data //FIXME check item is a dir // The last-activated row in the other pane is focused - this is not // necessarily sensible //CHECKME Q this e2_filelist_disable_refresh (); gchar *name = e2_fileview_get_row_name (curr_view, curr_view->row); if (name == NULL) { e2_filelist_enable_refresh (); return FALSE; //the model got mixed up, somehow } gchar *path = e2_utils_strcat (curr_view->dir, name); //separator comes with dir #ifdef E2_VFS if (other_view->spacedata != curr_view->spacedata) { VPATH ddata = { path, curr_view->spacedata }; //utf8 path expected e2_pane_change_space (other_pane, &ddata); //should never fail } else #endif e2_pane_change_dir (other_pane, path); g_free (name); g_free (path); e2_filelist_enable_refresh (); return TRUE; } /** @brief toggle tag on selected item in active pane Deleted after discussion with tooar @return */ /*void __e2_task_toggle_tag_cb (void) { //FIXME = logic of this is silly !! GList *tmp = e2_fileview_get_selection (curr_view); //deprecated FileInfo *info; gint row; // g_signal_emit_by_name (G_OBJECT (curr_view->clist), "end-selection"); for (; tmp != NULL; tmp = tmp->next) { / * row = GPOINTER_TO_INT (tmp->data); info = gtk_clist_get_row_data(GTK_CLIST(curr_view->clist), row); if (g_list_find (curr_view->tagged, info) != NULL) { curr_view->tagged = g_list_remove(curr_view->tagged, info); gtk_clist_set_background(GTK_CLIST(curr_view->clist), row, &LIST_COLOR); } else { curr_view->tagged = g_list_append(curr_view->tagged, info); gtk_clist_set_background(GTK_CLIST(curr_view->clist), row, e2_option_color_get ("color-tag")); } * / //FIXME } e2_fileview_focus_row(curr_view, curr_view->row+1, FALSE, TRUE, TRUE, TRUE); } */ /** @brief revert all configuration settings to defailt values @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ gboolean e2_task_configure_default (gpointer from, E2_ActionRuntime *art) { //dump old data, don't re-read the config file, recreate screen e2_option_refresh (FALSE, TRUE); return TRUE; } /** @brief initiate configuration change @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ gboolean e2_task_configure (gpointer from, E2_ActionRuntime *art) { //put this in config dialog file ? // gtk_widget_set_sensitive (app.main_window, FALSE); gchar *page = (gchar *)art->data; e2_config_dialog_create (page); // gtk_widget_set_sensitive (app.main_window, TRUE); return TRUE; } /** @brief find and select named item in active pane This differs from treeview type-ahead searching because the supplied pattern does not need to be at the start of the line The search starts from the current line, forward to end, then cycles back to the start @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if task completed successfully, else FALSE */ gboolean e2_task_find (gpointer from, E2_ActionRuntime *art) { gchar *s, *pattern; gint i, last, rowcount; //find the no. of rows in the store rowcount = gtk_tree_model_iter_n_children (curr_view->model, NULL); if (rowcount < 1) return FALSE; //no message ... e2_window_set_cursor (GDK_WATCH); //FIXME nicer to use a dialog that stays open until specifically closed, //with this stuff in its "find" callback DialogButtons choice = e2_dialog_line_input (_("find"), _("Enter a name or partial name to find:"), curr_view->last_find, 0, TRUE, &s); if (choice == OK) { g_strlcpy (curr_view->last_find, s, sizeof(curr_view->last_find)); pattern = g_strdup_printf ("*%s*", s); GtkTreeModel *model = curr_view->model; GtkTreePath *path; GtkTreeIter iter; if (gtk_tree_model_iter_nth_child (model, &iter, NULL, curr_view->row)) path = gtk_tree_model_get_path (model, &iter); else //try fallback to the row of the current cursor position gtk_tree_view_get_cursor (GTK_TREE_VIEW (curr_view->treeview), &path, NULL); if (path == NULL) { last = rowcount - 1; //no defined start pos, start from beginning i = 0; } else { last = *gtk_tree_path_get_indices (path); if (last == rowcount - 1) i = 0; //roll around to the start else i = last + 1; //start at the next line gtk_tree_path_free (path); } //iterate through the rows gchar *thisrowname; gboolean matched = FALSE; while (i != last) { thisrowname = e2_fileview_get_row_name (curr_view, i); if (g_pattern_match_simple (pattern, thisrowname)) matched = TRUE; g_free (thisrowname); if (matched) break; if (++i >= rowcount) i = 0; //cycle to start } if (i == last) { //nothing found sofar, but maybe it's in the "last" row, where the scan started thisrowname = e2_fileview_get_row_name (curr_view, i); if (g_pattern_match_simple (pattern, thisrowname)) matched = TRUE; g_free (thisrowname); } if (matched) { gtk_tree_model_iter_nth_child (model, &iter, NULL, i); path = gtk_tree_model_get_path (model, &iter); gtk_tree_view_set_cursor (GTK_TREE_VIEW (curr_view->treeview), path, NULL, FALSE); } g_free(pattern); g_free(s); } e2_window_set_cursor (GDK_LEFT_PTR); // gtk_widget_grab_focus (curr_view->treeview); return TRUE; } /** @brief register task-related actions @return */ void e2_task_actions_register (void) { E2_Action task_actions[] = { //we don't bother explicitly setting each element's last-3 parameters { NULL, e2_task_open, FALSE, E2_ACTION_TYPE_ITEM, 0 }, { NULL, e2_task_open_in_other_pane, FALSE, E2_ACTION_TYPE_ITEM, 0 }, { NULL, e2_task_open_with, FALSE, E2_ACTION_TYPE_ITEM, 0 }, { NULL, e2_task_view, FALSE, E2_ACTION_TYPE_ITEM, 0 }, { NULL, e2_task_edit, FALSE, E2_ACTION_TYPE_ITEM, 0 }, { NULL, e2_task_file_info, FALSE, E2_ACTION_TYPE_ITEM, 0 }, { NULL, e2_task_permissions, FALSE, E2_ACTION_TYPE_ITEM, 0 }, { NULL, e2_task_ownership, FALSE, E2_ACTION_TYPE_ITEM, 0 }, { NULL, e2_task_rename, FALSE, E2_ACTION_TYPE_ITEM, 0 }, { NULL, e2_task_copy, FALSE, E2_ACTION_TYPE_ITEM, 0 }, //data == E2_FTM_NORMAL { NULL, e2_task_copy_as, FALSE, E2_ACTION_TYPE_ITEM, 0 }, { NULL, e2_task_move, FALSE, E2_ACTION_TYPE_ITEM, 0 }, { NULL, e2_task_move_as, FALSE, E2_ACTION_TYPE_ITEM, 0 }, { NULL, e2_task_symlink, FALSE, E2_ACTION_TYPE_ITEM, 0 }, { NULL, e2_task_symlink_as, FALSE, E2_ACTION_TYPE_ITEM, 0 }, { NULL, e2_task_delete, FALSE, E2_ACTION_TYPE_ITEM, 0 }, { NULL, e2_task_find, FALSE, E2_ACTION_TYPE_ITEM, 0 }, { NULL, e2_task_trashit, FALSE, E2_ACTION_TYPE_ITEM, 0 }, { NULL, e2_task_trashempty, FALSE, E2_ACTION_TYPE_ITEM, 0 } }; task_actions[0].name = g_strconcat(_A(5),".",_A(60),NULL); task_actions[1].name = g_strconcat(_A(5),".",_A(61),NULL); task_actions[2].name = g_strconcat(_A(5),".",_A(62),NULL); task_actions[3].name = g_strconcat(_A(5),".",_A(101),NULL); task_actions[4].name = g_strconcat(_A(5),".",_A(39),NULL); task_actions[5].name = g_strconcat(_A(5),".",_A(52),NULL); task_actions[6].name = g_strconcat(_A(5),".",_A(67),NULL); task_actions[7].name = g_strconcat(_A(5),".",_A(63),NULL); task_actions[8].name = g_strconcat(_A(5),".",_A(73),NULL); task_actions[9].name = g_strconcat(_A(5),".",_A(33),NULL); task_actions[10].name = g_strconcat(_A(5),".",_A(34),NULL); task_actions[11].name = g_strconcat(_A(5),".",_A(58),NULL); task_actions[12].name = g_strconcat(_A(5),".",_A(59),NULL); task_actions[13].name = g_strconcat(_A(5),".",_A(91),NULL); task_actions[14].name = g_strconcat(_A(5),".",_A(92),NULL); task_actions[15].name = g_strconcat(_A(5),".",_A(38),NULL); task_actions[16].name = g_strconcat(_A(5),".",_A(6),NULL); task_actions[17].name = g_strconcat(_A(5),".",_A(97),NULL); task_actions[18].name = g_strconcat(_A(1),".",_A(98),NULL); //not applied to selected items gint actions_count = sizeof (task_actions) / sizeof (E2_Action); gint i; for (i = 0; i < actions_count; i++) { e2_action_register ( task_actions[i].name, task_actions[i].type, task_actions[i].func, NULL, task_actions[i].has_arg, task_actions[i].exclude, NULL); } //these have action data gchar *action = g_strconcat(_A(5),".",_A(35),NULL); e2_action_register (action, E2_ACTION_TYPE_ITEM, e2_task_copy, GINT_TO_POINTER (E2_FTM_MERGE), FALSE, 0, NULL); action = g_strconcat(_A(5),".",_A(36),NULL); e2_action_register (action, E2_ACTION_TYPE_ITEM, e2_task_copy, GINT_TO_POINTER (E2_FTM_SAMETIME), FALSE, 0, NULL); } emelfm2-0.4.1/src/e2_task.h0000600000175000017500000002031411010340377014273 0ustar cairocairo/* $Id: e2_task.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2003-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_TASK_H__ #define __E2_TASK_H__ #include "emelfm2.h" #include "e2_permissions_dialog.h" #ifndef ALLPERMS #define ALLPERMS (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO) #endif //flags for special treatment of copy (and move, link?) actions typedef enum { E2_FTM_NORMAL = 0, //this must have no bits set E2_FTM_BACKUP = 1, //backup overwritten items E2_FTM_TRASH = 1 << 1, //trash overwritten items E2_FTM_MERGE = 1 << 2, //merge (not replace) the contents of processed directories E2_FTM_RENAME = 1 << 3, //copy-as UNUSED E2_FTM_CHECK = 1 << 4, //perform backend overwrite checking (used when merging) E2_FTM_PAUSABLE = 1 << 5, //UNUSED check for pause requests during file copy E2_FTM_SAMETIME = 1 << 8, //preserve times of processed items E2_FTM_SAMEACL = 1 << 9, //preserve ACL's of processed items } E2_FileTaskMode; //code to facilitate undoing (not implemented yet) typedef enum { E2_TASK_COPY, E2_TASK_COPYAS, E2_TASK_MOVE, E2_TASK_MOVEAS, E2_TASK_LINK, E2_TASK_LINKAS, E2_TASK_DELETE, E2_TASK_RENAME, E2_TASK_TRASH, E2_TASK_TRASHEMPTY, E2_TASK_CHMOD, E2_TASK_CHOWN, E2_TASK_INFO, E2_TASK_VIEW, E2_TASK_EDIT, E2_TASK_OPEN, E2_TASK_OPENWITH, E2_TASK_DROP, E2_TASK_MKDIR, //plugin tasks E2_TASK_CLONE, E2_TASK_TIMESET, E2_TASK_PACK, E2_TASK_UNPACK, E2_TASK_FOREACH, E2_TASK_CHACL, E2_TASK_CRYPT, E2_TASK_LAST //this is just a code for other plugins etc } E2_TaskType; //the order here matters - some code checks for > E2_TASK_RUNNING //or > E2_TASK_COMPLETED or <= E2_TASK_QUEUED typedef enum { E2_TASK_NONE, //not yet specified E2_TASK_QUEUED, //waiting to be processed E2_TASK_PAUSED, //a running action is paused, eg while waiting for user input E2_TASK_RUNNING, //task function has been called E2_TASK_COMPLETED, //task function has returned (any completion status) E2_TASK_INCOMPLETE, //the task has timed out E2_TASK_ABORTED, //the task was stopped by the user E2_TASK_DONE, //UNUSED E2_TASK_FAILED //command execution failed for some reason } E2_TaskStatus; typedef enum { E2_TASKTYPE_ASYNC, E2_TASKTYPE_SYNC, E2_TASKTYPE_ACTION } E2_TaskDataType; typedef struct _E2_ActionTaskData { VOLATILE E2_TaskType tasktype; //what type of task VOLATILE gboolean result; //result from the function performing the task VOLATILE gchar *currdir; //the active dir when action initiated, localised string with trailer VOLATILE gchar *othrdir; //the inactive dir when action initiated, localised string with trailer #ifdef E2_VFS VOLATILE PlaceInfo *currspace; //pointers to space data, -1 if unknown VOLATILE PlaceInfo *othrspace; #endif VOLATILE GPtrArray *names; //selected items array VOLATILE E2_Action *action; //action (hence, name and setup data) VOLATILE gpointer rt_data; //action runtime data (usually string or NULL, maybe flags) VOLATILE GtkWidget *initiator; //the widget which was activated to initiate the task VOLATILE gpointer taskfunc; //the function to perform the task VOLATILE gpointer callback; //the function to call upon completion (maybe NULL) //to prevent races, only 1 actioner and 1 monitor at any time - hence static ID's // VOLATILE pthread_t athreadID; //id of the action-thread assigned to run the task // VOLATILE pthread_t mthreadID; //id of the monitor-thread assigned to run the task VOLATILE time_t timelimit; //if not finished by this, abort the task VOLATILE E2_TaskStatus *status; //pointer to status in E2_TaskRuntime (for changes) } E2_ActionTaskData; //task-queue data for commands and actions typedef struct _E2_TaskRuntime { VOLATILE gboolean action; //TRUE when this data applies to an internal command VOLATILE glong pid; //id assigned to the child (process or thread, real or fake, unsigned for a thread) VOLATILE gchar *pidstr; //string form of pid (any leading "-" replaced by "a" or "s") //ordinal enumerator of the task's position in the (possibly-queued) execution sequence VOLATILE E2_TaskStatus status; union _E2_TaskTypeExtra { E2_CommandTaskData command; //for a command E2_ActionTaskData action; //for an action } ex; //=background_tab or &app.tab if this command is currently running in the focused tab VOLATILE E2_OutputTabRuntime *current_tab; //the output data to use for this command when running in a non-focused tab VOLATILE E2_OutputTabRuntime *background_tab; VOLATILE GtkWidget *dialog; //timeout dialog, or NULL; } E2_TaskRuntime; typedef struct _E2_DirEnt { gchar *path; mode_t mode; time_t modtime; time_t axstime; } E2_DirEnt; typedef struct _E2_CopyData { #ifdef E2_VFS GError **operr; #endif E2_FileTaskMode taskmode; //flags to guide the features of the copying gboolean continued_after_problem; //TRUE when a problem is found, but the walk is not aborted gint oldroot_len; //length of source item path, less its last element gchar *newroot; //destination item path, less its last element, no trailing /, localised string #ifdef E2_VFS PlaceInfo *destspace; #endif GList *dirdata; //list of directories copied } E2_CopyData; typedef struct _E2_ChmodData { #ifdef E2_VFS GError **operr; #endif gboolean continued_after_problem; E2_RecurseType scope; mode_t setmask; mode_t clearmask; GList *dirdata; } E2_ChmodData; typedef struct _E2_ChownData { #ifdef E2_VFS GError **operr; #endif gboolean continued_after_problem; uid_t new_uid; gid_t new_gid; GList *dirdata; } E2_ChownData; E2_TaskRuntime *e2_task_find_running_task (glong pid); E2_TaskRuntime *e2_task_find_last_running_child (gboolean anytab); E2_TaskRuntime *e2_task_set_data (glong pid, E2_TaskDataType mode, gchar *command); void e2_task_abort (gboolean all); #define e2_task_do_task(type,art,from,taskfunc,callback) \ e2_task_run_task (type, art, from, taskfunc, callback, TRUE, TRUE) #define e2_task_enqueue_task(type,art,from,taskfunc,callback) \ e2_task_run_task (type, art, from, taskfunc, callback, FALSE, TRUE) gboolean e2_task_run_task (E2_TaskType type, E2_ActionRuntime *art, gpointer from, gpointer taskfunc, gpointer callback, gboolean immediate, gboolean pane_data); void e2_task_refresh_lists (E2_ActionTaskData *qed); void e2_task_advise (void); gchar *e2_task_tempname (const gchar *path); gboolean e2_task_sync_dirs (gpointer from, E2_ActionRuntime *art); gboolean e2_task_view (gpointer from, E2_ActionRuntime *art); gboolean e2_task_drop (E2_TaskType type, gchar *srcdir_local, gchar *destdir_local, GPtrArray *names_array); gboolean e2_task_refresh (gpointer from, E2_ActionRuntime *art); //gboolean e2_task_open (gpointer from, E2_ActionRuntime *art); gboolean e2_task_open_with (gpointer from, E2_ActionRuntime *art); //gboolean e2_task_open_in_other_pane (gpointer from, E2_ActionRuntime *art); gboolean e2_task_configure (gpointer from, E2_ActionRuntime *art); gboolean e2_task_configure_default (gpointer from, E2_ActionRuntime *art); //gboolean e2_task_find (gpointer from, E2_ActionRuntime *art); gboolean e2_task_backend_copy (VPATH *src, VPATH *dest, E2_FileTaskMode mode); gboolean e2_task_backend_move (VPATH *src, VPATH *dest); gboolean e2_task_backend_link (VPATH *target, VPATH *name); gboolean e2_task_backend_rename (VPATH *oldsrc, VPATH*newsrc); gboolean e2_task_backend_delete (VPATH *localpath); gboolean e2_task_backend_chmod (VPATH *localpath, gchar *mode, E2_RecurseType recurse); gboolean e2_task_backend_chown (VPATH *localpath, uid_t owner_id, gid_t group_id, gboolean recurse); gboolean e2_task_backend_open (VPATH *localpath, gboolean ask); gboolean e2_task_backend_view (VPATH *localpath); void e2_task_actions_register (void); #endif //ndef __E2_TASK_H__ emelfm2-0.4.1/src/build/0000700000175000017500000000000011015120161013657 5ustar cairocairoemelfm2-0.4.1/src/e2_context_menu.h0000600000175000017500000000253411010340377016045 0ustar cairocairo/* $Id: e2_context_menu.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2003-2008 tooar Portions copyright (C) 2004 Florian Zaehringer This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_CONTEXT_MENU_H__ #define __E2_CONTEXT_MENU_H__ #include "emelfm2.h" void e2_context_menu_create_desktop_actions_menu (GtkWidget *menu, gchar *desktopfilepath); void e2_context_menu_create_filetype_actions_menu (GtkWidget *menu, const gchar **actions); void e2_context_menu_show (guint button, guint32 time, gint type); gboolean e2_context_menu_show_menu_action (gpointer from, E2_ActionRuntime *art); void e2_context_menu_options_register (void); #endif //ndef __E2_CONTEXT_MENU_H__ emelfm2-0.4.1/src/e2_window.c0000600000175000017500000017461010776374176014672 0ustar cairocairo/* $Id: e2_window.c 850 2008-04-07 10:33:34Z tpgww $ Copyright (C) 2004-2008 tooar Portions copyright (C) 1999 Michael Clark. This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/e2_window.c @brief main window functions main window functions, including actions on panes */ /** \page status the status line ToDo - description of how this works */ #include "emelfm2.h" #include #include #include #include #include "e2_window.h" #include "e2_option.h" #include "e2_action.h" #include "e2_context_menu.h" #include "e2_toolbar.h" #include "e2_filelist.h" #include "e2_task.h" extern GList *cols_data; extern gint col_width_store[2][MAX_COLUMNS]; extern gint stored_col_order[2][MAX_COLUMNS]; //main-window size before fullscreen enacted gint real_width; gint real_height; static gdouble _e2_window_get_pos (GtkWidget *paned); /*****************/ /***** utils *****/ /*****************/ /** @brief create paned widget to contain the file panes, and surrounding containers Depending on whether the file panes are horiz or vert, a vert or horiz paned widget is packed inside two boxes The 'separator' between the panes is set to the correct position (this is before pane content is added, so that un-necessary content mapping is minimized) All created widgets are 'shown' @param rt ptr to window data struct @return */ static void _e2_window_create_pane_boxes (E2_WindowRuntime *rt) { gint base; if (e2_option_bool_get ("panes-horizontal")) { rt->panes_horizontal = TRUE; rt->panes_outer_box = gtk_hbox_new (FALSE, 0); rt->panes_inner_box = gtk_vbox_new (FALSE, 0); rt->panes_paned = gtk_vpaned_new (); base = app.main_window->allocation.height - 28; //28 = notional height of commandbar } else { rt->panes_horizontal = FALSE; rt->panes_outer_box = gtk_vbox_new (FALSE, 0); rt->panes_inner_box = gtk_hbox_new (FALSE, 0); rt->panes_paned = gtk_hpaned_new (); base = app.main_window->allocation.width - 5; //5 = trial'n'error value to minimise separator jitter } if (base < 2) base = 2; //set interim panes sizes, to minimize un-necessary content mapping //real size set again, later gtk_paned_set_position (GTK_PANED (rt->panes_paned), base * rt->panes_paned_ratio); rt->panes_paned_pos = gtk_paned_get_position (GTK_PANED (rt->panes_paned)); gtk_box_pack_start_defaults (GTK_BOX (rt->panes_outer_box), rt->panes_inner_box); gtk_box_pack_start_defaults (GTK_BOX (rt->panes_inner_box), rt->panes_paned); gtk_widget_show (rt->panes_outer_box); gtk_widget_show (rt->panes_inner_box); gtk_widget_show (rt->panes_paned); } /** @brief get ratio value for position of paned widget divider @param paned the widget for which the ratio is to be calculated @return ratio: paned current position / _paned->max_position */ static gdouble _e2_window_get_pos (GtkWidget *paned) { GtkPaned *_paned = GTK_PANED (paned); gint pos = gtk_paned_get_position (_paned); return ((gdouble) pos / _paned->max_position); } /** @brief calculate position of the divider for @a paned This converts a string fraction or percentage (with "%" suffix) to an integer, which corresponds to the specified fraction or percentage of @a paned ->max_position @param str string with value to be converted @param paned the paned widget to which the value applies @return integer value of the 'position' of the paned divider, or -2 if invalid string is provided */ static gint _e2_window_get_pos_from_str (gchar *str, GtkPaned *paned) { if (str == NULL) return -2; gboolean percent = g_str_has_suffix (str, "%"); gchar *end = NULL; gdouble num = g_ascii_strtod (str, &end); if (end == str) return -2; gint retval; if (percent) retval = paned->max_position * (num / 100.0); else retval = paned->max_position * num; return retval; } /** @brief change position of the separator between window file-panes or between file & output panes Value(s) provided in @a arg is (are both) a proportion of the relevant window dimension, or (the first one) may be * which causes the stored prior value of the fraction to be used Expects BGL to be on/closed on arrival here @param paned ptr to paned widget to be adjusted @param pos pointer to store for paned position value @param ratio_last pointer to store for the prior value of the 'paned ratio' (position / max. position) @param arg string with one (or two comma-separated) ratios in [0-1] @return */ static void _e2_window_adjust_panes_ratio (GtkPaned *paned, gint *pos, gdouble *ratio_last, gchar *arg) { printd (DEBUG, "_e2_window_adjust_panes_ratio (paned:,pos:%d,ratio_last:%f,arg:%s)", *pos, *ratio_last, arg); if (arg == NULL) return; gint cur; gchar *second = strchr (arg, ','); //if always ascii ',', don't need g_utf8_strchr() if (second != NULL) { glong offset = g_utf8_pointer_to_offset (arg, second); gchar *temp_str = e2_utf8_ndup (arg, offset); gint one = _e2_window_get_pos_from_str (temp_str, paned); g_free (temp_str); gint two = _e2_window_get_pos_from_str (second + 1, paned); //assumes the ',' is a single byte long ! if (two == -2) //invalid 2nd value provided return; cur = gtk_paned_get_position (paned); if (cur == two) { if (one == -2) { //invalid 1st value (probably *) provided if ((*pos == paned->max_position) || (*pos == paned->min_position)) cur = paned->max_position * *ratio_last; else cur = *pos; } else cur = one; } else { if ((cur != 0) && (cur != paned->max_position)) *pos = cur; cur = two; } } else //there is only one parameter provided { cur = _e2_window_get_pos_from_str (arg, paned); if (cur == -2) { //invalid value (probably *) provided if ((*pos == paned->max_position) || (*pos == paned->min_position)) cur = paned->max_position * *ratio_last; else cur = *pos; } else *pos = cur; } gtk_paned_set_position (paned, cur); gdouble ratio_now = cur / paned->max_position; if (ratio_now > 0.001 && ratio_now < 0.999) *ratio_last = ratio_now; if (paned == GTK_PANED (app.window.panes_paned)) { //do this only for filelist divider changes (may hang for output pane divider) if (ratio_now > 0.99 && GTK_WIDGET_HAS_FOCUS (app.pane2_view.treeview)) { WAIT_FOR_EVENTS //note - expects BGL closed gtk_widget_grab_focus (app.pane1_view.treeview); } else if (ratio_now < 0.01 && GTK_WIDGET_HAS_FOCUS (app.pane1_view.treeview)) { WAIT_FOR_EVENTS gtk_widget_grab_focus (app.pane2_view.treeview); } } } /** @brief change position of file-panes separator This is a wrapper for _e2_window_adjust_panes_ratio () Downstream expects BGL closed @param arg string with new ratio to be set @return */ void e2_window_adjust_pane_ratio (gchar *arg) { _e2_window_adjust_panes_ratio (GTK_PANED (app.window.panes_paned), &app.window.panes_paned_pos, &app.window.panes_paned_ratio_last, arg); } /** @brief set default window icon list Set default window icon list from files in configured icon_dir. That dir is assumed native Each with name starts with "emelfm2_", and has trailing 'xx.png' where xx are digits @param base filename base of the icon files @return */ static void _e2_window_set_icon (void) { gchar *icons_dir = e2_utils_get_icons_path (FALSE); DIR *d = e2_fs_dir_open (icons_dir); if (d == NULL) { printd (WARN, "could not open window icon directory '%s' (%s)", icons_dir, g_strerror (errno)); g_free (icons_dir); gtk_window_set_default_icon_name (BINNAME); return; } GList *list = NULL; const gchar *name; //name is like "emelfm2_24.png" gchar *base = BINNAME"_"; //this is the 'prefix' for window icon filenames guint len = strlen (base) + 6; //assuming ascii, +6 allows for xx.png, filters out the 'alternates' struct dirent entry; struct dirent *entryptr; while (TRUE) { if (e2_fs_dir_read (d, &entry, &entryptr) || entryptr == NULL) break; name = entry.d_name; if (g_str_has_prefix (name, base) && g_str_has_suffix (name, ".png") && strlen (name) == len) { gchar *file = g_build_filename (icons_dir, name, NULL); GError *error = NULL; GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file (file, &error); if (error != NULL) { printd (WARN, "could not open image file '%s' (%s)", file, error->message); g_free (file); g_error_free (error); continue; } g_free (file); list = g_list_append (list, pixbuf); } } if (list != NULL) { gtk_window_set_default_icon_list (list); //gtk copies list and ref's its items, so cleanup here g_list_foreach (list, (GFunc) g_object_unref, NULL); g_list_free (list); } g_free (icons_dir); e2_fs_dir_close (d); } /*********************/ /***** callbacks *****/ /*********************/ /** @brief callback for "size-allocate" signal on app.pane1.outer_box This happens when file panes divider moves (among other instances) @param widget UNUSED the resized widget, app.pane1.outer_box @param alloc the allocation for @a widget @param rt pointer to window data struct @return */ static void _e2_window_filepanes_adjusted_cb (GtkWidget *widget, GtkAllocation *alloc, E2_WindowRuntime *rt) { static gint prev_size = -1; if ((rt->panes_horizontal && alloc->height != prev_size) || (!rt->panes_horizontal && alloc->width != prev_size)) { //this is not just some gtk housekeeping printd (DEBUG, "_e2_window_filepanes_adjusted_cb (widget:,alloc:%d-%d,rt:_)", alloc->width, alloc->height); prev_size = (rt->panes_horizontal) ? alloc->height : alloc->width; rt->panes_paned_ratio = _e2_window_get_pos (rt->panes_paned); // printd (DEBUG, "1ratio: %f", rt->panes_paned_ratio); if (rt->panes_paned_ratio > 0.001 && rt->panes_paned_ratio < 0.999) rt->panes_paned_ratio_last = rt->panes_paned_ratio; } } /** @brief after a file pane is mapped or unmapped, ensure the correct toggle button is visible in the other pane's toolbar This fixes the toggle button when the panes divider is dragged @param widget ptr to scrolled window for the pane that has just been mapped @param state TRUE for map signal, FALSE for unmap @return */ static void _e2_window_map_pane_cb (GtkWidget *widget, gpointer state) { E2_ToggleType num = (widget == app.pane1.pane_sw) ? E2_TOGGLE_PANE2FULL : E2_TOGGLE_PANE1FULL; if (e2_toolbar_toggle_button_get_state (toggles_array [num]) == GPOINTER_TO_INT (state)) e2_toolbar_button_toggle (toggles_array [num]); } /** @brief callback for signal emitted when output pane size is allocated This happens when output content change varies the size of @a widget, and also when the output-paned divider is moved @param widget UNUSED the resized widget, app.outbook @param alloc the allocation for @a widget @param rt pointer to window data struct @return TRUE, always */ static void _e2_window_outputpane_adjusted_cb (GtkWidget *widget, GtkAllocation *alloc, E2_WindowRuntime *rt) { static gint prev_height = -1; if (alloc->height != prev_height) { //this is not just more output being displayed printd (DEBUG, "_e2_window_outputpane_adjusted_cb (widget:,alloc:%d, %d, %d-%d,rt:_)", alloc->x, alloc->y, alloc->width, alloc->height); prev_height = alloc->height; rt->output_paned_ratio = _e2_window_get_pos (app.window.output_paned); // printd (DEBUG, "2ratio: %f", rt->output_paned_ratio); //remember visibility state app.output.visible = (rt->output_paned_ratio != 1.0); if (rt->output_paned_ratio > 0.001 && rt->output_paned_ratio < 0.999) rt->output_paned_ratio_last = rt->output_paned_ratio; //make sure the correct toggle btn is shown gboolean truenow = e2_toolbar_toggle_button_get_state (toggles_array [E2_TOGGLE_OUTPUTFULL]); if ((!truenow && rt->output_paned_ratio < 0.001) ||(truenow && rt->output_paned_ratio >= 0.001)) e2_toolbar_button_toggle (toggles_array [E2_TOGGLE_OUTPUTFULL]); } } /** @brief after the output pane is mapped or unmapped, ensure the correct toggle button is visible in the commandbar This fixes the toggle button when the panes divider is dragged @param widget ptr to output pane scrolled window that has just been mapped @param state TRUE when mapped, FALSE when unmapped @return */ static void _e2_window_map_output_cb (GtkWidget *widget, gpointer state) { // printd (DEBUG, "_e2_window_map_output_cb (widget:,state:%s)", state ? "true" : "false"); //use toolbar runtime and its .option because that string is not translated //make sure the toggle for the other pane is not 'split' form if (e2_toolbar_toggle_button_get_state (toggles_array [E2_TOGGLE_OUTPUTSHADE]) == GPOINTER_TO_INT (state)) e2_toolbar_button_toggle (toggles_array [E2_TOGGLE_OUTPUTSHADE]); } /** @brief show output pane, at its previous size This is a callback for ouptput pane "focus-in-event", and also used generally Runs output adjust ratio action Downstream expects BGL to be on/closed @param widget UNUSED the newly-focused entry widget or NULL @param data UNUSED @return FALSE to propagate the event to other handlers */ gboolean e2_window_output_show (GtkWidget *widget, gpointer data) { if (!app.output.visible) { _e2_window_adjust_panes_ratio (GTK_PANED (app.window.output_paned), &app.window.output_paned_pos, &app.window.output_paned_ratio_last, "*"); } return FALSE; } /** @brief hide output pane This is a callback for command-line "focus-out-event", and also used generally Runs output adjust ratio action Downstream expects BGL to be on/closed @param widget UNUSED newly-departed widget or NULL @param event UNUSED pointer to event data struct @param user_data UNUSED data specified when callback was connected @return FALSE to propagate the event to other handlers */ gboolean e2_window_output_hide (GtkWidget *widget, GdkEventFocus *event, gpointer user_data) { if (app.output.visible) { _e2_window_adjust_panes_ratio (GTK_PANED (app.window.output_paned), &app.window.output_paned_pos, &app.window.output_paned_ratio_last, "1"); } gtk_widget_grab_focus (curr_view->treeview); return FALSE; } /** @brief callback for "window-state-event" signal emitted when the main window changes @param widget the main-window object @param event the GdkEventWindowState which triggered this signal @param user_data UNUSED data specified when the signal handler was connected @return FALSE always, so event propogates */ static gboolean _e2_window_change_cb (GtkWidget *widget, GdkEventWindowState *event, gpointer user_data) { //GdkWindowState event->changed_mask; //GdkWindowState event->new_window_state if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) { //toggling fullscreen on or off will grab the "unfull" size each time real_width = app.main_window->allocation.width; real_height = app.main_window->allocation.height; printd (DEBUG, "window-change cb main window size: W %d H %d", real_width, real_height); } app.mainwindow_state = event->new_window_state; return FALSE; } /** @brief callback for signal emitted when the main window is first shown paned dividers' positions are 'crudely' set when those widgets are created, but now all things are setup properly, the sizes are fine-tuned, here Pane resize etc callbacks are then connected, so that we don't get unnecessary callbacks during session setup After all is stable, the toolbars space handling arrangements are initialized, again, to avoid unnecessary callbacks @param window UNUSED, the window widget being shown @param rt pointer to window data struct @return TRUE, always */ static void _e2_window_show_cb (GtkWidget *window, E2_WindowRuntime *rt) { printd (DEBUG, "show main window cb"); //initialize window flags app.mainwindow_state = gdk_window_get_state (app.main_window->window); //notice future window-state changes g_signal_connect (G_OBJECT(app.main_window), "window-state-event", G_CALLBACK(_e2_window_change_cb), NULL); gtk_paned_set_position (GTK_PANED (rt->panes_paned), GTK_PANED (rt->panes_paned)->max_position * rt->panes_paned_ratio); gtk_paned_set_position (GTK_PANED (app.window.output_paned), GTK_PANED (app.window.output_paned)->max_position * rt->output_paned_ratio); rt->panes_paned_pos = gtk_paned_get_position (GTK_PANED (rt->panes_paned)); rt->output_paned_pos = gtk_paned_get_position (GTK_PANED (app.window.output_paned)); //ensure inter-list divider-position is logged when divider is moved g_signal_connect (G_OBJECT (app.pane1.outer_box), "size-allocate", G_CALLBACK (_e2_window_filepanes_adjusted_cb), rt); //ensure correct toggle button is displayed when a pane divider is dragged g_signal_connect (G_OBJECT (app.pane1.pane_sw), "map", G_CALLBACK (_e2_window_map_pane_cb), GINT_TO_POINTER (TRUE)); g_signal_connect (G_OBJECT (app.pane1.pane_sw), "unmap", G_CALLBACK (_e2_window_map_pane_cb), GINT_TO_POINTER (FALSE)); g_signal_connect (G_OBJECT (app.pane2.pane_sw), "map", G_CALLBACK (_e2_window_map_pane_cb), GINT_TO_POINTER (TRUE)); g_signal_connect (G_OBJECT (app.pane2.pane_sw), "unmap", G_CALLBACK (_e2_window_map_pane_cb), GINT_TO_POINTER (FALSE)); //ensure inter-pane divider-position is logged when divider is moved g_signal_connect (G_OBJECT (app.outbook), "size-allocate", G_CALLBACK (_e2_window_outputpane_adjusted_cb), rt); g_signal_connect (G_OBJECT (app.outbook), "map", G_CALLBACK (_e2_window_map_output_cb), GINT_TO_POINTER (TRUE)); g_signal_connect (G_OBJECT (app.outbook), "unmap", G_CALLBACK (_e2_window_map_output_cb), GINT_TO_POINTER (FALSE)); gtk_notebook_set_current_page (GTK_NOTEBOOK (app.outbook), 0); WAIT_FOR_EVENTS; //faster window completion if we hide buttons now, // if need be (ie dont wait till main loop) //now that window is established, we are ready to initialise all toolbars' overflow handling e2_toolbar_initialise_space_handler (NULL); } /** @brief callback for main window "destroy-event", "delete-event" signals @param widget UNUSED the window button which was activated @param event UNUSED pointer to event data @param forced pointerised gboolean, NULL if the user is permitted to choose to keep the window open @return TRUE if the user cancelled the close, or else it does not return at all */ static gboolean _e2_window_gone_cb (GtkWidget *widget, GdkEvent *event, gpointer forced) { e2_main_closedown (GPOINTER_TO_INT (forced), TRUE, TRUE); return TRUE; } /* * @brief callback for main window "destroy" signal @param object UNUSED the window object @param user_data UNUSED @return */ /*static void _e2_window_destroy_cb (GtkObject *object, gpointer user_data) { printd (DEBUG, "main window \"destroy\" signal callback"); e2_main_closedown (TRUE, TRUE, TRUE); } */ #ifdef E2_COMPOSIT /* * @brief callback for "expose-event" signal on @a widget This is called when we need to draw the window's contents. The X server sends an expose event when the window becomes visible on screen, meaning we need to draw the window's contents. On a composited desktop expose is normally only sent when the window is put on the screen. (On a non-composited desktop it can be sent whenever the window is uncovered by another.) @param widget window widget @param event pointer to event data struct @param data pointerised opacity level, 0 (transparent) to 100 (opaque) or -1 to use config default @return FALSE always, to propogate the event to other handlers */ /*static gboolean _e2_window_expose_cb (GtkWidget *widget, GdkEventExpose *event, gpointer data) { cairo_t *cr = gdk_cairo_create (widget->window); if (cr == NULL) return FALSE; cairo_rectangle (cr, event->area.x, event->area.y, event->area.width, event->area.height); cairo_clip (cr); //draw the background gint level = (GPOINTER_TO_INT (data) == -1) ? e2_option_int_get ("window-bleed"): GPOINTER_TO_INT (data); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); / * cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.0); //fully transparent cairo_set_source_rgba (cr, 1.0, 1.0, 1.0); //opaque white or partially transparent cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, (gfloat) level / 100.0); //black transparent as wanted //CHECKME discover destktop background "general" shade and/or current window source //and set background accordingly ? if () cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.25); else //light desktop background cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.05); cairo_set_source_rgba (cr, R, G, B, A); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); cairo_paint (cr); * / cairo_paint_with_alpha (cr, (gfloat) level / 100.0); cairo_destroy (cr); return FALSE; } */ /* * @brief callback for "screen-changed" signal on @a widget GTK supports migration of running applications between X servers, which might not support the same features (and in partucular, an alpha channel). This signal arrives when the display on which @a widget is drawn has changed. So we need to check whether it's still ok for transparent windows, and respond accordingly. @param widget the widget for the window whose screen changed, or whose opacity is being changed @param old_screen pointer to screen before the change @param data pointerised opacity level, 0 (transparent) to 100 (opaque) or -1 to use config default @return */ /*static void _e2_window_screen_changed_cb (GtkWidget *widget, GdkScreen *old_screen, gpointer data) { GdkScreen *new_screen; GdkColormap *colormap; //#ifdef USE_GTK2_12 // GdkDisplay *display = ; what for old screen ?? // if gdk_display_supports_composite (display)) //#else #if defined (USE_GTK2_10) if (gdk_screen_is_composited (old_screen)) #else //def USE_GTK2_8 //to check if the display supports alpha channels, check the alpha-colormap colormap = gdk_screen_get_rgba_colormap (old_screen); if (colormap != NULL) #endif { //former screen allowed compositing #ifdef USE_GTK2_12 if (!gtk_widget_is_composited (widget)) #elif defined (USE_GTK2_10) new_screen = gtk_widget_get_screen (widget); if (!gdk_screen_is_composited (new_screen)) #else //def USE_GTK2_8 new_screen = gtk_widget_get_screen (widget); colormap = gdk_screen_get_rgba_colormap (new_screen); if (colormap == NULL) //this test not entirely sufficient (need relevant compositor and WM) #endif { //but this one doesn't, make some changes #ifdef USE_GTK2_12 / *CHECKME GtkWindowType wtype; g_object_get (G_OBJECT (widget), "type", &wtype, NULL); if (wtype == GTK_WINDOW_TOPLEVEL) gdk_window_set_opacity (widget->window, gdouble opacity); else //window, FALSE); #endif g_signal_handlers_disconnect_by_func (G_OBJECT (widget), _e2_window_expose_cb, NULL); gtk_widget_set_app_paintable (widget, FALSE); #ifdef USE_GTK2_12 new_screen = gtk_widget_get_screen (widget);; #endif colormap = gdk_screen_get_rgb_colormap (new_screen); gtk_widget_set_colormap (widget, colormap); if (GTK_WIDGET_VISIBLE (widget)) { //trigger a window repaint GdkRegion *region = gdk_region_rectangle (&widget->allocation); gdk_window_invalidate_region (widget->window, region, TRUE); gdk_region_destroy (region); //CHECKME } } else //old and new screens both support compositing if (new_screen == old_screen) //setting or changing opacity level, not a real callback { //#ifdef USE_GTK2_10 //#else //2.8 //FIXME do much the same as below //#endif goto setup; } } else //the old screen was not composited #ifdef USE_GTK2_12 if (gtk_widget_is_composited (widget)) #elif defined (USE_GTK2_10) { new_screen = gtk_widget_get_screen (widget); if (gdk_screen_is_composited (new_screen)) #else { //but the new one is, so setup for that new_screen = gtk_widget_get_screen (widget); colormap = gdk_screen_get_rgba_colormap (new_screen); if (colormap != NULL) //not entirely sufficient #endif { setup: //FIXME THIS AND/OR THE EXPOSE CB DO NOT WORK PROPERLY #ifdef USE_GTK2_12 GtkWindowType wtype; g_object_get (G_OBJECT (widget), "type", &wtype, NULL); if (wtype == GTK_WINDOW_TOPLEVEL) gdk_window_set_opacity (widget->window, gdouble opacity); else //window, TRUE); #endif #ifdef USE_GTK2_10 colormap = gdk_screen_get_rgba_colormap (new_screen); #endif //now we have a colormap appropriate for the screen, use it gtk_widget_set_colormap (widget, colormap); //we wil draw the background gtk_widget_set_app_paintable (widget, TRUE); //and this is where we'll do so //CHECKME data value g_signal_connect (G_OBJECT (widget), "expose-event", G_CALLBACK (_e2_window_expose_cb), data); if (GTK_WIDGET_VISIBLE (widget)) { //trigger a window repaint GdkRegion *region = gdk_region_rectangle (&widget->allocation); gdk_window_invalidate_region (widget->window, region, TRUE); gdk_region_destroy (region); //CHECKME } } #ifndef USE_GTK2_12 } #endif } */ #endif //def E2_COMPOSIT /*******************/ /***** actions *****/ /*******************/ /** @brief toggle between horizontal and vertical file panes @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_window_toggle_panes_direction (gpointer from, E2_ActionRuntime *art) { e2_option_bool_toggle ("panes-horizontal"); e2_window_recreate (&app.window); //FIXME Q this return TRUE; } /** @brief change the position of the filepanes separator This is a wrapper for _e2_window_adjust_panes_ratio () Downstream expects BGL to be on/closed @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_window_adjust_pane_ratio_action (gpointer from, E2_ActionRuntime *art) { gchar *arg = (gchar *) art->data; _e2_window_adjust_panes_ratio (GTK_PANED (app.window.panes_paned), &app.window.panes_paned_pos, &app.window.panes_paned_ratio_last, arg); return TRUE; } /** @brief respond to pane 1 full-width toggle button The visible toggle button is swapped, and the panes-ajust fn is called with either "1" or "*" Expects BGL closed @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_window_toggle_full_pane1 (gpointer from, E2_ActionRuntime *art) { gchar *arg = (e2_toolbar_button_toggle (toggles_array [E2_TOGGLE_PANE1FULL])) ? "1" : "*"; //no translation _e2_window_adjust_panes_ratio (GTK_PANED (app.window.panes_paned), &app.window.panes_paned_pos, &app.window.panes_paned_ratio_last, arg); return TRUE; } /** @brief respond to pane2 full-width toggle button The visible toggle button is swapped, and the panes-ajust fn is called with either "0" or "*" Expects BGL to be on/closed @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_window_toggle_full_pane2 (gpointer from, E2_ActionRuntime *art) { gchar *arg = (e2_toolbar_button_toggle (toggles_array [E2_TOGGLE_PANE2FULL])) ? "0" : "*"; //no translation _e2_window_adjust_panes_ratio (GTK_PANED (app.window.panes_paned), &app.window.panes_paned_pos, &app.window.panes_paned_ratio_last, arg); return TRUE; } /** @brief respond to output pane full-window toggle button The visible toggle button is swapped or changed, and the panes-adjust fn is called with either "0" or "*" Expects BGL to be on/closed @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_window_toggle_full_output (gpointer from, E2_ActionRuntime *art) { gboolean next_state = e2_toolbar_button_toggle (toggles_array [E2_TOGGLE_OUTPUTFULL]); gchar *arg = (next_state) ? "0" : "*"; if (!next_state) e2_output_get_bottom_iter (&app.tab); //get current bottom position _e2_window_adjust_panes_ratio (GTK_PANED (app.window.output_paned), &app.window.output_paned_pos, &app.window.output_paned_ratio_last, arg); if (!next_state) { //cancelling full-window reverts to "last" split ratio, never to hidden //if appropriate, adjust scroll position to show the stuff at the bottom of the former view e2_output_scroll_to_bottom (&app.tab); //get back to the bottom portion /* E2_OutputTabRuntime *trt = &app.tab; GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (trt->scroll)); gdouble value = gtk_adjustment_get_value (vadj); if (value >= (vadj->upper - vadj->page_size)) { //we're showing the end of the text value += vadj->page_increment; //CHECKME find text actually at top of pane ? gtk_adjustment_set_value (vadj, value); } */ //make sure the other toggle btn is correct if (app.window.output_paned_ratio_last < 0.999) e2_toolbar_toggle_button_set_state (toggles_array [E2_TOGGLE_OUTPUTSHADE], FALSE); } return TRUE; } /** @brief respond to output pane visible toggle button The visible toggle button is swapped, and the panes-ajust fn is called with either "1" or "*" Expects BGL to be on/closed @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_window_toggle_visible_output (gpointer from, E2_ActionRuntime *art) { gchar *arg = (e2_toolbar_button_toggle (toggles_array [E2_TOGGLE_OUTPUTSHADE])) ? "1" : "*"; //no translation _e2_window_adjust_panes_ratio (GTK_PANED (app.window.output_paned), &app.window.output_paned_pos, &app.window.output_paned_ratio_last, arg); return TRUE; } /** @brief implement adjust-output-ratio action This is a wrapper for _e2_window_toggle etc The visible toggle button is swapped, and the panes-ajust fn is called with "*" ",1" or ",0" Expects BGL to be on/closed @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if the argument string is valid */ static gboolean _e2_window_adjust_output_ratio_action (gpointer from, E2_ActionRuntime *art) { gchar *arg = (gchar *) art->data; gchar *newarg; //check for "1" or "100%" first if (strchr (arg, '1') != NULL //always ascii, no translation && app.window.output_paned_ratio < 1.0) newarg = "1"; //no translation else if (strchr (arg, '0') != NULL && app.window.output_paned_ratio > 0) newarg = "0"; //no translation else if (strchr (arg, '*') != NULL) newarg = "*"; //no translation else newarg = NULL; if (newarg != NULL) { gboolean shade = (g_str_equal (newarg,"*") && app.window.output_paned_ratio < 0.01); if (shade) e2_output_get_bottom_iter (&app.tab); //get current bottom position _e2_window_adjust_panes_ratio (GTK_PANED (app.window.output_paned), &app.window.output_paned_pos, &app.window.output_paned_ratio_last, newarg); if (shade) e2_output_scroll_to_bottom (&app.tab); //get back to the bottom portion of the former view return TRUE; } return FALSE; } /** @brief window-fullscreen toggle action @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_window_toggle_full_screen (gpointer from, E2_ActionRuntime *art) { if (app.mainwindow_state & GDK_WINDOW_STATE_FULLSCREEN) gtk_window_unfullscreen (GTK_WINDOW(app.main_window)); else gtk_window_fullscreen (GTK_WINDOW(app.main_window)); return TRUE; } /******************/ /***** public *****/ /******************/ #ifdef E2_COMPOSIT /** @brief set or reset translucency of top-level window @a window Levels < 10 are treated as 0 and > 95 is ignored Expects BGL on/active @param window the widget for the window to be processed @param level opacity %, 50 (faint) to 100 (opaque), or -1 (config default level) @return */ void e2_window_set_opacity (GtkWidget *window, gint level) { /* GdkScreen *screen; #ifndef USE_GTK2_10 GdkColormap *colormap; #endif */ gint deflevel = e2_option_int_get ("window-opacity"); //50=faint...100=opaque if (deflevel < 100) //compositing is active { if (level < 0) level = deflevel; //non-top-level windows more opaque than the default setting GtkWindowType wtype; g_object_get (G_OBJECT (window), "type", &wtype, NULL); if (wtype != GTK_WINDOW_TOPLEVEL) level = MAX (deflevel + 5, level); if (level < 30) //ignore unusably-invisible levels level = 30; else if (level > 96) //ignore nearly-opaque or > 100 levels level = 100; #ifdef USE_GTK2_12 gtk_window_set_opacity (GTK_WINDOW (window), (gdouble) level / 100.0); #endif /* FIXME //turn off translucence if it was on, or turn on or adjust translucence #ifdef USE_GTK2_12 if (gtk_widget_is_composited (window)) #elif defined (USE_GTK2_10) screen = gtk_widget_get_screen (window); if (gdk_screen_is_composited (screen)) #else screen = gtk_widget_get_screen (window); colormap = gdk_screen_get_rgba_colormap (screen); if (colormap != NULL) //not entirely sufficient #endif g_signal_connect (G_OBJECT (window), "screen-changed", G_CALLBACK (_e2_window_screen_changed_cb), GINT_TO_POINTER (level)); else g_signal_handlers_disconnect_matched (G_OBJECT (window), G_SIGNAL_MATCH_FUNC, 0, 0, NULL, _e2_window_screen_changed_cb, NULL); #ifdef USE_GTK2_12 screen = gtk_widget_get_screen (window); #endif //FIXME with same screen, this does not change settings! //AND NOT CORRECT, even if it did change settings _e2_window_screen_changed_cb (window, screen, GINT_TO_POINTER (level)); } else //compositing option not in effect { //check for tranlucent on now, turn it off if so #ifdef USE_GTK2_12 if (!gtk_widget_is_composited (window)) #elif defined (USE_GTK2_10) screen = gtk_widget_get_screen (window); if (!gdk_screen_is_composited (screen)) #else screen = gtk_widget_get_screen (window); colormap = gdk_screen_get_rgba_colormap (screen); if (colormap == NULL) //not entirely sufficient #endif { //turn off translucence g_signal_handlers_disconnect_matched (G_OBJECT (window), G_SIGNAL_MATCH_FUNC, 0, 0, NULL, _e2_window_screen_changed_cb, NULL); #ifdef USE_GTK2_12 screen = gtk_widget_get_screen (window); #endif _e2_window_screen_changed_cb (window, screen, GINT_TO_POINTER (100)); } */ } } #endif /** @brief setup custom window title This probably needs BGL to be closed upon arrival @param widget dialog or window widget @param title custom string to append to window titlebar, or NULL @return */ void e2_window_set_title (GtkWidget *wid, const gchar *title) { if (title != NULL) { gchar *tt = e2_utils_strcat (PROGNAME ": ", title); gtk_window_set_title (GTK_WINDOW (wid), tt); //needs BGL ? g_free (tt); } else gtk_window_set_title (GTK_WINDOW (wid), PROGNAME); } /** @brief set app window cursor to @a type Expects BGL on/active @param type enumerator of desired cursor type @return */ void e2_window_set_cursor (GdkCursorType type) { GdkCursor *cursor = gdk_cursor_new (type); gdk_window_set_cursor (app.main_window->window, cursor); gdk_cursor_unref (cursor); gdk_flush (); } /* * @brief set output pane size UNUSED for internal use, not a toolbar toggle Downstream expects BGL to be on/closed @param rt window rt data structure @param arg string indicating the desired state of the pane e.g "*" or "0" @return */ /* void e2_window_adjust_output_pane_ratio (E2_WindowRuntime *rt, gchar *arg) { _e2_window_adjust_panes_ratio (GTK_PANED (rt->output_paned), &rt->output_paned_pos, &rt->output_paned_ratio_last, arg); }*/ guint last_selected_rows = -1; //-1 ensures it always reports at session start static guint last_total_rows; /** @brief update status line message about selected and total items counts This is called periodically by the status line timer function, when that's not suspended Any ".." entry is filtered out from the count Expects BGL off/open @param userdata UNUSED @return TRUE, always (so the timer is never cancelled) */ gboolean e2_window_update_status_bar (gpointer userdata) { //CHECKME make this happen after specific events (refresh, refilter, cd, swap panes) //not on a timer #ifdef E2_STATUS_BLOCK if (app.status_working) { printd (DEBUG, "update status bar BLOCKED"); return TRUE; } #endif // printd (DEBUG, "e2_window_update_status_bar"); #ifdef E2_STATUS_BLOCK app.status_working = TRUE; #endif gint selected_rows, total_rows; GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (curr_view->treeview)); GtkTreeSelection *sel = curr_view->selection; selected_rows = gtk_tree_selection_count_selected_rows (sel); total_rows = gtk_tree_model_iter_n_children (model, NULL); if ((selected_rows != last_selected_rows) || (total_rows != last_total_rows)) { if (e2_option_bool_get ("show-updir-entry")) { //make status-line counts ignore the updir entry //the treeview sorting functions always put any "../" entry at the //start of the list, so if such entry is selected, it will be the 1st GtkTreeIter iter; if (gtk_tree_model_get_iter_first (model, &iter)) { //really, there will always be an iter total_rows--; if (gtk_tree_selection_iter_is_selected (sel, &iter)) selected_rows--; } } last_selected_rows = selected_rows; last_total_rows = total_rows; gchar *status_text1 = (curr_view->total_items > total_rows) ? g_strdup_printf (_("displayed & %d concealed "), curr_view->total_items-total_rows) : "" ; gchar *status_text2 = g_strdup_printf ( _("%s%d selected item(s) of %d %sin %s"), "::: ", #ifdef E2_VFSTMP //FIXME tip when not mounted local #else selected_rows, total_rows, status_text1, curr_view->dir); #endif //generally remove trailing / (ascii) #ifdef E2_VFSTMP //FIXME tip when not mounted local #else if (*(curr_view->dir + sizeof (gchar)) != '\0') //dir is a single char when at root dir #endif { gint len = strlen (status_text2); *(status_text2 + len - sizeof (gchar)) = '\0'; } gdk_threads_enter (); gtk_label_set_text (GTK_LABEL (app.status_bar_label2), status_text2); gdk_threads_leave (); if (*status_text1 != '\0') g_free (status_text1); g_free (status_text2); } #ifdef E2_STATUS_BLOCK app.status_working = FALSE; #endif // printd (DEBUG, "e2_window_update_status_bar ends"); return TRUE; //never turn this off } #ifdef E2_STATUS_REF gint statref_count = 0; #endif gint lastinterval = E2_STATUSREPORT_INTERVAL; #ifndef E2_STATUS_DEMAND /** @brief turn on status bar 'selected files' message refreshing @param interval no of milliseconds between updates, or -1 for default @return */ void e2_window_enable_status_update (gint interval) { if (interval < 0) interval = lastinterval; else lastinterval = interval; #ifdef E2_STATUS_REF statref_count--; if (statref_count == 0) { #endif #ifdef E2_STATUS_BLOCK app.status_working = FALSE; #endif app.timers[STATUS_T] = g_timeout_add (interval, e2_window_update_status_bar, NULL); #ifdef E2_STATUS_REF } else if (statref_count < 0) statref_count = 0; #endif } /** @brief turn off status bar 'selected files' message refreshing @return */ void e2_window_disable_status_update (void) { #ifdef E2_STATUS_REF statref_count++; if (statref_count == 1) { // printd (BEBUG, "block update status bar cb"); #endif if (app.timers[STATUS_T] > 0) { g_source_remove (app.timers[STATUS_T]); app.timers[STATUS_T] = 0; #ifdef E2_STATUS_REF } #endif } } #endif //ndef E2_STATUS_DEMAND /* * @brief display status bar custom message The specified string is added to the end of the hbox, but only displayed if it is the first message in the box This allows multiple messages to be queued FIXME the messages may be cancelled in any order if threads are in force ... @param message string to be displayed, may have pango markup @return */ /* UNUSED void e2_window_show_status_message (gchar *message) { GtkWidget *label = gtk_label_new (NULL); gtk_label_set_markup (GTK_LABEL (label), message); gtk_box_pack_start (GTK_BOX (app.status_bar_box3), label, FALSE, FALSE, 0); GList *children = GTK_BOX (app.status_bar_box3)->children; if (g_list_length (children) == 1) gtk_widget_show_all (app.status_bar_box3); } */ /* * @brief cancel display of status bar custom message @return */ //FIXME the logic here is crap - needs to be thread-safe /* UNUSED void e2_window_remove_status_message (void) { GList *children = GTK_BOX (app.status_bar_box3)->children; gint num = g_list_length (children); if (num > 1) { //show the next label in the queue (stupid !!) GtkWidget *label = ((GtkBoxChild *) children->next->data)->widget; gtk_widget_show_all (label); } if (num ==1) gtk_widget_hide (app.status_bar_box3); if (num > 0) { GtkWidget *label = ((GtkBoxChild *) children->data)->widget; gtk_container_remove (GTK_CONTAINER (app.status_bar_box3), label); } } */ /** @brief show statusline message @a labeltext Any existing message is simply replaced Gtk's BGL is expected to be open/off @param message utf-8 string, which may include pango markup @return */ void e2_window_show_status_message (gchar *message) { GList *children = gtk_container_get_children (GTK_CONTAINER (app.status_bar_box3)); if (children != NULL) { gdk_threads_enter (); //maybe the message is already displayed gtk_widget_destroy (children->data); //FIXME make this a friendly Q gdk_threads_leave (); g_list_free (children); } // GtkWidget *label = gtk_label_new (message); GtkWidget *label = gtk_label_new (NULL); gchar *public = g_markup_escape_text (message, -1); gtk_label_set_markup (GTK_LABEL (label), public); g_free (public); gdk_threads_enter (); // gtk_box_pack_start (GTK_BOX (app.status_bar_box3), label, FALSE, FALSE, 0); gtk_container_add (GTK_CONTAINER (app.status_bar_box3), label); gtk_widget_show_all (app.status_bar_box3); gdk_threads_leave (); } /** @brief idle function to clear statusline message @param data UNUSED data specified when idle was setup @return FALSE always */ static gboolean _e2_window_unadvise (gpointer data) { gdk_threads_enter (); gtk_widget_hide (app.status_bar_box3); gdk_threads_leave (); // GList *children = GTK_BOX (app.status_bar_box3)->children; // GtkWidget *label = ((GtkBoxChild *) children->data)->widget; // gtk_container_remove (GTK_CONTAINER (app.status_bar_box3), label); GList *children = gtk_container_get_children (GTK_CONTAINER (app.status_bar_box3)); if (children != NULL) { gtk_widget_destroy (children->data); //FIXME make this a more-friendly Q g_list_free (children); } return FALSE; } /** @brief remove statusline message In case this change is done at a busy time for X/gdk, it's done asynchronously @return */ void e2_window_clear_status_message (void) { g_idle_add ((GSourceFunc) _e2_window_unadvise, NULL); } /** @brief create main window This is called only at session-start, from main () @param rt ptr to window data struct @return */ void e2_window_create (E2_WindowRuntime *rt) { _e2_window_set_icon (); //setup main window app.main_window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_role (GTK_WINDOW (app.main_window), "main"); gtk_window_set_title (GTK_WINDOW (app.main_window), PROGNAME); gtk_window_set_wmclass (GTK_WINDOW (app.main_window), "main", BINNAME); gtk_widget_set_size_request (app.main_window, 0, 0); //window as small as possible //default size 640 x 480 //FIXME these are wrong when window is maximised e2_cache_int_register ("window-width", &app.main_window->allocation.width, 640); e2_cache_int_register ("window-height", &app.main_window->allocation.height, 480); gtk_window_set_default_size (GTK_WINDOW (app.main_window), app.main_window->allocation.width, app.main_window->allocation.height); gtk_window_set_resizable (GTK_WINDOW (app.main_window), TRUE); #ifdef E2_COMPOSIT e2_window_set_opacity (app.main_window, -1); #endif g_signal_connect (G_OBJECT (app.main_window), "show", G_CALLBACK (_e2_window_show_cb), rt); //arrange to clean up gracefully g_signal_connect (G_OBJECT (app.main_window), "delete-event", G_CALLBACK (_e2_window_gone_cb), NULL); //NULL data = optional cancellation of window-close /*FIXME if the system needs shutdown-feedback. support different processes for user- and system-initiated shutdowns? g_signal_connect (G_OBJECT (app.main_window), ??, G_CALLBACK (system_shutdown), NULL); */ //trap signals that might (but probably don't) get issued when a //session-manager initiates a shutdown // g_signal_connect (GTK_OBJECT (app.main_window), "destroy", // G_CALLBACK (_e2_window_destroy_cb), NULL); g_signal_connect (G_OBJECT (app.main_window), "destroy-event", G_CALLBACK (_e2_window_gone_cb), (gpointer)TRUE); app.hbox_main = gtk_hbox_new (FALSE, 0); app.vbox_main = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (app.main_window), app.hbox_main); gtk_box_pack_start_defaults (GTK_BOX (app.hbox_main), app.vbox_main); gtk_widget_show (app.hbox_main); gtk_widget_show (app.vbox_main); #ifdef E2_RAINBOW e2_option_color_filetypes_sync (); #endif //file panes e2_cache_double_register ("file-pane-ratio-last", &rt->panes_paned_ratio_last, 0.55); e2_cache_double_register ("file-pane-ratio", &rt->panes_paned_ratio, 0.55); _e2_window_create_pane_boxes (rt); //setup paned widget, with approximately 'correct' ratio //output pane e2_cache_double_register ("output-pane-ratio-last", &rt->output_paned_ratio_last, 0.85); e2_cache_double_register ("output-pane-ratio", &rt->output_paned_ratio, 0.85); e2_cache_int_register ("output-pane-tabs", &app.tabcount, 1); GtkWidget *wid = e2_output_initialise (); //set visibility state app.output.visible = (rt->output_paned_ratio != 1.0); rt->output_paned = gtk_vpaned_new (); GTK_WIDGET_UNSET_FLAGS (rt->output_paned, GTK_CAN_FOCUS); gtk_paned_pack1 (GTK_PANED (rt->output_paned), rt->panes_outer_box, TRUE, TRUE); gtk_paned_pack2 (GTK_PANED (rt->output_paned), wid, TRUE, TRUE); gtk_paned_set_position (GTK_PANED (rt->output_paned), app.main_window->allocation.height * rt->output_paned_ratio); rt->output_paned_pos = gtk_paned_get_position (GTK_PANED (app.window.output_paned)); gtk_widget_show (wid); gtk_widget_show (rt->output_paned); gtk_box_pack_start (GTK_BOX (app.vbox_main), rt->output_paned, TRUE, TRUE, 0); //do this registration here, rather that in the pane context, //as it covers both panes, and to avoid attempted re-registers //whenever the main window is re-created /* gint i; gint j; cols_data = NULL; E2_Cache *cache = e2_cache_list_register ("columns-data", &cols_data); //before writing cache, this will update list data cache->sync_func = e2_fileview_update_col_cachedata; if (cols_data == NULL) { //there was no cache found, so setup defaults for (j = 0; j < 2; j++) { for (i = 0; i < MAX_COLUMNS; i++) { stored_col_order[j][i] = i; col_width_store[j][i] = e2_all_columns[i].size; }} } else //column data found in cache { //get data from cached list (no data checks!!) //list format = pane1 { order, width, ...} pane2 {order, width, ... } GList *iterator = g_list_first (cols_data); for (j = 0; j < 2; j++) { for (i = 0; i < MAX_COLUMNS; i++) { stored_col_order[j][i] = atoi (iterator->data); iterator = iterator->next; col_width_store[j][i] = atoi (iterator->data); iterator = iterator->next; }} } */ //register columns data, with 2 panes sequential gint orders[] = {0,1,2,3,4,5,6,7,0,1,2,3,4,5,6,7}; e2_cache_array_register ("columns-order", MAX_COLUMNS*2, (gint *)stored_col_order, orders); gint widths [] = { e2_all_columns[0].size, e2_all_columns[1].size, e2_all_columns[2].size, e2_all_columns[3].size, e2_all_columns[4].size, e2_all_columns[5].size, e2_all_columns[6].size, e2_all_columns[7].size, e2_all_columns[0].size, e2_all_columns[1].size, e2_all_columns[2].size, e2_all_columns[3].size, e2_all_columns[4].size, e2_all_columns[5].size, e2_all_columns[6].size, e2_all_columns[7].size }; e2_cache_array_register ("columns-width", MAX_COLUMNS*2, (gint *)col_width_store, widths); curr_pane = &app.pane1; other_pane = &app.pane2; curr_view = app.pane1.view = &app.pane1_view; other_view = app.pane2.view = &app.pane2_view; e2_cache_int_register ("pane1-sort-column", &app.pane1_view.sort_column, FILENAME); e2_cache_int_register ("pane2-sort-column", &app.pane2_view.sort_column, FILENAME); e2_cache_int_register ("pane1-sort-direction", (gint *) &app.pane1_view.sort_order, GTK_SORT_ASCENDING); e2_cache_int_register ("pane2-sort-direction", (gint *) &app.pane2_view.sort_order, GTK_SORT_ASCENDING); e2_cache_bool_register ("pane1-show-hidden", &app.pane1_view.show_hidden, FALSE); e2_cache_bool_register ("pane2-show-hidden", &app.pane2_view.show_hidden, FALSE); //to avoid slow startup, any use of cached vfs data (including button state) //is not done here, but is deferred until an idle when main loop is first started //#if sizeof(time_t) == 8 //#define LONGTIME //#endif time_t _now; time (&_now); if (_now == (time_t) -1) _now = 0; e2_cache_bool_register ("pane1-filter-names", &app.pane1_view.name_filter.active, FALSE); e2_cache_str_register ("pane1-filter-nametype", &app.pane1_view.name_filter.patternptr, "*"); e2_cache_bool_register ("pane1-filter-nameinvert", &app.pane1_view.name_filter.invert_mask, FALSE); e2_cache_bool_register ("pane1-filter-namecase", &app.pane1_view.name_filter.case_sensitive, TRUE); e2_cache_bool_register ("pane1-filter-dates", &app.pane1_view.date_filter.active, FALSE); //#ifdef LONGTIME e2_cache_time_register ("pane1-filter-datebase", &app.pane1_view.date_filter.time, _now); //#else // e2_cache_int_register ("pane1-filter-datebase", (gint *) &app.pane1_view.date_filter.time, _now); //#endif e2_cache_int_register ("pane1-filter-daterel", (gint *) &app.pane1_view.date_filter.op, GT); e2_cache_int_register ("pane1-filter-datetype", (gint *)&app.pane1_view.date_filter.time_type, MTIME); e2_cache_bool_register ("pane1-filter-sizes", &app.pane1_view.size_filter.active, FALSE); e2_cache_int_register ("pane1-filter-sizebase", (gint *) &app.pane1_view.size_filter.size, 0); //size_t=guint e2_cache_int_register ("pane1-filter-sizerel", (gint *) &app.pane1_view.size_filter.op, GT); e2_cache_bool_register ("pane1-filter-dirs", &app.pane1_view.filter_directories, FALSE); e2_cache_bool_register ("pane2-filter-names", &app.pane2_view.name_filter.active, FALSE); e2_cache_str_register ("pane2-filter-nametype", &app.pane2_view.name_filter.patternptr, "*"); e2_cache_bool_register ("pane2-filter-nameinvert", &app.pane2_view.name_filter.invert_mask, FALSE); e2_cache_bool_register ("pane2-filter-namecase", &app.pane2_view.name_filter.case_sensitive, TRUE); e2_cache_bool_register ("pane2-filter-dates", &app.pane2_view.date_filter.active, FALSE); //#ifdef LONGTIME e2_cache_time_register ("pane2-filter-datebase", &app.pane2_view.date_filter.time, _now); //#else // e2_cache_int_register ("pane2-filter-datebase", (gint *) &app.pane2_view.date_filter.time, _now); //#endif e2_cache_int_register ("pane2-filter-daterel", (gint *) &app.pane2_view.date_filter.op, GT); e2_cache_int_register ("pane2-filter-datetype", (gint *) &app.pane2_view.date_filter.time_type, MTIME); e2_cache_bool_register ("pane2-filter-sizes", &app.pane2_view.size_filter.active, FALSE); e2_cache_int_register ("pane2-filter-sizebase", (gint *) &app.pane2_view.size_filter.size, 0); e2_cache_int_register ("pane2-filter-sizerel", (gint *) &app.pane2_view.size_filter.op, GT); e2_cache_bool_register ("pane2-filter-dirs", &app.pane2_view.filter_directories, FALSE); //create all bindings, then register _("general" //(must be done before file-pane contents are created) e2_keybinding_register (_C(17), app.main_window); //get working copies /* g_strlcpy (app.pane1_view.name_filter.pattern, app.pane1_view.name_filter.patternptr, sizeof (app.pane1_view.name_filter.pattern)); g_strlcpy (app.pane2_view.name_filter.pattern, app.pane2_view.name_filter.patternptr, sizeof (app.pane2_view.name_filter.pattern)); */ g_hook_list_init (&app.hook_pane_focus_changed, sizeof (GHook)); e2_pane_create (&app.pane1); e2_pane_create (&app.pane2); gtk_paned_pack1 (GTK_PANED (rt->panes_paned), app.pane1.outer_box, TRUE, TRUE); gtk_paned_pack2 (GTK_PANED (rt->panes_paned), app.pane2.outer_box, TRUE, TRUE); //show which pane (pane 1) is active e2_pane_flag_active (); //setup correct focus gtk_widget_grab_focus (curr_view->treeview); //other toolbars e2_toolbar_initialise (E2_BAR_TASK); e2_toolbar_create (&app.toolbar); e2_toolbar_initialise (E2_BAR_COMMAND); e2_toolbar_create (&app.commandbar); if (e2_option_bool_get_direct (app.commandbar.show)) //FIXME button could be on any bar { //show corrrect output pane toggle buttons according to cached pane ratio if (app.window.output_paned_ratio < 0.001) e2_toolbar_toggle_button_set_state (toggles_array [E2_TOGGLE_OUTPUTFULL], TRUE); else if (app.window.output_paned_ratio > 0.999) e2_toolbar_toggle_button_set_state (toggles_array [E2_TOGGLE_OUTPUTSHADE], TRUE); } //status bar // wid = gtk_table_new (1, 3, FALSE); wid = gtk_hbox_new (FALSE, E2_PADDING_SMALL); gtk_box_pack_start (GTK_BOX (app.vbox_main), wid, FALSE, FALSE, 0); gtk_widget_show (wid); //find current user name (can't use environment string ...) struct passwd *pw_buf; gint myuid = getuid (); GString *who_where = g_string_new (""); while ((pw_buf = getpwent ()) != NULL) if ((gint) pw_buf->pw_uid == myuid) break; if (pw_buf != NULL) g_string_assign (who_where, pw_buf->pw_name); //find current machine name const gchar *env = g_getenv ("HOSTNAME"); if (env != NULL) g_string_append_printf (who_where, " @ %s", env); //no translation GtkWidget *wid2 = gtk_label_new (who_where->str); g_string_free (who_where, TRUE); // gtk_misc_set_alignment (GTK_MISC (wid2), 0.0, 0.5); // gtk_table_attach (GTK_TABLE(wid), wid2, 0, 1, 0, 1, // GTK_SHRINK, GTK_SHRINK, // E2_PADDING_SMALL, 0); gtk_box_pack_start (GTK_BOX (wid), wid2, FALSE, FALSE, 10); gtk_widget_show (wid2); app.status_bar_label2 = gtk_label_new (" "); // gtk_misc_set_alignment (GTK_MISC (app.status_bar_label2), 0.5, 0.5); // gtk_box_pack_start (GTK_BOX (app.vbox_main), app.status_bar_label2, FALSE, TRUE, 0); // gtk_table_attach (GTK_TABLE(wid), app.status_bar_label2, 1, 2, 0, 1, // GTK_EXPAND, GTK_SHRINK, // E2_PADDING_SMALL, 0); gtk_box_pack_start (GTK_BOX (wid), app.status_bar_label2, FALSE, FALSE, 0); gtk_widget_show (app.status_bar_label2); //create hbox for any other status indicator eg for progress bars //not shown until needed app.status_bar_box3 = gtk_hbox_new (FALSE, E2_PADDING_SMALL); gtk_box_pack_end (GTK_BOX (wid), app.status_bar_box3, FALSE, TRUE, E2_PADDING_LARGE); // gtk_table_attach (GTK_TABLE(wid), app.status_bar_box3, 2, 3, 0, 1, // GTK_EXPAND, GTK_SHRINK, // E2_PADDING_SMALL, 0); } /** @brief re-create main window This is used after config dialog, or detection of a config file that is updated (e.g. by another instance of e2), or a change of file-panes direction @param rt ptr to window data struct @return */ void e2_window_recreate (E2_WindowRuntime *rt) { printd (DEBUG, "recreate main window"); //make sure current cols data are used for the window recreate e2_fileview_update_col_cachedata (); //save current scroll positions gint curr_xscroll, other_xscroll; gint curr_yscroll, other_yscroll; //FIXME this func always finds column 0 e2_fileview_get_scroll_data (curr_view, &curr_xscroll, &curr_yscroll); e2_fileview_get_scroll_data (other_view, &other_xscroll, &other_yscroll); GtkAdjustment *adj; gdouble curr_thumbx, curr_upperx, other_thumbx, other_upperx; adj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (curr_pane->pane_sw)); curr_thumbx = gtk_adjustment_get_value (adj); curr_upperx = adj->upper; //no fn for this adj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (other_pane->pane_sw)); other_thumbx = gtk_adjustment_get_value (adj); other_upperx = adj->upper; //no fn for this LISTS_LOCK gboolean busy = curr_view->listcontrols.cd_working || curr_view->listcontrols.refresh_working; LISTS_UNLOCK if (!busy) //no racing with in-progress re-list { //save current selections if (curr_view->selected_names != NULL) //setup to get rid of any old hash g_idle_add_full (G_PRIORITY_LOW, (GSourceFunc) e2_fileview_treehash_free, curr_view->selected_names, NULL); curr_view->selected_names = e2_fileview_log_selected_names (curr_view); } LISTS_LOCK busy = other_view->listcontrols.cd_working || other_view->listcontrols.refresh_working; LISTS_UNLOCK if (!busy) { if (other_view->selected_names != NULL) g_idle_add_full (G_PRIORITY_LOW, (GSourceFunc) e2_fileview_treehash_free, other_view->selected_names, NULL); other_view->selected_names = e2_fileview_log_selected_names (other_view); } //ensure all toolbar toggle buttons are recreated with their current state e2_toolbar_toggle_buttons_set_destroyed (NULL); //clear hooks if (app.pane1.hook_change_dir.is_setup) g_hook_list_clear (&app.pane1.hook_change_dir); if (app.pane2.hook_change_dir.is_setup) g_hook_list_clear (&app.pane2.hook_change_dir); //save current liststores so data can be reinstated for updating GtkListStore *curr_store = curr_view->store; g_object_ref (G_OBJECT (curr_store)); GtkListStore *other_store = other_view->store; g_object_ref (G_OBJECT (other_store)); //prevent any new refresh from starting during this rebuild e2_filelist_disable_refresh (); //wait until any in-progress refresh is completed //CHECKME do this at start of this func ? while (TRUE) { LISTS_LOCK busy = curr_view->listcontrols.cd_working || curr_view->listcontrols.refresh_working || other_view->listcontrols.cd_working || other_view->listcontrols.refresh_working; LISTS_UNLOCK if (!busy) break; usleep (50000); } //destroy the widgets //toolbar(s) may be outside rt->panes_outer_box, so destroy them independently gtk_widget_destroy (app.pane1.toolbar.toolbar_container); gtk_widget_destroy (app.pane2.toolbar.toolbar_container); gtk_widget_destroy (app.toolbar.toolbar_container); gtk_widget_destroy (app.commandbar.toolbar_container); gtk_widget_destroy (rt->panes_outer_box); #ifdef E2_COMPOSIT e2_window_set_opacity (app.main_window, -1); #endif e2_keybinding_register (_C(17), app.main_window); //_("general" _e2_window_create_pane_boxes (rt); e2_pane_create_part (&app.pane1); e2_pane_create_part (&app.pane2); e2_toolbar_create (&app.toolbar); e2_toolbar_create (&app.commandbar); gtk_paned_pack1 (GTK_PANED (rt->panes_paned), app.pane1.outer_box, TRUE, TRUE); gtk_paned_pack2 (GTK_PANED (rt->panes_paned), app.pane2.outer_box, TRUE, TRUE); gtk_paned_pack1(GTK_PANED (rt->output_paned), rt->panes_outer_box, TRUE, TRUE); //mark the active pane e2_pane_flag_active (); WAIT_FOR_EVENTS; //make sure pane parameters are set, before the cb _e2_window_show_cb (NULL, rt); e2_alias_sync (&app.aliases); //reinstate former liststore, so its data can be recovered gtk_tree_view_set_model (GTK_TREE_VIEW (curr_view->treeview), NULL); curr_view->store = curr_store; //FIXME block new filelist if currently busy // LISTS_LOCK curr_view->listcontrols.refreshtype = E2_RECREATE; // LISTS_UNLOCK e2_fileview_prepare_list (curr_view); //apply a rebuilt liststore //revert the vert scroll //needs to be scroll-to-position - maybe treeview is not //immediately ready to accept vertical adjustment change ? e2_fileview_scroll_to_position (curr_view, 0, curr_yscroll); //revert the horiz scroll too adj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (curr_pane->pane_sw)); adj->upper = curr_upperx; gtk_adjustment_set_value (adj, curr_thumbx); gtk_adjustment_changed (adj); //clear out any old selection // gtk_tree_selection_unselect_all (curr_view->selection); e2_fileview_reselect_names (curr_view); //try to reselect items, and at least clean the hash #ifdef E2_VFSTMP //FIXME dir when not mounted local #else // printd (DEBUG, "finished new selection for %s", view->dir); #endif gtk_tree_view_set_model (GTK_TREE_VIEW (other_view->treeview), NULL); other_view->store = other_store; //FIXME block new filelist if currently busy // LISTS_LOCK other_view->listcontrols.refreshtype = E2_RECREATE; // LISTS_UNLOCK e2_fileview_prepare_list (other_view); //apply a rebuilt liststore e2_fileview_scroll_to_position (other_view, 0, other_yscroll); adj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (other_pane->pane_sw)); adj->upper = other_upperx; gtk_adjustment_set_value (adj, other_thumbx); gtk_adjustment_changed (adj); //clear out any old selection // gtk_tree_selection_unselect_all (other_view->selection); e2_fileview_reselect_names (other_view); //try to reselect items, then clean the hash #ifdef E2_VFSTMP //FIXME dir when not mounted local #else // printd (DEBUG, "finished new selection for %s", view->dir); #endif //set output visibility flag app.output.visible = (rt->output_paned_ratio != 1.0); e2_output_update_style (); e2_filelist_enable_refresh (); gtk_widget_grab_focus (curr_view->treeview); } /** @brief register main-window-related actions @return */ void e2_window_actions_register (void) { //(name strings freed as part of the registration process) gchar *action_name = g_strconcat(_A(9),".",_A(27),NULL); //"output.adjust_ratio e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_window_adjust_output_ratio_action, &app.window, TRUE); action_name = g_strconcat(_A(13),".",_A(27),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_window_adjust_pane_ratio_action, &app.window, TRUE); action_name = g_strconcat(_A(15),".",_A(44),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_window_toggle_full_screen, NULL, FALSE); action_name = g_strconcat(_A(13),".",_A(70),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, e2_task_refresh, NULL, FALSE); action_name = g_strconcat(_A(13),".",_A(72),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, e2_filelist_disable_refresh_action, NULL, FALSE); action_name = g_strconcat(_A(13),".",_A(71),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, e2_filelist_enable_refresh_action, NULL, FALSE); action_name = g_strconcat(_A(13),".",_A(93),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, e2_task_sync_dirs, NULL, FALSE); action_name = g_strconcat(_A(13),".",_A(94),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_window_toggle_panes_direction, NULL, FALSE); action_name = g_strconcat(_A(10),".",_A(90),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, e2_pane_activate_other_action, NULL, FALSE); //these are "self-managed" toggle actions, with names already logged e2_action_register_simple (g_strdup (toggles_array[E2_TOGGLE_OUTPUTSHADE]), E2_ACTION_TYPE_ITEM, _e2_window_toggle_visible_output, NULL, FALSE); e2_action_register_simple (g_strdup (toggles_array[E2_TOGGLE_OUTPUTFULL]), E2_ACTION_TYPE_ITEM, _e2_window_toggle_full_output, NULL, FALSE); e2_action_register_simple (g_strdup (toggles_array[E2_TOGGLE_PANE1FULL]), E2_ACTION_TYPE_ITEM, _e2_window_toggle_full_pane1, NULL, FALSE); e2_action_register_simple (g_strdup (toggles_array[E2_TOGGLE_PANE2FULL]), E2_ACTION_TYPE_ITEM, _e2_window_toggle_full_pane2, NULL, FALSE); } emelfm2-0.4.1/src/e2_filelist.c0000600000175000017500000006633411007465057015164 0ustar cairocairo/* $Id: e2_filelist.c 856 2008-05-05 02:00:15Z tpgww $ Copyright (C) 2004-2008 tooar . This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/e2_filelist.c @brief directory content liststore functions This file contains functions related to creation, filling and emptying a liststore of directory content data, and fns for the associated model. Also filtering of store content, and other related utilties. */ #include "e2_filelist.h" #include #include #include #include #include #include #include "e2_option.h" #include "e2_task.h" #include "e2_dialog.h" #ifdef E2_SELTXT_RECOLOR extern GdkColor selectedtext; #endif #ifdef E2_FAM_DNOTIFY #include extern sigset_t dnotify_signal_set; #endif //disable-refresh counter for both filelists gint refresh_refcount; //disable-refresh counter for single filelists static gint refresh_active_refcount; static gint refresh_inactive_refcount; //refresh deferral counters static gint pane1repeats = 0; static gint pane2repeats = 0; //static gboolean case_sensitive; //local copy of option value, for faster access /** @brief determine whether pane defined by @a pane is the active one @param pane enumerator for pane to be checked @return TRUE if @a pane is the active one */ static gboolean _e2_filelist_check_active_pane (E2_ListChoice pane) { gboolean active; switch (pane) { case PANE1: active = (curr_view == &app.pane1_view); break; case PANE2: active = (curr_view == &app.pane2_view); break; case PANEINACTIVE: active = FALSE; break; default: active = TRUE; break; } return active; } /** @brief disable refreshing of a single pane filelist This is mainly intended to block changes of filesystem CWD when a process is working in a displayed directory @param pane enumerator for pane to be blocked @return */ void e2_filelist_disable_one_refresh (E2_ListChoice pane) { if (_e2_filelist_check_active_pane (pane)) { refresh_active_refcount++; if (refresh_active_refcount == 1) { LISTS_LOCK curr_view->listcontrols.norefresh = TRUE; LISTS_UNLOCK } } else { refresh_inactive_refcount++; if (refresh_inactive_refcount == 1) { LISTS_LOCK other_view->listcontrols.norefresh = TRUE; LISTS_UNLOCK } } } /** @brief enable refreshing of a single pane filelist @param pane enumerator for pane to be unblocked @return */ void e2_filelist_enable_one_refresh (E2_ListChoice pane) { if (_e2_filelist_check_active_pane (pane)) { refresh_active_refcount--; if (refresh_active_refcount == 0 && refresh_refcount == 0) { LISTS_LOCK curr_view->listcontrols.norefresh = FALSE; LISTS_UNLOCK } if (refresh_active_refcount < 0) refresh_active_refcount = 0; } else { refresh_inactive_refcount--; if (refresh_inactive_refcount == 0 && refresh_refcount == 0) { LISTS_LOCK other_view->listcontrols.norefresh = FALSE; LISTS_UNLOCK } if (refresh_inactive_refcount < 0) refresh_inactive_refcount = 0; } } /** @brief disable -refresh action @param from the button, menu item etc which was activated @param art action runtime data @return TRUE always */ gboolean e2_filelist_disable_refresh_action (gpointer from, E2_ActionRuntime *art) { e2_filelist_disable_refresh (); return TRUE; } /** @brief enable -refresh action @param from the button, menu item etc which was activated @param art action runtime data @return TRUE always */ gboolean e2_filelist_enable_refresh_action (gpointer from, E2_ActionRuntime *art) { e2_filelist_enable_refresh (); return TRUE; } /** @brief reset polling of config data and pane filelists @return */ void e2_filelist_reset_refresh (void) { if (refresh_refcount != 0) { refresh_refcount = 1; e2_filelist_enable_refresh (); } } /* These functions must encapsulate any instructions that access the FileInfo structs produced when a selection is interrogated, because a refresh in the middle of such an operation would free FileInfo structs as a side-effect. */ /** @brief disable refreshing of pane filelists and config data Increase ref count, if == 1, stop the timer process which calls the refresh fns Why is config data check bundled here ? @return */ void e2_filelist_disable_refresh (void) { refresh_refcount++; #ifdef E2_REFRESH_DEBUG printd (DEBUG, "disable refresh, refresh ref now = %d", refresh_refcount); #endif if (refresh_refcount == 1) { if (app.timers[REFRESHBEGIN_T] > 0) { //we're waiting for a pending refresh g_source_remove (app.timers[REFRESHBEGIN_T]); app.timers[REFRESHBEGIN_T] = 0; } if (e2_option_bool_get ("auto-refresh")) { //don't cancel check-dirty timer, to reduce risk that FAM //dirty-reports queue may over-fill LISTS_LOCK curr_view->listcontrols.norefresh = TRUE; other_view->listcontrols.norefresh = TRUE; LISTS_UNLOCK } if (e2_option_bool_get ("auto-refresh-config")) { //CHECKME just block this too? //(can't reasonably be many changes to the config file) if (app.timers[CONFIG_T] > 0) { g_source_remove (app.timers[CONFIG_T]); app.timers[CONFIG_T] = 0; } } #ifndef E2_STATUS_DEMAND e2_window_disable_status_update (); #endif } } /** @brief re-enable polling of pane filelists and config data Decrease ref count, if == 0, re-enable/restart the processes which detect changes of filelist content and/or config data Config timer is bundled here because the same func refreshes the panes @return */ void e2_filelist_enable_refresh (void) { refresh_refcount--; #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, refresh ref now = %d", refresh_refcount); #endif if (refresh_refcount < 0) { printd (WARN, "The auto-refresh refresh count is < 0"); gdk_threads_enter (); e2_window_output_show (NULL, NULL); e2_output_print_error ( _("Something is wrong with the auto-refresh mechanism"), FALSE); gdk_threads_leave (); } if (refresh_refcount == 0) { if (e2_option_bool_get ("auto-refresh")) { LISTS_LOCK curr_view->listcontrols.norefresh = FALSE; other_view->listcontrols.norefresh = FALSE; LISTS_UNLOCK } //this timer is used, even with fam backends if (e2_option_bool_get ("auto-refresh-config")) app.timers[CONFIG_T] = #ifdef USE_GLIB2_14 g_timeout_add_seconds (E2_CONFIGCHECK_INTERVAL_S, #else g_timeout_add (E2_CONFIGCHECK_INTERVAL, #endif (GSourceFunc) e2_option_check_config_files, NULL); #ifndef E2_STATUS_DEMAND e2_window_enable_status_update (-1); #endif } } /** @brief timer destroy function after cancelling filelists checking @param data UNUSED data specified when the timer was established @return */ static void _e2_filelist_timer_shutdown (gpointer data) { app.timers[REFRESHBEGIN_T] = 0; } /** @brief timer callback function which refreshes filelist(s) as appropriate, as soon as any block is gone @param data pointerised value of current timer delay @return FALSE when there's nothing left to refresh or more waiting is needed */ static gboolean _e2_filelist_refresh_manage (gpointer data) { pthread_t thisID; gpointer result; gboolean cvdone, ovdone; LISTS_LOCK ViewInfo *cv = curr_view; //log these, in case active pane is changed during this process ViewInfo *ov = other_view; retest: if (cv->listcontrols.refresh_requested) { cvdone = !( cv->listcontrols.norefresh || //norefresh flags set when refresh is disabled cv->listcontrols.refresh_working || cv->listcontrols.cd_working #ifdef E2_STATUS_BLOCK || app.status_working #endif ); if (cvdone) { LISTS_UNLOCK #ifdef E2_VFSTMP //FIXME dir when not mounted local #else printd (DEBUG, "accepting request to refresh %s", cv->dir); #endif //refresh function expects BGL open if (pthread_create (&thisID, NULL, (gpointer) e2_fileview_refresh_list, cv) == 0) pthread_join (thisID, &result); else printd (WARN, "Failed to create list refresh thread"); LISTS_LOCK //clear flag, too bad if a request happened late in the process if (result != NULL) cv->listcontrols.refresh_requested = FALSE; } } else cvdone = TRUE; retest2: if (ov->listcontrols.refresh_requested) { ovdone = !( ov->listcontrols.norefresh || ov->listcontrols.refresh_working || ov->listcontrols.cd_working #ifdef E2_STATUS_BLOCK || app.status_working #endif ); if (ovdone) { LISTS_UNLOCK #ifdef E2_VFSTMP //FIXME dir when not mounted local #else printd (DEBUG, "accepting request to refresh %s", ov->dir); #endif if (pthread_create (&thisID, NULL, (gpointer) e2_fileview_refresh_list, ov) == 0) pthread_join (thisID, &result); else printd (WARN, "Failed to create list refresh thread"); LISTS_LOCK if (result != NULL) ov->listcontrols.refresh_requested = FALSE; } } else ovdone = TRUE; if (cv->listcontrols.refresh_requested || ov->listcontrols.refresh_requested) { //again/now/still want to do one or both panes //but only go back now not blocked before if (cv->listcontrols.refresh_requested && cvdone) { LISTS_UNLOCK goto retest; } if (ov->listcontrols.refresh_requested && ovdone) { LISTS_UNLOCK goto retest2; } printd (DEBUG, "more refresh request(s), deferred because busy"); app.timers[REFRESHBEGIN_T] = g_timeout_add_full (G_PRIORITY_HIGH, 200, (GSourceFunc) _e2_filelist_refresh_manage, GINT_TO_POINTER (200), (GDestroyNotify) _e2_filelist_timer_shutdown); } LISTS_UNLOCK return FALSE; } /** @brief log request, and possibly initiate, filelist(s) refresh if it/they currently show @a dir If a refresh is initiated, either or both flagged lists will be refreshed as soon as any in-progress re-list (either or both panes) is completed @param dir the dir to be refreshed (utf8 string) @param immediate TRUE to intiate a refresh @return TRUE if a refresh was initiated */ gboolean e2_filelist_request_refresh (gchar *dir, gboolean immediate) { gboolean matched = FALSE; #ifdef E2_VFSTMP //CHECKME v-dir ok #endif if (g_str_equal (curr_view->dir, dir)) { matched = TRUE; LISTS_LOCK curr_view->listcontrols.refresh_requested = TRUE; LISTS_UNLOCK } #ifdef E2_VFSTMP //CHECKME v-dir ok #endif if (g_str_equal (other_view->dir, dir)) { matched = TRUE; LISTS_LOCK other_view->listcontrols.refresh_requested = TRUE; LISTS_UNLOCK } if (matched && immediate) //clear any fs report about dirty list, then do the refresh e2_filelist_check_dirty (NULL); return matched; } /** @brief decide whether to lengthen the interval between filelist refresh requests This is called when a 'dirty' flag has been detected @param pane enumerator for pane 1 or pane 2, which was reported dirty @return TRUE when */ static gboolean _e2_filelist_refresh_ok (E2_ListChoice pane) { gboolean retval; /* Ideally this would check CPU resource-usage for this app and all children, and only proceed if there's enough(?) idle capacity Instead we assume small dirs can be processed without much adverse effect */ gint times, itemcount = (pane == PANE1) ? //these are counts of total items, not filtered items gtk_tree_model_iter_n_children (GTK_TREE_MODEL (app.pane1_view.store), NULL): gtk_tree_model_iter_n_children (GTK_TREE_MODEL (app.pane2_view.store), NULL); if (itemcount < 51) times = 1; else if (itemcount < 251) times = 2; else times = 3; if (pane == PANE1) { if (++pane1repeats >= times) { printd (DEBUG, "Pane 1 refresh interval count = %d", times); pane1repeats = 0; retval = TRUE; } else retval = FALSE; } else { if (++pane2repeats >= times) { printd (DEBUG, "Pane 2 refresh interval count = %d", times); pane2repeats = 0; retval = TRUE; } else retval = FALSE; } return retval; } /** @brief request refresh of filepane(s) contents (and with FAM, config too) if the corresponding changed is detected Among other uses, this is the timer callback function for filelists auto refresh When FAM/gamin in use, it also checks for and flags (not processes) config file updates, as that data comes from the same data stream. In certain circumstances any dirty flag might be ignored, or deferred to lengthen the effective interval between refreshes This expects gtk's BGL to be off/open @param userdata data specified when the timer was initialised (NULL), or when called directly, non-NULL @return TRUE so the timer keeps working */ gboolean e2_filelist_check_dirty (gpointer userdata) { static gboolean busy = FALSE; if (busy) return TRUE; //no reentrant usage busy = TRUE; #ifdef E2_FAM static gboolean cfgdeferred = FALSE; #endif gboolean p1dirty, p2dirty; #ifdef E2_FAM gboolean localcfgdirty; //use a local copy so that the extern value doesn't get cleared before it's noticed extern gboolean cfgdirty; if (userdata != NULL && app.monitor_type != E2_MONITOR_DEFAULT) g_usleep (E2_FAMWAIT); //wait for things to be noticed #endif #ifndef E2_FAM if (userdata != NULL) { p1dirty = p2dirty = TRUE; //force a refresh } else #endif //this reports 'gone' as 'dirty', and then the list-refesh //function handles choosing a replacement dir e2_fs_FAM_check_dirty (&p1dirty, &p2dirty #ifdef E2_FAM , &localcfgdirty #endif ); p1dirty = p1dirty || pane1repeats; p2dirty = p2dirty || pane2repeats; if (p1dirty || p2dirty) { LISTS_LOCK if (p1dirty && (userdata != NULL || _e2_filelist_refresh_ok (PANE1))) app.pane1_view.listcontrols.refresh_requested = TRUE; else p1dirty = FALSE; if (p2dirty && (userdata != NULL || _e2_filelist_refresh_ok (PANE2))) app.pane2_view.listcontrols.refresh_requested = TRUE; else p2dirty = FALSE; LISTS_UNLOCK if (p1dirty || p2dirty) { if (app.timers[REFRESHBEGIN_T] > 0) { //clear any incomplete refresh request g_source_remove (app.timers[REFRESHBEGIN_T]); } //the delay between attempts will be lengthened later, if appropriate //CHECKME we may be in a timer callback here - are nested timers still acceptable to glib/gtk ? app.timers[REFRESHBEGIN_T] = g_timeout_add_full (G_PRIORITY_HIGH, 5, (GSourceFunc) _e2_filelist_refresh_manage, GINT_TO_POINTER (5), (GDestroyNotify) _e2_filelist_timer_shutdown); } } #ifdef E2_FAM if (localcfgdirty || cfgdeferred) { if (!app.rebuild_enabled) { cfgdeferred = TRUE; printd (DEBUG, "config refresh deferred"); } else { cfgdeferred = FALSE; if (localcfgdirty) { cfgdirty = TRUE; //set, but don't clear, the main flag printd (DEBUG, "config refresh flag set"); } } } #endif //def E2_FAM busy = FALSE; return TRUE; } /** @brief initialise timer which polls for various 'dirty' indicator(s) The timer is never suspended or paused, to avoid the risk of overflow from kernel-fam processes. It may be stopped permanently if change-monitoring is abandoned via a config dialog @return */ void e2_filelist_start_refresh_polling (void) { if (e2_option_bool_get ("auto-refresh")) { app.timers[DIRTYCHECK_T] = #ifdef USE_GLIB2_14 g_timeout_add_seconds (E2_FILESCHECK_INTERVAL_S, #else g_timeout_add (E2_FILESCHECK_INTERVAL, #endif (GSourceFunc) e2_filelist_check_dirty, NULL); } } /** @brief idle function to clear old liststores @param user_data UNUSED data specified when the idle was setup @return FALSE, to stop the callbacks */ gboolean e2_filelist_clear_old_stores (gpointer user_data) { GSList *tmp; #ifdef DEBUG_MESSAGES gint debug = g_slist_length (app.used_stores); #endif for (tmp = app.used_stores; tmp != NULL; tmp = tmp->next) { GtkListStore *store = tmp->data; GtkTreeModel *mdl = GTK_TREE_MODEL (store); GtkTreeIter iter; if (gtk_tree_model_get_iter_first (mdl, &iter)) { //it's not empty already //clear file info data structs referred to in the store //CHECKME need to clear anything else? FileInfo *info; do { gtk_tree_model_get (mdl, &iter, FINFO, &info, -1); #ifdef USE_GLIB2_10 g_slice_free1 (sizeof(FileInfo), info); //DEALLOCATE (FileInfo, info); #else DEALLOCATE (FileInfo, info); #endif } while (gtk_tree_model_iter_next (mdl, &iter)); } //CHECKME clear filtermodel ? g_object_unref (G_OBJECT (store)); } g_slist_free (app.used_stores); app.used_stores = NULL; printd (DEBUG, "%d old liststore(s) cleared", debug); return FALSE; } /** @brief create and populate filelist-compatible list store with rows for each item in @a entries @param entries list of FileInfo's to be processed @param view pointer to data struct for the view to which the filled store will apply @return pointer to the liststore, or NULL if a problem occurs */ GtkListStore *e2_filelist_fill_store (GList *entries, ViewInfo *view) { // printd (DEBUG, "start store fill"); GtkListStore *store = e2_filelist_make_store (); if (store == NULL) return NULL; GList *tmp; FileInfo *infoptr; GtkTreeIter iter; struct tm *tm_ptr; struct passwd *pwd_buf; struct group *grp_buf; gchar size_buf[20]; //enough for 999 Tb gchar modified_buf[25]; gchar accessed_buf[25]; gchar changed_buf[25]; // gchar perm_buf[11]; gchar uid_buf[20]; gchar gid_buf[20]; gchar *buf[NAMEKEY+1]; //pointers to strings to be inserted into each row gchar *freeme; gchar *__utf__; //for e2_utf8_from_locale_fast() macro //get format parameters //format for dates display gchar *strf_string; switch (e2_option_int_get ("date-string")) { case 1: strf_string = "%d/%m/%y %H:%M"; //standard break; case 2: strf_string = "%m/%d/%y %H:%M"; //american break; case 3: strf_string = "%Y-%m-%dT%H:%M"; //ISO8601 break; case 4: /* { strf_string = nl_langinfo (D_T_FMT); if (strf_string != NULL && *strf_string != '\0') break; } */ strf_string = "%x %X"; //localised break; default: strf_string = "%b %d %H:%M"; break; } //get format for size display gchar *comma; switch (e2_option_int_get ("size-string")) { case 1: { comma = nl_langinfo (THOUSEP); if (comma == NULL || *comma == '\0') comma = ","; break; } default: comma = NULL; //signal to use the condensed version break; } gboolean caseignore = ! e2_option_bool_get ("namesort-case-sensitive"); GtkStyle *style = gtk_rc_get_style (curr_view->treeview); GdkColor *default_color = &style->text[GTK_STATE_NORMAL]; #ifdef E2_ASSISTED GdkColor *back_color = (e2_option_bool_get ("color-background-set")) ? e2_option_color_get ("color-background") : NULL; #endif GdkColor *link_color = e2_option_color_get ("color-ft-link"); GdkColor *dir_color = e2_option_color_get ("color-ft-dir"); GdkColor *dev_color = e2_option_color_get ("color-ft-dev"); GdkColor *sock_color = e2_option_color_get ("color-ft-socket"); GdkColor *exec_color = e2_option_color_get ("color-ft-exec"); //iterate through the listed FileInfo's for (tmp = entries; tmp != NULL; tmp = tmp->next) { infoptr = (FileInfo *) tmp->data; //convert to utf if appropriate buf[FILENAME] = F_DISPLAYNAME_FROM_LOCALE (infoptr->filename); //directories (and links to dirs) get a trailing separator //FIXME speed this by using a more efficient process to check for dir if (e2_fs_is_dir (infoptr, view)) { freeme = buf[FILENAME]; buf[FILENAME] = e2_utils_strcat (freeme, "/"); if (freeme != infoptr->filename) g_free (freeme); } else if (buf[FILENAME] == infoptr->filename) //no actual conversion, above buf[FILENAME] = g_strdup (buf[FILENAME]); if (comma == NULL) { //use condensed size format if (infoptr->statbuf.st_size < 1024) //less than 1k { g_snprintf(size_buf, sizeof(size_buf), "%"PRIu64, infoptr->statbuf.st_size); } else if (infoptr->statbuf.st_size < 1048576) //less than a meg { g_snprintf(size_buf, sizeof(size_buf), "%.1f%s", (gfloat) infoptr->statbuf.st_size / 1024.0, _("k")); } else //a meg or more if (infoptr->statbuf.st_size < 1073741824) { g_snprintf(size_buf, sizeof(size_buf), "%.1f%s", (gfloat) infoptr->statbuf.st_size / 1048576.0, _("M")); } /* else //a gig or more { g_snprintf(size_buf, sizeof(size_buf), "%.1f%s", (gfloat) infoptr->statbuf.st_size / 1073741824.0, _("G")); } */ } else { //use actual size, with commas g_snprintf(size_buf, sizeof(size_buf), "%"PRIu64, infoptr->statbuf.st_size); guint len = strlen (size_buf); guint ths = len-1; //0-based index guint i; while (ths > 2 && len < sizeof(size_buf)) { for (i = len-1; i > ths-3; i--) size_buf[i+1] = size_buf[i]; size_buf[i+1] = *comma; size_buf[++len] = '\0'; ths = i; } } buf[SIZE] = g_strdup (size_buf); //content is ascii, no need to convert to utf buf[PERM] = e2_fs_get_perm_string (infoptr->statbuf.st_mode); //string is already utf if ((pwd_buf = getpwuid (infoptr->statbuf.st_uid)) == NULL) { g_snprintf (uid_buf, sizeof(uid_buf), "%d", (guint) infoptr->statbuf.st_uid); buf[OWNER] = g_strdup (uid_buf); //content is ascii number, no need to convert to utf } else { buf[OWNER] = e2_utf8_from_locale_fast (pwd_buf->pw_name); } if ((grp_buf = getgrgid (infoptr->statbuf.st_gid)) == NULL) { g_snprintf(gid_buf, sizeof(gid_buf), "%d", (guint) infoptr->statbuf.st_gid); buf[GROUP] = g_strdup (gid_buf); //content is ascii number, no need to convert to utf } else { buf[GROUP] = e2_utf8_from_locale_fast (grp_buf->gr_name); } tm_ptr = localtime (&(infoptr->statbuf.st_mtime)); strftime (modified_buf, sizeof(modified_buf), strf_string, tm_ptr); buf[MODIFIED] = e2_utf8_from_locale_fast (modified_buf); tm_ptr = localtime (&(infoptr->statbuf.st_atime)); strftime (accessed_buf, sizeof(accessed_buf), strf_string, tm_ptr); buf[ACCESSED] = e2_utf8_from_locale_fast (accessed_buf); tm_ptr = localtime(&(infoptr->statbuf.st_ctime)); strftime (changed_buf, sizeof(changed_buf), strf_string, tm_ptr); buf[CHANGED] = e2_utf8_from_locale_fast (changed_buf); if (caseignore) { freeme = g_utf8_casefold (buf[FILENAME], -1); #ifdef USE_GTK2_8 buf[NAMEKEY] = g_utf8_collate_key_for_filename (freeme, -1); #else buf[NAMEKEY] = g_utf8_collate_key (freeme, -1); #endif g_free (freeme); } else #ifdef USE_GTK2_8 buf[NAMEKEY] = g_utf8_collate_key_for_filename (buf[FILENAME], -1); #else buf[NAMEKEY] = g_utf8_collate_key (buf[FILENAME], -1); #endif GdkColor *foreground; switch (infoptr->statbuf.st_mode & S_IFMT) { case S_IFLNK: foreground = link_color; break; case S_IFDIR: foreground = dir_color; break; case S_IFCHR: case S_IFBLK: foreground = dev_color; break; case S_IFSOCK: foreground = sock_color; break; case S_IFREG: //show as executable if _anyone_ has that permission if ((S_IXUSR & infoptr->statbuf.st_mode) || (S_IXGRP & infoptr->statbuf.st_mode) || (S_IXOTH & infoptr->statbuf.st_mode)) foreground = exec_color; else { #ifdef E2_RAINBOW //find extension of current item //localised text, but assumes . is ascii //hashed extension-strings used for comparison have been localised foreground = NULL; //in case there's no '.' at all gchar *ext = infoptr->filename + 1; //+1 in case item is hidden while ((ext = strchr (ext,'.')) != NULL) { ext++; //pass the '.' //use its color, if any foreground = g_hash_table_lookup (app.colors, ext); if (foreground != NULL) break; } if (foreground == NULL) foreground = default_color; #else foreground = default_color; #endif } break; default: foreground = default_color; break; } //gtk >= 2.10 can handle &iter = NULL gtk_list_store_insert_with_values (store, &iter, -1, FILENAME, buf[FILENAME], SIZE, buf[SIZE], PERM, buf[PERM], OWNER, buf[OWNER], GROUP, buf[GROUP], MODIFIED, buf[MODIFIED], ACCESSED, buf[ACCESSED], CHANGED, buf[CHANGED], NAMEKEY, buf[NAMEKEY], FINFO, infoptr, FORECOLOR, foreground, #ifdef E2_ASSISTED BACKCOLOR, back_color, #endif // VISIBLE, TRUE, -1); //cleanup gint i; for (i = 0; i <= NAMEKEY; i++) g_free (buf[i]); } /* // gettimeofday (&strt, NULL); //FOR DEBUGGING, check time this load takes // gettimeofday (&nd, NULL); if (nd.tv_usec < strt.tv_usec) { int nsec = (strt.tv_usec - nd.tv_usec) / 1000000 + 1; strt.tv_usec -= 1000000 * nsec; strt.tv_sec += nsec; } if (nd.tv_usec - strt.tv_usec > 1000000) { int nsec = (strt.tv_usec - nd.tv_usec) / 1000000; strt.tv_usec += 1000000 * nsec; strt.tv_sec -= nsec; } result.tv_sec = result.tv_sec + nd.tv_sec - strt.tv_sec; result.tv_usec = result.tv_usec + nd.tv_usec - strt.tv_usec; if (result.tv_usec > 1000000) { int nsec = result.tv_usec / 1000000 + 1; result.tv_usec -= 1000000 * nsec; result.tv_sec += nsec; } // printd (DEBUG, "populating rows took %f seconds", result.tv_sec + result.tv_usec / 1000000.0 ); */ // printd (DEBUG, "populating rows finished"); return store; } /** @brief create list store structure Creates basic structure of list store, for panes filelists No rows or data are added. @return the new liststore */ GtkListStore *e2_filelist_make_store (void) { GtkListStore *store = gtk_list_store_new (MODEL_COLUMNS, G_TYPE_STRING, //FILENAME G_TYPE_STRING, //SIZE G_TYPE_STRING, //PERM G_TYPE_STRING, //OWNER G_TYPE_STRING, //GROUP G_TYPE_STRING, //MODIFIED G_TYPE_STRING, //ACCESSED G_TYPE_STRING, //CHANGED // the rest are not displayed G_TYPE_STRING, //NAMEKEY for i18n name sorts G_TYPE_POINTER, //FINFO pr to FileInfo for the item GDK_TYPE_COLOR, //FORECOLOR line colour GDK_TYPE_COLOR //BACKCOLOR line colour // G_TYPE_BOOLEAN //VISIBLE whether filtered or not ); return store; } #ifdef STORECOPY /** @brief helper function to copy each FileInfo stored in a filelist store @param model the store to be updated @param tpath UNUSED tree path of iter being processed ? @param iter pointer to iter being processed @param user_data UNUSED data specified when foreach was called @return FALSE if the process can continue */ static gboolean _e2_filelist_copy_storeinfos (GtkTreeModel *model, GtkTreePath *tpath, GtkTreeIter *iter, gpointer user_data) { FileInfo *info; gtk_tree_model_get (model, iter, FINFO, &info, -1); //FIXME duplicate the data #ifdef USE_GLIB2_14 info = (FileInfo *) g_slice_copy (sizeof (FileInfo), info); gtk_list_store_set (GTK_LIST_STORE (model), iter, FINFO, info, -1); #else FileInfo *info2 = ALLOCATE (FileInfo); CHECKALLOCATEDWARN (info2, return TRUE;) memcpy (info2, info, sizeof (FileInfo)); gtk_list_store_set (GTK_LIST_STORE (model), iter, FINFO, info2, -1); #endif // gtk_tree_path_free (tpath); CRASHER return FALSE; } /** @brief copy a filelist liststore with all its contents @param original the store to be copied @return the new liststore */ GtkListStore *e2_filelist_copy_store (GtkListStore *original) { gpointer copied; e2_tree_store_copy (GTK_TREE_MODEL (original), FALSE, &copied); gtk_tree_model_foreach (GTK_TREE_MODEL ((GtkListStore *)copied), (GtkTreeModelForeachFunc) _e2_filelist_copy_storeinfos, NULL); return ((GtkListStore *)copied); } #endif emelfm2-0.4.1/src/e2_context_menu.c0000600000175000017500000005211310776374631016060 0ustar cairocairo/* $Id: e2_context_menu.c 853 2008-04-07 10:38:17Z tpgww $ Copyright (C) 2003-2008 tooar Portions copyright (C) 1999 Michael Clark. This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /* 09-20-2000: Modified by Aurelien Gateau Added code for Named actions for filetypes */ /** @file src/e2_context_menu.c @brief file-pane context menu functions Some of the functions are used in other contexts eg toolbar menus Other context menus are handled in the same file as their 'context'. */ /** \page menu the filelist context menu ToDo -describe how this works */ /** \page othermenu other context menus ToDo */ #include "e2_context_menu.h" #include #include "e2_filelist.h" #include "e2_filetype.h" /*******************/ /**** callbacks ****/ /*******************/ /** @brief execute action corresponding to item selected from filetype tasks menu This is the callback for handling a selection of a filetype action from the context menu @param widget the selected menu item widget @return */ static void _e2_context_menu_choose_filetype_action_cb (GtkWidget *widget) { printd (DEBUG, "context menu choose filetype action cb"); e2_filetype_exec_action ((gchar *) gtk_widget_get_name (widget)); } /* static gboolean _e2_context_menu_item_click_cb (GtkWidget *widget, GdkEventButton *event, E2_ActionRuntime *rt) { printd (DEBUG, "item_click_cb (widget:_,event:_,rt:_)"); if (event->button == 1 && event->type == GDK_BUTTON_PRESS) { e2_action_run (widget, rt); return TRUE; } return FALSE; } */ /** @brief context-menu destroy callback @param menu the menu widget (= app.context_menu) @param data data specified when the callback was connected @return */ static void _e2_context_menu_destroy_menu_cb (GtkWidget *menu, gpointer data) { printd (DEBUG, "destroy_menu_cb"); // printd (DEBUG, "destroy_menu_cb, refresh ref = %d", refresh_refcount); //CHECKME is this specific destruction redundant ? gtk_container_foreach (GTK_CONTAINER (menu), (GtkCallback) gtk_widget_destroy, NULL); gtk_widget_destroy (menu); //no enable refresh, here as it is done somewhere else!! //#ifdef E2_REFRESH_DEBUG // printd (DEBUG, "enable refresh, destroy_menu_cb"); //#endif // e2_filelist_enable_refresh(); app.context_menu = NULL; } /*******************/ /**** utilities ****/ /*******************/ /** @brief populate @a menu with items for the deesktop actions for a file @param menu the menu widget to which the action menu-items are to be added @param desktopfilepath path of .desktop file to parse @return */ void e2_context_menu_create_desktop_actions_menu (GtkWidget *menu, gchar *desktopfilepath) { GKeyFile *key_file = g_key_file_new (); if (g_key_file_load_from_file (key_file, desktopfilepath, G_KEY_FILE_NONE, NULL)) { gsize count = 0; gchar **actions = g_key_file_get_string_list (key_file, "Desktop Entry", "Actions", &count, NULL); if (count > 0) { gchar *group, *label, *command; gchar **iterator; for (iterator = actions; *iterator != NULL; iterator++) { group = g_strconcat ("Desktop Action ", *iterator, NULL); if (g_key_file_has_group (key_file, group)) { command = g_key_file_get_string (key_file, group, "Exec", NULL); if (command != NULL) { label = g_key_file_get_string (key_file, group, "Name", NULL); if (label == NULL) label = command; GtkWidget *menu_item = e2_menu_add (menu, label, NULL, NULL, _e2_context_menu_choose_filetype_action_cb, NULL); gtk_widget_set_name (GTK_WIDGET(menu_item), command); if (label != command) g_free (label); g_free (command); } } g_free (group); } g_strfreev (actions); } } g_key_file_free (key_file); } /** @brief populate @a menu with items for the actions for a filetype Each member of @a actions is like "command" or "label@command" @param menu the menu widget to which the action menu-items are to be added @param actions NULL-terminated array of utf8 strings, each a command for a filetype @return */ void e2_context_menu_create_filetype_actions_menu (GtkWidget *menu, const gchar **actions) { gchar *sep; GtkWidget *menu_item; while (*actions != NULL) { if ((sep = strchr (*actions, '@')) != NULL) //if always ascii @, don't need g_utf8_strchr() { *sep = '\0'; menu_item = e2_menu_add (menu, (gchar *)*actions, NULL, NULL, _e2_context_menu_choose_filetype_action_cb, NULL); *sep = '@'; //revert to original form (this is the 'source' data) gtk_widget_set_name (menu_item, sep+1); } else { menu_item = e2_menu_add (menu, (gchar *)*actions, NULL, NULL, _e2_context_menu_choose_filetype_action_cb, NULL); gtk_widget_set_name (menu_item, *actions); } actions++; } } /** @brief process a filetypes item in @a menu This adds menu-items for a filetype to @a menu @param menu the menu widget to which the menu-items are to be added @return */ static void _e2_context_menu_add_filetype_item (GtkWidget *menu) { gboolean exec, freeext = TRUE; gchar *ext, *ext2; const gchar **actions = NULL; FileInfo *info; GtkTreeModel *mdl = curr_view->model; GtkTreeIter iter; if (gtk_tree_model_iter_nth_child (mdl, &iter, NULL, curr_view->row)) gtk_tree_model_get (mdl, &iter, FINFO, &info, -1); else return; if (e2_fs_is_dir (info, curr_view)) { exec = FALSE; //no special treatment for dirs with X permission ext = g_strconcat (".", _(""), NULL); } else { //this is a tougher exec test than used in context menu // exec = e2_fs_is_executable (info, curr_view); //runs 'file' which prompts a refresh gchar *localpath = e2_utils_dircat (curr_view, info->filename, TRUE); #ifdef E2_VFS #ifdef E2_VFSTMP //CHECKME allow exec options for non-local items ? #endif VPATH ddata = { localpath, NULL }; if (curr_view->spacedata != NULL) exec = FALSE;//CHECKME commands for non-local items ? else exec = !e2_fs_access (&ddata, X_OK E2_ERR_NONE()); #else exec = !e2_fs_access (localpath, X_OK E2_ERR_NONE()); #endif g_free (localpath); freeext = FALSE; //assumes extension is that part of the name after the leftmost '.' ext = strchr (info->filename, '.'); //localised text, but assumes . is ascii if (ext == info->filename //it's a dot file || (ext == NULL //no extension #ifdef E2_VFS && e2_fs_is_text (&ddata E2_ERR_NONE()))) //runs 'file' again!! #else && e2_fs_is_text (info->filename E2_ERR_NONE()))) //runs 'file' again!! #endif //fake text extension ext = ".txt"; //too bad if this is not a recognised text extension in filetypes else if (ext != NULL) { ext2 = ext; ext = F_DISPLAYNAME_FROM_LOCALE (ext); //get utf if (ext != ext2) //conversion actually happened freeext = TRUE; //so free ext before exit } } if (ext != NULL) { ext2 = ext; //remember what to free, if need be //check all possible extensions for a matching filetype do { //skip leading dot "." ext++; //NCHR(ext); ascii '.'. always single char actions = e2_filetype_get_actions (ext); if (actions != NULL) { e2_context_menu_create_filetype_actions_menu (menu, actions); break; } } while ((ext = strchr (ext, '.')) != NULL); //always ascii '.', don't need g_utf8_strchr() if (freeext) g_free (ext2); } if (exec) { //add exec-filetype items unless item has been found in that type already const gchar **acts2 = e2_filetype_get_actions (_("")); if (actions != NULL //was a matching extension && actions != acts2 && acts2 != NULL) //CHECKME this test e2_context_menu_create_filetype_actions_menu (menu, acts2); else if (acts2 != NULL) e2_context_menu_create_filetype_actions_menu (menu, acts2); //if appropriate, get desktop entry actions and add them too gchar *path, *localpath; gchar *localname = e2_utils_strcat (info->filename, ".desktop"); const gchar* const *sysdir = g_get_system_data_dirs (); while (*sysdir != NULL) { path = g_build_filename (*sysdir, "applications", localname, NULL); localpath = F_FILENAME_TO_LOCALE (path); //probably redundant #ifdef E2_VFS VPATH data = { localpath, NULL }; //always local if (!e2_fs_access (&data, R_OK E2_ERR_NONE())) #else if (!e2_fs_access (localpath, R_OK E2_ERR_NONE())) #endif { e2_context_menu_create_desktop_actions_menu (menu, localpath); g_free (path); F_FREE (localpath); break; } g_free (path); F_FREE (localpath); sysdir++; } g_free (localname); } } /** @brief populate context-menu @a menu @param menu the context-menu widget @param model model for context-menu data treestore @param iter pointer to iter used to interrogate @a model @param level the current depth in @a model @param mtype enumerator for type of menu, 0=full ... 3=plugins only @return */ static void _e2_context_menu_add_items (GtkWidget *menu, GtkTreeModel *model, GtkTreeIter *iter, gint mtype) { gboolean selected = (gtk_tree_selection_count_selected_rows (curr_view->selection) > 0); if (mtype == 3) { // shift+ctrl menu e2_menu_create_plugins_menu (menu, selected); return; } gchar *prefix = g_strconcat (_A(5), ".", NULL); do { gchar *label, *icon, *command, *arg; gboolean _mtype; switch (mtype) { case 1: // shift menu gtk_tree_model_get (model, iter, 0, &label, 1, &icon, 2, &_mtype, 4, &command, 5, &arg, -1); break; case 2: // ctrl menu gtk_tree_model_get (model, iter, 0, &label, 1, &icon, 3, &_mtype, 4, &command, 5, &arg, -1); break; default: gtk_tree_model_get (model, iter, 0, &label, 1, &icon, 4, &command, 5, &arg, -1); break; } if (selected || !g_str_has_prefix (command, prefix)) { if ((mtype != 0) && !_mtype) //CHECKME should be _mtype { GtkTreeIter iter2; if (gtk_tree_model_iter_children (model, &iter2, iter)) //recurse _e2_context_menu_add_items (menu, model, &iter2, mtype); } else { gchar *realarg; E2_Action *action = e2_action_get_with_custom (command, arg, &realarg); switch (action->type) { case E2_ACTION_TYPE_SUBMENU: { GtkWidget *item = e2_menu_add (menu, label, icon, NULL, NULL, NULL); GtkWidget *menu2; if (arg == NULL || *arg == '\0') //no data == not a custom sub-menu { GtkTreeIter iter2; if (gtk_tree_model_iter_children (model, &iter2, iter)) { menu2 = gtk_menu_new (); //recurse _e2_context_menu_add_items (menu2, model, &iter2, mtype); } else menu2 = NULL; } else //custom submenu menu2 = e2_menu_create_custom_menu (arg); //may be NULL if (menu2 != NULL) { GList *children = gtk_container_get_children (GTK_CONTAINER (menu2)); if (g_list_length (children) == 0) { gtk_widget_destroy (item); gtk_widget_destroy (menu2); } else gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), menu2); g_list_free (children); } else gtk_widget_destroy (item); } break; case E2_ACTION_TYPE_BOOKMARKS: { E2_OptionSet *set = e2_option_get ("bookmarks"); GtkTreeIter iter; if (arg != NULL && *arg != '\0') { if (e2_tree_find_iter_from_str (set->ex.tree.model, 0, arg, &iter, TRUE)) { GtkTreeIter iter2; if (gtk_tree_model_iter_children (set->ex.tree.model, &iter2, &iter)) e2_menu_create_bookmarks_menu (action, set, menu, &iter2); } } else if (gtk_tree_model_get_iter_first (set->ex.tree.model, &iter)) e2_menu_create_bookmarks_menu (action, set, menu, &iter); } break; case E2_ACTION_TYPE_FILE_ACTIONS: _e2_context_menu_add_filetype_item (menu); break; case E2_ACTION_TYPE_PLUGINS: e2_menu_create_plugins_menu (menu, selected); break; case E2_ACTION_TYPE_SEPARATOR: { GList *children = gtk_container_get_children (GTK_CONTAINER (menu)); if (g_list_length (children) > 0) e2_menu_add_separator (menu); g_list_free (children); } break; case E2_ACTION_TYPE_TEAR_OFF_MENU: e2_menu_add_tear_off (menu); break; case E2_ACTION_TYPE_TOGGLE: e2_menu_add_toggle (menu, label, icon, "", command, arg); break; case E2_ACTION_TYPE_ITEM: { E2_ActionRuntime *actionrt = e2_action_pack_runtime (action, realarg, g_free); GtkWidget *item; //FIXME add tips item = e2_menu_add (menu, label, icon, NULL, e2_action_run, actionrt); g_object_set_data_full (G_OBJECT (item), "free-callback-data", actionrt, (GDestroyNotify) e2_action_free_runtime); } break; default: break; } if (action->type != E2_ACTION_TYPE_ITEM) g_free (realarg); } } //cleanup g_free (label); g_free (icon); g_free (command); g_free (arg); } while (gtk_tree_model_iter_next (model, iter)); g_free (prefix); } /******************/ /***** public *****/ /******************/ /** @brief create and show main (filepane) context menu @param button initiator enumerator, 0-menu-key or action, 1=L etc @param time time at which event was triggered @param type menu type enumerator 0=full menu, 1=Shft, 2=Ctrl, 3=Shft+Ctrl @return */ void e2_context_menu_show (guint button, guint32 time, gint type) { // FileInfo *info; //FIXME if we need to disable refresh during the popup, // make all menu choices depart via a fn that enables refresh again //#ifdef E2_REFRESH_DEBUG // printd (DEBUG, "disable refresh, context menu show"); //#endif // e2_filelist_disable_refresh(); // if (curr_view->tagged != NULL) // info = curr_view->tagged->data; // else // { GtkTreeIter iter; /* show menu even with empty dir if (gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (curr_view->store), &iter, NULL, curr_view->row)) gtk_tree_model_get (GTK_TREE_MODEL (curr_view->store), &iter, FINFO, &info, -1); else { printd (WARN, "missing model row"); return; } */ // } E2_OptionSet *set = e2_option_get ("context-menu"); if (gtk_tree_model_get_iter_first (set->ex.tree.model, &iter)) { app.context_menu = gtk_menu_new (); _e2_context_menu_add_items (app.context_menu, set->ex.tree.model, &iter, type); g_signal_connect (G_OBJECT (app.context_menu), "selection-done", G_CALLBACK (_e2_context_menu_destroy_menu_cb), NULL); if (button == 0) //this was a menu key press or specific action gtk_menu_popup (GTK_MENU (app.context_menu), NULL, NULL, (GtkMenuPositionFunc) e2_fileview_set_menu_position, curr_view->treeview, 0, time); else //this was a button-3 click gtk_menu_popup (GTK_MENU (app.context_menu), NULL, NULL, NULL, NULL, button, time); } } /** @brief show context-menu action @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ gboolean e2_context_menu_show_menu_action (gpointer from, E2_ActionRuntime *art) { gint type = 0; gchar *arg = (gchar *)art->data; if (arg != NULL) { g_strstrip (arg); if (g_str_equal (arg, _A(112))) //_(shift type = 1; else if (g_str_equal (arg, _A(105))) //_(ctrl type = 2; //CHECKME alt option here ? } e2_context_menu_show (0, 0, type); return TRUE; } /** @brief install default tree options for context menu This function is called only if the default is missing from the config file @param set pointer to set data @return */ static void _e2_context_menu_tree_defaults (E2_OptionSet *set) { e2_option_tree_setup_defaults (set, g_strdup("context-menu=<"), //internal name g_strconcat("||false|false|",_A(5),".",_A(20),"|",NULL), g_strconcat(_("Open _with.."),"|open_with_"E2IP".png|false|false|", _A(5),".",_A(62),"|",NULL), g_strconcat(_("_View"),"|view_"E2IP".png|false|false|", _A(5),".",_A(101),"|",NULL), g_strconcat(_("_Info"),"|gtk-dialog-info|false|false|",_A(5),".",_A(52),"|",NULL), g_strconcat("||false|false|",_A(18),"|",NULL), g_strconcat(_("_Actions"),"|gtk-execute|false|false|",_A(19),"|",NULL), g_strconcat("\t",_("_Copy"),"|gtk-copy|true|false|",_A(5),".",_A(33),"|",NULL), g_strconcat("\t",_("_Move"),"|move_"E2IP".png|true|false|",_A(5),".",_A(58),"|",NULL), g_strconcat("\t",_("_Link"),"|symlink_"E2IP".png|true|false|",_A(5),".",_A(91),"|",NULL), g_strconcat("\t",_("_Trash"),"|trash_"E2IP".png|true|false|",_A(5),".",_A(97),"|",NULL), g_strconcat("\t","",_("_Delete"),"|delete_"E2IP".png|true|false|", _A(5),".",_A(38),"|",NULL), g_strconcat("\t",_("_Rename.."),"|gtk-convert|true|false|",_A(5),".",_A(73),"|",NULL), g_strconcat("\t",_("Change _owners.."),"|owners_"E2IP".png|true|false|", _A(5),".",_A(63),"|",NULL), g_strconcat("\t",_("Change _permissions.."),"|permissions_"E2IP".png|true|false|", _A(5),".",_A(67),"|",NULL), g_strconcat("\t",_("Copy as.."),"|copy_as_"E2IP".png|true|false|",_A(5),".",_A(34),"|",NULL), g_strconcat("\t",_("Move as.."),"|move_as_"E2IP".png|true|false|",_A(5),".",_A(59),"|",NULL), g_strconcat("\t",_("Link as.."),"|symlink_as_"E2IP".png|true|false|",_A(5),".",_A(92),"|",NULL), g_strconcat(_("_Plugins"),"|gtk-index|false|false|",_A(19),"|",NULL), g_strconcat("\t||false|false|",_A(14),".",_A(24),"|",NULL), g_strconcat("\t||false|false|",_A(18),"|",NULL), g_strconcat("\t",_("_Edit plugins.."),"|gtk-preferences|false|false|",_A(2),".",_C(33),"|",NULL), g_strconcat(_("_User commands"),"|user_commands_"E2IP".png|false|false|",_A(19),"|",NULL), g_strconcat("\t",_("_Make new file.."),"|gtk-new|false|true|touch|'%{",_("Enter file name:"),"}'",NULL), //_A(17) g_strconcat("\t",_("_Compare files"),"||false|true|>cmp|-s %f %F && echo \"",_("The files are identical"),"\"\\n \\|\\| echo \"",_("The files are different"),"\"\\n",NULL), //_A(17) g_strconcat("\t",_("Compare _directories"),"||false|true|diff|%d %D",NULL), //_A(17) // g_strconcat("\t",_("_Easytag"),"||false|true|easytag|%d &",NULL), //_A(17) not common enough to be a default g_strconcat("\t",_("_Remove spaces"),"||false|true|>mv|%f `echo %f \\| sed -e 's/ //g'` 2>/dev/null &",NULL), //_A(17) g_strconcat("\t",_("_Split file.."),"||false|true|split|-b %{",_("Enter the piece-size (in kB):"),"}k %f %f_",NULL), //_A(17) g_strconcat("\t",_("Co_ncatenate files.."),"||false|true|cat|%f > '%{",_("Enter the name of the combined file:"),"}'",NULL), //_A(17) g_strconcat("\t",_("_Free space"),"||false|true|>stat|-f %d \\| awk '/Blocks/ {printf \"%2.1f ", _("percent free"),"\",$5/$3*100}'",NULL), //_A(17) g_strconcat("\t||false|true|",_A(18),"|",NULL), g_strconcat("\t",_("_Edit user commands.."),"|gtk-preferences|false|true|",_A(2),".",_A(32),"|",_C(8),NULL), g_strconcat(_("Ma_ke dir.."),"|gtk-open|false|false|",_A(1),".",_A(56),"|",NULL), //same _ as main toolbar g_strconcat("||false|false|",_A(18),"|",NULL), g_strconcat(_("_Bookmarks"),"|bookmark_"E2IP".png|false|false|",_A(19),"|",NULL), g_strconcat("\t",_("Add _top"),"|add_mark_top_"E2IP".png|false|false|",_A(0),".",_A(26),"|",_A(114),NULL), g_strconcat("\t||false|false|",_A(0),".",_A(24),"|",NULL), g_strconcat("\t",_("Add _bottom"),"|add_mark_bottom_"E2IP".png|false|false|",_A(0),".",_A(26),"|",NULL), g_strconcat("\t||false|false|",_A(18),"|",NULL), g_strconcat("\t",_("_Edit bookmarks.."),"|gtk-preferences|false|false|",_A(2),".",_C(1),"|",NULL), g_strconcat("||false|false|",_A(18),"|",NULL), g_strconcat(_("_Edit filetype.."),"|gtk-preferences|false|false|",_A(2),".",_C(15),"|",NULL), g_strdup(">"), //allow this to be freed NULL); } /** @brief register context-menu option @return */ void e2_context_menu_options_register (void) { //this option does not require a rebuild after config change gchar *group_name = g_strconcat (_C(32), ".", _C(8), NULL); E2_OptionSet *set = e2_option_tree_register ("context-menu", group_name, _C(8), //_("context menu" NULL, NULL, NULL, E2_OPTION_TREE_UP_DOWN | E2_OPTION_TREE_ADD_DEL, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP); e2_option_tree_add_column (set, _("Label"), E2_OPTION_TREE_TYPE_STR, 0, "", 0, NULL, NULL); e2_option_tree_add_column (set, _("Icon"), E2_OPTION_TREE_TYPE_ICON, 0, "", 0, NULL, NULL); //"false" is public name, but is widely used internally, not translated e2_option_tree_add_column (set, _("Shift"), E2_OPTION_TREE_TYPE_BOOL, FALSE,"false", 0, NULL, NULL); e2_option_tree_add_column (set, _("Ctrl"), E2_OPTION_TREE_TYPE_BOOL, FALSE,"false", 0, NULL, NULL); e2_option_tree_add_column (set, _("Action"), E2_OPTION_TREE_TYPE_SEL, 0, _A(18), 0, NULL, GINT_TO_POINTER (E2_ACTION_EXCLUDE_GENERAL | E2_ACTION_EXCLUDE_MENU)); e2_option_tree_add_column (set, _("Argument"), E2_OPTION_TREE_TYPE_SEL, 0, "", 0, NULL, GINT_TO_POINTER (E2_ACTION_EXCLUDE_GENERAL | E2_ACTION_EXCLUDE_MENU | E2_ACTION_EXCLUDE_TOGGLE)); e2_option_tree_create_store (set); e2_option_tree_prepare_defaults (set, _e2_context_menu_tree_defaults); } emelfm2-0.4.1/src/e2_toolbar.h0000600000175000017500000001226111010340377014775 0ustar cairocairo/* $Id: e2_toolbar.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2003-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_TOOLBAR_H__ #define __E2_TOOLBAR_H__ #include "emelfm2.h" #include "e2_action.h" #include "e2_option.h" #include "e2_fileview.h" #define FOLDBARS typedef enum { //these are ordered for decreasing visual impact when changes //are made //if order is changed, correspondingly change order of array rows //in e2_toolbar_options_register() E2_BAR_TASK, E2_BAR_PANE1, E2_BAR_PANE2, E2_BAR_COMMAND, E2_BAR_COUNT //this is the no. of bars, not a 'type' } E2_BarType; typedef struct _E2_ToolbarRuntime { const gchar *name; //'internal' name of bar, for referencing // const gchar *public_name; //translated form of name GtkToolbar *toolbar; GtkWidget *toolbar_container; GtkWidget *toolbar_container_box; #ifdef FOLDBARS GtkWidget *toolbarvbox; //hbox holding just a dir-line #endif gboolean blocked; //TRUE when bar's context menu is being created gboolean hidden; E2_OptionSet *set; E2_OptionSet *show; E2_OptionSet *tooltips; E2_OptionSet *space; E2_OptionSet *hori; E2_OptionSet *type; E2_OptionSet *priority; E2_OptionSet *relief; E2_OptionSet *same; E2_OptionSet *style; E2_OptionSet *isize; GtkIconSize icon_size; //working copy of size enumerator (supports 'default') GtkAllocation *alloc; GtkRequisition req; gboolean has_toggle; gboolean has_bookmarks; gboolean has_command_line; gboolean restbtn_shown; GtkWidget *button_rest; #ifdef FOLDBARS gint restbtn_width; //size to use if folded bars in force GtkToolItem *dirline_tool; //tool item for the movable dirline widget gint dirline_index; //index of dirline_tool in the toolbar gboolean folded; //TRUE when dirline_tool is in separate box // gint threshold; //pixel size when we swap to/from separate box gboolean size_queued; //TRUE to block repeated resizes before redrew #endif GList *bar_items_list; //list of toolbar buttons (not spacers etc), including hidden toggles GtkWidget *menu_starter; //bar item that is first in the overflow menu gboolean reversed; //TRUE if the config-order of bar items is reversed onscreen } E2_ToolbarRuntime; typedef struct _E2_ToolbarData { E2_BarType type; const gchar *name; //internal name, not translated const gchar *public_name; //translated name E2_ToolbarRuntime *rt; //data struct for the bar } E2_ToolbarData; // this is 'extended' data for toggle actions, stored at action.data2 typedef struct _E2_ToggleData { gboolean current_state; GList *boxes; //list of E2_ToggleBox's for this action gchar *true_action; //action command string gchar *false_action; //ditto } E2_ToggleData; typedef struct _E2_ToggleBox { E2_ToolbarRuntime *bar_rt; //ptr to data for the bar that includes this (instance of a toggle GtkWidget *true_image; GtkWidget *false_image; GtkWidget *true_label; GtkWidget *false_label; #ifdef USE_GTK2_12TIPS GtkToolItem *tool; gboolean tipped; gboolean trueactive; gchar *true_tip; gchar *false_tip; #else GtkTooltipsData *tips; #endif gint button_style; } E2_ToggleBox; //index for "self-managed" toggle actions typedef enum { E2_TOGGLE_PANE1FULL, E2_TOGGLE_PANE2FULL, E2_TOGGLE_PANE1HIDDEN, E2_TOGGLE_PANE2HIDDEN, E2_TOGGLE_PANE1FILTERS, E2_TOGGLE_PANE2FILTERS, #ifdef E2_VFS E2_TOGGLE_PANE1SPACE, E2_TOGGLE_PANE2SPACE, #endif E2_TOGGLE_OUTPUTFULL, E2_TOGGLE_OUTPUTSHADE, E2_TOGGLE_COUNT //this is the no. of actions, not a 'type' } E2_ToggleType; gchar *toggles_array [E2_TOGGLE_COUNT]; void e2_toolbar_initialise_space_handler (E2_ToolbarRuntime *rt); void e2_toolbar_set_menu_position (GtkMenu *menu, gint *x, gint *y, gboolean *push_in, GtkWidget *button); gboolean e2_toolbar_button_toggle (gchar *action_name); gboolean e2_toolbar_button_toggle_custom (gchar *hashkey); gboolean e2_toolbar_toggle_button_get_state (gchar *action_name); void e2_toolbar_toggle_button_set_state (gchar *action_name, gboolean state); void e2_toolbar_toggle_buttons_set_destroyed (E2_ToolbarRuntime *rt); void e2_toolbar_toggle_filter_button (ViewInfo *view); void e2_toolbar_initialise (E2_BarType barnum); void e2_toolbar_create (E2_ToolbarRuntime *rt); void e2_toolbar_recreate (E2_ToolbarRuntime *rt); void e2_toolbar_recreate_all (void); void e2_toolbar_destroy (E2_ToolbarRuntime *rt); void e2_toolbar_data_create (void); void e2_toolbar_actions_register (void); void e2_toolbar_options_register (E2_BarType barnum); void e2_toolbar_toolbaroptions_register (void); void e2_toolbar_commandbaroptions_register (void); void e2_toolbar_panebar_register (gint num); #endif //ndef __E2_TOOLBAR_H__ emelfm2-0.4.1/src/command/0000700000175000017500000000000011015120161014176 5ustar cairocairoemelfm2-0.4.1/src/command/e2_alias.c0000600000175000017500000003000110776374631016047 0ustar cairocairo/* $Id: e2_alias.c 853 2008-04-07 10:38:17Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelfm2. emelfm2 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 3, or (at your option) any later version. emelfm2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/command/e2_alias.c @brief functions for handling command aliases 'match' strings are compiled as extended regex. If they do not start with '^', then one is prepended. The whole alias list is scanned in order, so that aliases can be chained, unless and until a match with its 'stop' flag set 'replace' strings may contain: \\1 = substitute the matched alias \\2 = substitute that part of the input command string which is AFTER the matched alias In theory, \\0 = substitute that part of the parsed string which is BEFORE the matched alias, but actually the alias matching always starts from the start */ #include "emelfm2.h" #include #include "e2_alias.h" /** @brief cleanup data struct for @a alias @param alias pointer to compiled-alias data struct @return */ static void _e2_alias_free (E2_Alias *alias) { g_free (alias->match); g_free (alias->replace); regfree (&(alias->preg)); DEALLOCATE (E2_Alias, alias); } /** @brief create data struct for an alias entry Back-reference flag is set if @a replace includes \1 or \2 \0 is now irrelevant @param match string to be replaced @param replace string to use as a replacement @param preg pointer to regex struct @param stop TRUE if the scan is to stop at the first match @return pointer to alias struct with the matching data, or NULL if error occurred */ static E2_Alias *_e2_alias_get (gchar *match, gchar *replace, regex_t preg, gboolean stop) { E2_Alias *alias = ALLOCATE (E2_Alias); CHECKALLOCATEDWARN (alias, return NULL;) alias->match = match; alias->replace = replace; alias->preg = preg; alias->stop = stop; //test if the replace string has a back reference alias->has_back_reference = FALSE; gchar *test = NULL; if ((test = strchr (replace, '\\')) != NULL) //if always ascii \, don't need g_utf8_strchr() { if ( // test[1] == '0' || test[1] == '1' || test[1] == '2') alias->has_back_reference = TRUE; } return alias; } /** @brief create replacement for @a str with alias replaced This tries to match from start of string, as compiled aliases start with ^ and were not compiled with flag REG_NOTBOL @param alias pointer to compiled-alias data struct @param str utf command string, the start of which might match an alias @param replaced location for storing the string with the alias substituted @return TRUE if a match was found, and if so, a newly allocated string in @a replaced */ static gboolean _e2_alias_match (E2_Alias *alias, gchar *str, gchar **replaced) { printd (DEBUG, "match alias for %s", str); regmatch_t pmatch; //CHECKME does this support utf ? gint ret = regexec (&alias->preg, str, 1, &pmatch, 0); gchar *tmp = NULL, *tmp2 = NULL; if (ret == REG_NOMATCH) return FALSE; else if (ret == 0) { if ((pmatch.rm_eo == -1) || (pmatch.rm_so == -1)) //the compiled string wasn't acually used ?? return FALSE; //make sure the whole of the command matches (ie avoid e.g. x matches xpdf) // if (*(str+pmatch.rm_eo) != ' ' && *(str+pmatch.rm_eo) != '\0') // return FALSE; //so we have a match, replace it if (alias->has_back_reference) { tmp = g_strdup (alias->replace); // printd (DEBUG, "match alias ended at offset %d", pmatch.rm_eo); //carve off the end of the source string if (pmatch.rm_eo > 0) { gchar *real_start = str + pmatch.rm_eo; // if (*real_start != '\0') // real_start--; //backup so we grab the (last) separator too tmp2 = e2_utils_str_replace (tmp, "\\2", real_start); } else tmp2 = e2_utils_str_replace (tmp, "\\2", ""); g_free (tmp); tmp = tmp2; /* //carve off any prior part of the source string //(can never happen while ^ at start) if (pmatch.rm_so > 0) { gchar *match = g_strndup (str, pmatch.rm_so); tmp2 = e2_utils_str_replace (tmp, "\\0", match); g_free (match); } else tmp2 = e2_utils_str_replace (tmp, "\\0", ""); g_free (tmp); tmp = tmp2; */ //build the replacement string with the alias in place gchar *match = g_strndup (str + pmatch.rm_so, pmatch.rm_eo - pmatch.rm_so); *replaced = e2_utils_str_replace (tmp, "\\1", match); g_free (tmp); g_free (match); } else //no back-ref { gchar *last = str + pmatch.rm_eo; //the compiled pattern included whitespace (if any) after the command. //we need to keep some of that, to separate any following parameter //so if not at end of pattern, backup to grab the last separator too if (*last != '\0') last--; /* if (pmatch.rm_so > 0) //see above - never pass this test { gchar *first = g_strndup (str, pmatch.rm_so); *replaced = g_strconcat (first, alias->replace, last, NULL); g_free (first); } else */ *replaced = g_strconcat (alias->replace, last, NULL); } return TRUE; } else { printd (DEBUG, "regex error"); return FALSE; } } /** @brief convert all alias data into a form ready for use This adds a '^' to the beginning of the alias if not there already (causing matching from the start) so that the config data doesn't need to show a '^'. This means that aliases can only work for a command, not for a part of a command after its start. To avoid matches like x <=> xpdf, a separator is added to the end of each alias. (This means that checks are needed to reinstate a separator in the replacement command string.) The match strings are 'compiled' as extended regex. Data structs are put into a list. @param rt pointer to alias data struct @param iter pointer to tree iter, used for option tree traversal @return */ static void _e2_alias_add_all (E2_AliasRuntime *rt, GtkTreeIter *iter) { do { gchar *match, *replace, *builder; gboolean stop; gtk_tree_model_get (rt->set->ex.tree.model, iter, 0, &match, 1, &stop, 2, &replace, -1); if ((match != NULL) && (*match != '\0')) { if (*match != '^') { builder = g_strconcat ("^", match, NULL); g_free (match); match = builder; } //add separator builder = g_strconcat (match, "([ \t]+|$)", NULL); g_free (match); match = builder; regex_t preg; if (!regcomp (&preg, match, REG_EXTENDED)) { E2_Alias *alias = _e2_alias_get (match, replace, preg, stop); rt->aliases = g_list_append (rt->aliases, alias); } else printd (WARN, "error compiling regexp pattern '%s'", match); } } while (gtk_tree_model_iter_next (rt->set->ex.tree.model, iter)); } /******************/ /***** public *****/ /******************/ /** @brief find a matching alias for @a str If the aliases have not been enlisted, then that is done first. The whole alias list is scanned in order, so that aliases can be chained, unless and until a match has its 'stop' flag set @param str utf string starting with a command for which there may be an alias @param rt pointer to aliases runtime data struct @return a newly-allocated string, either the matched alias, or the same as @a str if there is no match */ gchar *e2_alias_apply (gchar *str, E2_AliasRuntime *rt) { gchar *strcpy = g_strdup (str); //never touch the original string if (!rt->sync) { e2_alias_sync (rt); if (!rt->sync) return strcpy; } // gint cmdlen = strlen (str); gchar *ret = NULL; //no-match signal GList *list = g_list_first (rt->aliases); while (list != NULL) { if (_e2_alias_match (list->data, str, &ret)) //cmdlen, &ret)) { g_free (strcpy); strcpy = ret; E2_Alias *alias = list->data; if (alias->stop) break; } list = g_list_next (list); } if (ret == NULL) return strcpy; else return ret; } /** @brief refresh alias data Any aliases in the list are cleared, a replacement list is built from config data if possible, and the 'sync' flag is set accordingly @param rt pointer to aliases runtime data struct @return */ void e2_alias_sync (E2_AliasRuntime *rt) { printd (DEBUG, "e2_alias_sync (rt:)"); if (rt->aliases != NULL) { g_list_foreach (rt->aliases, (GFunc) _e2_alias_free, NULL); g_list_free (rt->aliases); rt->aliases = NULL; } GtkTreeIter iter; if (gtk_tree_model_get_iter_first (rt->set->ex.tree.model, &iter)) { _e2_alias_add_all (rt, &iter); rt->sync = TRUE; } else { printd (NOTICE, "no aliases defined"); rt->sync = FALSE; } } /** @brief install default tree options for aliases This function is called only if the default is missing from the config file @param set pointer to set data @return */ static void _e2_alias_tree_defaults (E2_OptionSet *set) { gchar *utf = F_FILENAME_FROM_LOCALE (DOC_DIR G_DIR_SEPARATOR_S MAIN_HELP); e2_option_tree_setup_defaults (set, g_strdup("command-aliases=<"), //internal name // NOW IN COMMAND INTERPRETER // g_strdup(" *[^>].*[<>\\|].*||>\\1"), //make sure re-directions and pipes are run in a new shell // g_strdup(";\\|\\\\|\\|>\\|<\\|\\*\\|=||%\\0\\1\\2"), //some terminals (eg gnome-terminal) don't support these //NB $TERM doesn't always exist, or might be invalid // g_strconcat(_("x"),"|true|$[command-xterm] -e sh -c '\\2'",NULL), // g_strconcat(_("xx"),"|true|$[command-xterm] -e sh -c '\\2;echo -n \"",_("Done. Press enter "),"\";read'",NULL), // g_strconcat(_("su"),"|true|$[command-xterm] -e sh -c 'su -c \\2;echo -n \"",_("Done. Press enter "),"\";read'",NULL), g_strconcat(_("x"),"|true|xterm -e sh -c '\\2'",NULL), g_strconcat(_("xx"),"|true|xterm -e sh -c '\\2;echo -n \"",_("Done. Press enter "),"\";read'",NULL), g_strconcat("su|true|xterm -e sh -c 'su -c \"\\2\";echo -n \"",_("Done. Press enter "),"\";read'",NULL), g_strdup("cp|true|cp -i \\2"), g_strdup("rm|true|rm -i \\2"), g_strconcat(_("clear"),"|true|",_A(9),".",_A(29),NULL), //< g_strconcat(_("quit"),"|true|",_A(1),".",_A(69),NULL), //command.quit //< g_strconcat(_("help"),"|true|",_A(5),".",_A(103)," ",utf," [commands]",NULL), //file.view_at _I( NOTE helpfile titles are not (yet?) translated g_strconcat(_("keys"),"|true|",_A(9),".",_A(50)," ",_("keys")," \\2",NULL), g_strconcat(_("e2ps"),"|true|",_A(7),".",_A(28),NULL), #ifdef E2_VFS g_strconcat(_("cns"),"|true|",_A(10),".",_A(117),NULL), //pane_active.namespace #endif g_strdup("wget|true|\\1 --progress=bar:force \\2"), g_strdup(">"), NULL); F_FREE (utf); } /** @brief initialize alias-related options, and init some alias flags @return */ void e2_alias_init (void) { E2_AliasRuntime *rt = &app.aliases; rt->aliases = NULL; //moved up from end rt->sync = FALSE; //this option does not require a rebuild after config change gchar *group_name = g_strconcat(_C(6),".",_C(0),NULL); //_("commands.aliases" printd (DEBUG, "e2_alias_init (rt:,group:%s)", group_name); rt->set = e2_option_tree_register ("command-aliases", group_name, _C(0), NULL, NULL, NULL, E2_OPTION_TREE_UP_DOWN | E2_OPTION_TREE_ADD_DEL | E2_OPTION_TREE_LIST, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP | E2_OPTION_FLAG_BUILDALIAS); e2_option_tree_add_column (rt->set, _("Match"), E2_OPTION_TREE_TYPE_STR, 0, "", 0, NULL, NULL); //this option stops alias-scanning when 1st match found e2_option_tree_add_column (rt->set, _("Stop"), E2_OPTION_TREE_TYPE_BOOL, FALSE, "false", 0, NULL, NULL); e2_option_tree_add_column (rt->set, _("Replace"), E2_OPTION_TREE_TYPE_STR, 0, "", 0, NULL, NULL); e2_option_tree_create_store (rt->set); e2_option_tree_prepare_defaults (rt->set, _e2_alias_tree_defaults); } /** @brief cleanup alias-related data @return */ void e2_alias_clean (void) { E2_AliasRuntime *rt = &app.aliases; if (rt->aliases != NULL) { g_list_foreach (rt->aliases, (GFunc) _e2_alias_free, NULL); g_list_free (rt->aliases); rt->aliases = NULL; } } emelfm2-0.4.1/src/command/e2_command.h0000600000175000017500000000635511010340377016376 0ustar cairocairo/* $Id: e2_command.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_COMMAND_H__ #define __E2_COMMAND_H__ #include "emelfm2.h" #include "e2_output.h" //enable queueing of entered commands //#define E2_COMMANDQ //flags governing the way a command is interpreted and run typedef enum { E2_COMMAND_RANGE_DEFAULT = 1, E2_COMMAND_RANGE_FILE_ACTION = 1 << 1, E2_COMMAND_RANGE_FILE_OPERATION = 1 << 2, //add names of selected items E2_COMMAND_RANGE_SUBSHELL = 1 << 3, E2_COMMAND_RANGE_TOCHILD = 1 << 4, //send string to last-started child E2_COMMAND_RANGE_CUSTOM = 1 << 5 } E2_CommandRange; typedef struct _E2_CommandTaskData { VOLATILE gchar *command; //command string VOLATILE gchar *currdir; //the CWD when command is initiated (utf-8) #ifdef E2_COMMANDQ //data to allow later interpretation (for %D, %f, %F etc) and exection VOLATILE gchar *othrdir; //the inactive pane dir when command is initiated (utf-8) # ifdef E2_VFS VOLATILE PlaceInfo *currspace; VOLATILE PlaceInfo *othrspace; # endif VOLATILE GPtrArray *names; //selected items array VOLATILE GPtrArray *othernames; //selected items array VOLATILE E2_CommandRange range; #endif #ifdef E2_NEW_COMMAND VOLATILE gint child_stdin_fd; //pipe file descriptors, parent-side VOLATILE gint child_stdout_fd; VOLATILE gint child_stderr_fd; VOLATILE gboolean sync; //TRUE for synchronously-run command #else VOLATILE GIOChannel *to_child; //channel used for messages to an async child (= child's stdin) #endif VOLATILE gboolean extshell; //TRUE for command run in external shell VOLATILE gboolean show; //TRUE to display start and end messages for the command VOLATILE gint exit; //command exit code, 0 = success } E2_CommandTaskData; gboolean e2_command_clear_pending (gpointer from, E2_ActionRuntime *art); gboolean e2_command_find_process (guint pid); gint e2_command_run_at (gchar *command, const gchar *workdir, E2_CommandRange range); //gint e2_command_run (gchar *command, E2_CommandRange range); #define e2_command_run(c,r) e2_command_run_at(c,NULL,r) guint e2_command_count_running_tasks (gboolean countcmds, gboolean countpaused); void e2_command_retab_children (E2_OutputTabRuntime *currenttab, E2_OutputTabRuntime *replacetab); void e2_command_retab2_children (E2_OutputTabRuntime *currenttab, E2_OutputTabRuntime *replacetab); gboolean e2_command_kill_child (guint pid); const gchar *e2_command_get_variable_value (gchar *var_name); void e2_command_output_help (void); void e2_command_actions_register (void); void e2_command_options_register (void); #endif //ndef __E2_COMMAND_H__ emelfm2-0.4.1/src/command/e2_alias.h0000600000175000017500000000251311010340377016041 0ustar cairocairo/* $Id: e2_alias.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelfm2. emelfm2 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 3, or (at your option) any later version. emelfm2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_ALIAS_H__ #define __E2_ALIAS_H__ #include "emelfm2.h" #include #include "e2_option.h" typedef struct _E2_Alias { gchar *match; gchar *replace; regex_t preg; gboolean stop; gboolean has_back_reference; gboolean stop_on_success; } E2_Alias; typedef struct _E2_AliasRuntime { GList *aliases; E2_OptionSet *set; gboolean sync; } E2_AliasRuntime; gchar *e2_alias_apply (gchar *str, E2_AliasRuntime *rt); void e2_alias_sync (E2_AliasRuntime *rt); void e2_alias_init (void); void e2_alias_clean (void); #endif //ndef __E2_ALIAS_H__ emelfm2-0.4.1/src/command/e2_command_line.h0000600000175000017500000000402311010340377017373 0ustar cairocairo/* $Id: e2_command_line.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/command/e2_command_line.h @brief command line header This is the header file for the command line functions. */ #ifndef __E2_COMMAND_LINE_H__ #define __E2_COMMAND_LINE_H__ #include "emelfm2.h" #include "e2_pane.h" typedef enum { E2_COMMAND_LINE_ORIGINAL = 1<<0, E2_COMMAND_LINE_DIR_LINE = 1<<1 } E2_CommandLineFlags; typedef struct _E2_CommandLineRuntime { gchar *name; //this is a generated 'internal' name, not a 'public' one gpointer func; E2_CommandLineFlags flags; gboolean original; //TRUE for command entry(ies), FALSE for dir lines E2_PaneRuntime *pane; //was gpointer data, only used for dir lines; GtkWidget *cl; GList *history; GtkTreeModel *model; E2_OptionSet *opt_history_last; } E2_CommandLineRuntime; void e2_command_line_cd_activated_cb (GtkWidget *entry, E2_CommandLineRuntime *rt); E2_CommandLineRuntime *e2_command_line_create (gchar *name_default, gpointer cl_data, gpointer activation_cb, E2_CommandLineFlags flags); void e2_command_line_destroy (E2_CommandLineRuntime *rt); void e2_command_line_clean (void); void e2_command_line_change_dir (gchar *utfpath, E2_PaneRuntime *rt); void e2_command_line_actions_register (void); void e2_command_line_options_register (void); #endif // ndef __E2_COMMAND_LINE_H__ emelfm2-0.4.1/src/command/e2_command_line.c0000600000175000017500000010311110776374631017406 0ustar cairocairo/* $Id: e2_command_line.c 853 2008-04-07 10:38:17Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/command/e2_command_line.c @brief command line functions This file contains functions for e2's command and dir lines. */ #include #include #include "e2_command_line.h" #include "e2_complete.h" #include "e2_filelist.h" #include "e2_task.h" //list of names of command and dir lines, used to ensure //that each command- or dir-line has a unique name static GList *line_names = NULL; extern GList *children; extern const gchar *shellcmd; extern pthread_mutex_t task_mutex; /*****************/ /***** utils *****/ /*****************/ /** @brief sync command line tree model history with cachable glist history This is called in the process of destroying a command-line (eg when rebuilding toolbars) and when session is ending @param rt pointer to command line runtime data struct @return */ static void _e2_command_line_sync_history (E2_CommandLineRuntime *rt) { //free old history if (rt->history != NULL) e2_list_free_with_data (&rt->history); GtkTreeIter iter; //fill with new state //CHECKME how long does the model exist when the widget is being destroyed ? if (gtk_tree_model_get_iter_first (rt->model, &iter)) { gint count = 0; gint max = (rt->original) ? e2_option_int_get ("command-line-history-max"): e2_option_int_get ("dir-line-history-max"); gchar *value; do { gtk_tree_model_get (rt->model, &iter, 0, &value, -1); rt->history = g_list_append (rt->history, value); if (++count == max) break; } while (gtk_tree_model_iter_next (rt->model, &iter)); } } /** @brief check if strings @a arg1 and @a arg2 match @param arg1 string data for member of glist being scanned @param arg2 string to be found in the glist @return 0 if strings @a arg1 and @a arg2 are the same */ static gint _e2_command_line_match_list_str (const gchar *arg1, const gchar *arg2) { return (!g_str_equal (arg1, arg2)); } /** @brief find commandline by its name @a option @param option name of commandline @param def @return */ static gchar *_e2_command_line_find_name (gchar *option, gchar *def) { if ((option == NULL) || (*option == '\0')) option = def; gchar *name = NULL; GList *tmp = g_list_find_custom (line_names, option, (GCompareFunc)_e2_command_line_match_list_str); if (tmp == NULL) name = g_strdup (option); else { gint turn = 2; while (tmp != NULL) { g_free (name); name = g_strdup_printf ("%s %d", option, turn++); tmp = g_list_find_custom (line_names, name, (GCompareFunc)_e2_command_line_match_list_str); } } return name; } /** @brief set command-line text to last-used history value, if required, or to "" @param rt pointer to command line runtime data struct @return */ static void _e2_command_line_update_entry (E2_CommandLineRuntime *rt) { GtkWidget *entry = GTK_BIN (rt->cl)->child; if (e2_option_bool_get_direct (rt->opt_history_last)) { GtkTreeIter iter; if (gtk_tree_model_get_iter_first (rt->model, &iter)) { e2_combobox_set_active (rt->cl, 0); gtk_editable_set_position (GTK_EDITABLE (entry), -1); } } else gtk_entry_set_text (GTK_ENTRY (entry), ""); } /** @brief get pointer to rt data for first-recorded command line (as opposed to dir line) This does nothing if @a rt does not point to NULL @param rt pointer to location to store command line runtime data struct pointer @return */ static void _e2_command_line_get_first (E2_CommandLineRuntime **rt) { if (*rt == NULL) { // if (app.command_lines == NULL) // return; //no command lines registered // else // { //find the first-registered command-line E2_CommandLineRuntime *cl_rt; GList *member; for (member = app.command_lines; member != NULL ; member = g_list_next (member)) { cl_rt = (E2_CommandLineRuntime *) member->data; if (cl_rt->original) { *rt = cl_rt; break; } } // } } } /** @brief determine command line pointer(s) This function tries to find the corresponding entry widget if @a rt != NULL Or if @a rt is NULL, tries to find the @a rt corresponding to @a widget. But then, if @a entry is not actually an entry widget, @a rt is just set to the one for the first-registered command line (as opposed to dir line) if any @param widget pointer to widget to check/set (may be NULL) @param rt pointer to the command line runtime object to check/set, or to NULL @return TRUE if @a entry and @a rt are set to valid values */ static gboolean _e2_command_line_get (GtkWidget **widget, E2_CommandLineRuntime **rt) { //the runtime object has precedence if (*rt == NULL) { //if the entry really is an entry, get the pointer to the //runtime object if ((*widget != NULL) && (GTK_IS_ENTRY (*widget))) { *rt = g_object_get_data (G_OBJECT (*widget), "command-line-runtime"); if (*rt != NULL) return TRUE; else { E2_CommandLineRuntime *clrt; GList *line; for (line = app.command_lines; line != NULL ; line = g_list_next (line)) { clrt = line->data; if (GTK_BIN (clrt->cl)->child == *widget) break; } if (line != NULL) { *rt = clrt; return TRUE; } } } else //can't find anything from widget, just try to find the first available command line _e2_command_line_get_first (rt); if (*rt == NULL) //OOPS can't find anything !! return FALSE; } *widget = GTK_BIN ((*rt)->cl)->child; //set the real entry widget return TRUE; } /*********************/ /***** callbacks *****/ /*********************/ /** @brief change-dir hook function This is initiated with BGL closed/on @param path utf-8 string, path of the dir that is now current @param rt pointer to command line runtime data struct @return TRUE always */ //static gboolean static void _e2_command_line_change_dir_hook (gchar *path, E2_CommandLineRuntime *rt) { GtkWidget *entry = GTK_BIN (rt->cl)->child; gtk_entry_set_text (GTK_ENTRY (entry), path); e2_combobox_activated_cb (entry, GINT_TO_POINTER (e2_option_bool_get ("dir-line-history-double"))); if (!e2_option_bool_get_direct (rt->opt_history_last)) gtk_entry_set_text (GTK_ENTRY (entry), ""); if (e2_option_bool_get ("dir-line-pathname-hint")) #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif entry, path); // return TRUE; } /** @brief mouse button-press callback This triggers a directory selection dialog when the middle-button is pressed while the mouse is on a directory-line @param entry the entry widget where the button was pressed @param event pointer to gtk event data struct @param rt pointer to pane runtime data struct @return TRUE if the middle button was pressed */ static gboolean _e2_command_line_button_press_cb (GtkWidget *entry, GdkEventButton *event, E2_PaneRuntime *rt) { // curr_pane->focus_widget = entry; if (event->button == 2) return (e2_pane_goto_choice (rt, entry)); return FALSE; } /** @brief autocomplete dir-line entries when dir is mounted-local This is a(nother) callback for dir-line key-press-event signals @param entry pointer to the widget that received the keypress @param event event data struct @param rt pointer to data struct for the dir-line @return TRUE if the key was non-modified and a textkey, and triggered completion */ static gboolean _e2_command_line_key_press_cb (GtkWidget *entry, GdkEventKey *event, E2_CommandLineRuntime *rt) { if ((event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)) == 0 //#ifdef USE_GTK2_10 | GDK_SUPER_MASK | GDK_HYPER_MASK | GDK_META_MASK && (event->keyval < 0xF000 || event->keyval > 0xFFFF)) { guint pane = (rt->pane == &app.pane1) ? E2PANE1 : E2PANE2; if (e2_fs_complete_dir (entry, event->keyval, pane)) return TRUE; } return FALSE; } /**********************/ /***** activation *****/ /**********************/ /** @brief perform command entered in command line This is a(nother) "activate" signal callback for (each) command line entry @param entry widget containing command to run @param rt pointer to command line runtime data struct @return */ static void _e2_command_line_command_activated_cb (GtkWidget *entry, E2_CommandLineRuntime *rt) { printd (DEBUG, "_e2_command_line_default_cb (entry:,rt:)"); const gchar *command_raw = gtk_entry_get_text (GTK_ENTRY (entry)); //quick exit if ((command_raw == NULL) || (*command_raw == '\0')) return; #ifdef E2_COMMANDQ e2_command_run ((gchar *)command_raw, E2_COMMAND_RANGE_DEFAULT, FALSE); #else e2_command_run ((gchar *)command_raw, E2_COMMAND_RANGE_DEFAULT); #endif _e2_command_line_update_entry (rt); } /** @brief open directory entered in dir line This is a(nother) "activate" signal callback for a directory line entry @param entry widget containing directory string @param rt pointer to command line runtime data struct @return */ void e2_command_line_cd_activated_cb (GtkWidget *entry, E2_CommandLineRuntime *rt) { printd (DEBUG, "e2_command_line_directory_cb (entry:,rt:)"); const gchar *command_raw = gtk_entry_get_text (GTK_ENTRY (entry)); //quick exit if ((command_raw == NULL) || (*command_raw == '\0')) return; gchar *dir = e2_utf8_unescape (command_raw, ' '); //E2_VFSTMPOK no need to handle any URI here E2_PaneRuntime *pane_rt = rt->pane; e2_pane_change_dir (pane_rt, dir); if (e2_option_bool_get ("dir-line-focus-after-activate")) { if (pane_rt == other_pane) e2_pane_activate_other (); else gtk_widget_grab_focus (pane_rt->view->treeview); } g_free (dir); } /*******************/ /***** actions *****/ /*******************/ /** @brief focus the first-logged command line @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if the line is found */ static gboolean _e2_command_line_focus_action (gpointer from, E2_ActionRuntime *art) { E2_CommandLineRuntime *rt = NULL; _e2_command_line_get_first (&rt); //get the first line if (rt == NULL) return FALSE; gtk_editable_set_position (GTK_EDITABLE (GTK_BIN (rt->cl)->child), -1); gtk_widget_grab_focus (GTK_BIN (rt->cl)->child); return TRUE; } /** @brief toggle focus between first-logged command line and active pane @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if the line is found */ static gboolean _e2_command_line_focus_toggle_action (gpointer from, E2_ActionRuntime *art) { E2_CommandLineRuntime *rt = NULL; _e2_command_line_get_first (&rt); //gets the first line if (rt == NULL) return FALSE; if (GTK_WIDGET_HAS_FOCUS (GTK_BIN (rt->cl)->child)) gtk_widget_grab_focus (curr_view->treeview); else { gtk_editable_set_position (GTK_EDITABLE (GTK_BIN (rt->cl)->child), -1); gtk_widget_grab_focus (GTK_BIN (rt->cl)->child); } return TRUE; } /** @brief clear contents of an entry, generally @a from @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_command_line_clear_action (gpointer from, E2_ActionRuntime *art) { //from may be entry, button, menuitem etc GtkWidget *entry = (from == NULL) ? NULL : GTK_WIDGET (from); if (from == NULL || ! GTK_IS_ENTRY (from)) { //try to find the corresponding commandline E2_CommandLineRuntime *rt = NULL; _e2_command_line_get (&entry, &rt); } gtk_entry_set_text (GTK_ENTRY (entry), ""); return TRUE; } /** @brief clear command line history @param from the entry which was activated @param art action runtime data @return TRUE if there was a history */ static gboolean _e2_command_line_clear_history_action (gpointer from, E2_ActionRuntime *art) { GtkWidget *entry = (from == NULL) ? NULL : GTK_WIDGET (from); //find the command line for the entry E2_CommandLineRuntime *rt = NULL; _e2_command_line_get (&entry, &rt); if (rt->history != NULL) { gint num = g_list_length (rt->history); for (; num > 0; num--) gtk_combo_box_remove_text (GTK_COMBO_BOX (rt->cl), 0); e2_list_free_with_data (&rt->history); g_object_unref (rt->model); return TRUE; } return FALSE; } /** @brief insert name of each active-pane selected item into @a entry @param data UNUSED pointer to action runtime data, NULL @param arg action argument string @param entry entry for command/dir line to be processed @return */ static gboolean _e2_command_line_insert_action (gpointer from, E2_ActionRuntime *art) { gchar *arg = (gchar *)art->data; //find out from the action argument if the user wants quotes/escaping g_strstrip (arg); gchar *str_down = g_utf8_strdown (arg, -1); gboolean escape = g_str_equal (arg, _A(107)); //_("escape" gboolean quote = (escape) ? FALSE : g_str_equal (arg, _A(112)); //_("quote" g_free (str_down); #ifdef E2_REFRESH_DEBUG printd (DEBUG, "disable refresh, insert action"); #endif e2_filelist_disable_refresh (); gchar *utf; GString *cmd = g_string_sized_new (128); GList *tmp, *base; base = tmp = e2_fileview_get_selected_local (curr_view); for (; tmp != NULL; tmp=tmp->next) { utf = F_FILENAME_FROM_LOCALE (((FileInfo *) tmp->data)->filename); //not DISPLAY if (escape) { gchar *temp = e2_utf8_escape (utf, ' '); F_FREE (utf); utf = temp; } cmd = g_string_append_c (cmd, ' '); if (quote) g_string_append_printf (cmd, "\"%s\"", utf); else cmd = g_string_append (cmd, utf); if (escape) g_free (utf); else F_FREE (utf); } GtkWidget *entry = (from == NULL) ? NULL : GTK_WIDGET (from); E2_CommandLineRuntime *rt = NULL; _e2_command_line_get (&entry, &rt); gint cursor = gtk_editable_get_position (GTK_EDITABLE (entry)); gtk_editable_insert_text (GTK_EDITABLE (entry), cmd->str, cmd->len, &cursor); gtk_editable_set_position (GTK_EDITABLE (entry), cursor); g_string_free (cmd, TRUE); g_list_free (base); #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, insert action"); #endif e2_filelist_enable_refresh(); return TRUE; } /** @brief complete the string in the command line associated with @a from This is the mechanism for tab-completion, via a keybinding to the relevant widget @param from a pointer to the entry widget whose cntents are to be completed, or NULL @param art pointer to action rt data @return */ static gboolean _e2_command_line_complete_action (gpointer from, E2_ActionRuntime *art) { //find which command line is active GtkWidget *entry = (from == NULL) ? NULL : GTK_WIDGET (from); E2_CommandLineRuntime *rt = NULL; _e2_command_line_get (&entry, &rt); if (rt == NULL) return FALSE; //can't find what to work on gint pos = gtk_editable_get_position (GTK_EDITABLE (entry)); //characters, not bytes gchar *text = g_strdup (gtk_entry_get_text (GTK_ENTRY (entry))); //could be "" gchar *arg = (gchar *)art->data; printd (DEBUG, "complete_action (rt:_,arg:%s,entry:_", arg); //find out what type of completion is wanted and relevant E2_CompleteFlags flags; if (!rt->original) flags = E2_COMPLETE_FLAG_DIRS; //dir-lines just find dirs else { flags = E2_COMPLETE_FLAG_PATH; //command-lines default to finding executables if (g_strstr_len (text, sizeof("mount") + 3,"mount") != NULL) //_I( encoding ok ? flags |= E2_COMPLETE_FLAG_MOUNT; //BUT completion might be for command argument if (*text != '\0') { gchar *s = e2_utils_pass_whitespace (text); if (s != NULL) { gint i, p = pos - (s - text); //whitespace chars are all 1-byte for (i = 0; i < p; i++) { //FIXME handle quoted whitepace in a path string if (s[i] == ' ' || s[i] == '\t') { //gap before cursor means we are completing an argument flags &= ~E2_COMPLETE_FLAG_PATH; g_strstrip (arg); // gchar *str_down = g_utf8_strdown (arg, -1); if (strstr (arg, _A(106)) != NULL) flags |= E2_COMPLETE_FLAG_DIRS; if (strstr (arg, _A(109)) != NULL) flags |= E2_COMPLETE_FLAG_FILES; if (strstr (arg, _("mounts")) != NULL) //_I("mounts") flags |= E2_COMPLETE_FLAG_MOUNT; if (strstr (arg, _("all")) != NULL) //_I("all") flags |= E2_COMPLETE_FLAG_ALL; if (flags == 0) flags = E2_COMPLETE_FLAG_ALL; // g_free (str_down); break; } } } } } GList *found = NULL; gint num = e2_complete_str (&text, &pos, &found, flags, 0); // printd (DEBUG, "found %d - %s - %d", num, text, pos); if (num > 0) { // if it isn't a dir: append a if ((e2_option_bool_get ("command-line-complete-append-space")) && (num == 1) && (g_utf8_get_char (g_utf8_offset_to_pointer (text, pos - 1)) != G_DIR_SEPARATOR)) { gchar *part1 = e2_utf8_ndup (text, pos); gchar *part2 = g_utf8_offset_to_pointer (text, pos); gchar *text_spaced = g_strconcat (part1, " ", *part2 != '\0' ? part2 : "" , NULL); g_free (part1); gtk_entry_set_text (GTK_ENTRY (entry), text_spaced); gtk_editable_set_position (GTK_EDITABLE (entry), pos +1); g_free (text_spaced); } else { gtk_entry_set_text (GTK_ENTRY (entry), text); gtk_editable_set_position (GTK_EDITABLE (entry), pos); } if (num > 1) { GList *tmp; for (tmp = found; tmp != NULL; tmp = g_list_next (tmp)) { e2_output_print (&app.tab, (gchar *)tmp->data, NULL, TRUE, NULL); } e2_output_print_end (&app.tab, FALSE); } } e2_list_free_with_data (&found); g_free (text); return TRUE; } /** @brief children-menu callback, prepends a pid string to the command line @param item the selected menu item @param rt pointer to data for the command related to @a item, or NULL for "no children" item @return */ static void _e2_commmand_line_child_menu_cb (GtkWidget *item, E2_TaskRuntime *rt) { if (rt == NULL) //rt == NULL for a menu item "no children" return; pthread_mutex_lock (&task_mutex); GList *member = g_list_find (app.taskhistory, rt); pthread_mutex_unlock (&task_mutex); if (member != NULL) //command data still exists { E2_CommandLineRuntime *clrt = NULL; _e2_command_line_get_first (&clrt); //in principle, there could be > 1 OK ?? if (clrt != NULL) { GtkWidget *entry = GTK_BIN (clrt->cl)->child; gint newlen; gint oldcursor = gtk_editable_get_position (GTK_EDITABLE (entry)); GdkModifierType mask = e2_utils_get_modifiers (); if (oldcursor == 0 || (mask & GDK_CONTROL_MASK)) { gint newpos = 0; gchar *newtext = g_strconcat (rt->pidstr, ":", NULL); newlen = strlen (newtext); gtk_editable_insert_text (GTK_EDITABLE (entry), newtext, newlen, &newpos); g_free (newtext); } else { newlen = strlen (rt->pidstr); gtk_editable_insert_text (GTK_EDITABLE (entry), rt->pidstr, newlen, &oldcursor); } gtk_editable_set_position (GTK_EDITABLE (entry), oldcursor+newlen); if (rt->status != E2_TASK_RUNNING) { gchar *msg = g_strdup_printf (_("Warning - process %s is not active"), rt->pidstr); e2_output_print (&app.tab, msg, NULL, TRUE, NULL); g_free (msg); } } } } /** @brief create and pop up a menu of child processes, in response to a toolbar button-click This essentially gets data for prepending to a command line @param action_data ptr to data assigned when action struct created at session start NULL @param rt_data data suppplied to the action @param widget the thing that was activated to popup the menu @return TRUE if the action succeeded */ static gboolean _e2_commmand_line_insert_child_pid (gpointer from, E2_ActionRuntime *art) { GtkWidget *menu = e2_menu_create_child_menu (E2_CHILD_ACTIVE, _e2_commmand_line_child_menu_cb); //determine the menu's popup position if (g_str_equal (IS_A (from), "GtkButton")) gtk_menu_popup (GTK_MENU (menu), NULL, NULL, (GtkMenuPositionFunc) e2_toolbar_set_menu_position, GTK_WIDGET (from), 1, 0); else gtk_menu_popup (GTK_MENU (menu), NULL, NULL, (GtkMenuPositionFunc) e2_output_set_menu_position, app.tab.text, 0, 0); return TRUE; } /******************/ /***** public *****/ /******************/ /** @brief create a commandline or dir-line This creates a command line runtime struct and a command line widget. The @a name_runtime parameter may be the same for several command lines as a unique name is created at runtime from it. @param name_default the default name e.g. "dir line" in case the runtime name is NULL or empty @param cl_data pointer to pane rt struct for a dir line, or NULL for a command line @param activation_cb a(nother) callabck for any "activate" signal from the command line entry @param flags commmand line flags @return pointer to the created runtime data struct */ E2_CommandLineRuntime *e2_command_line_create (gchar *name_default, gpointer cl_data, gpointer activation_cb, E2_CommandLineFlags flags) { E2_CommandLineRuntime *rt = ALLOCATE (E2_CommandLineRuntime); CHECKALLOCATEDFATAL (rt); app.command_lines = g_list_append (app.command_lines, rt); //get unique name for this line //BUT don't want 'public' name for cache entry - just use the default rt->name = _e2_command_line_find_name (name_default, "default cmd line"); //append name to local name register to ensure uniqueness of each name line_names = g_list_append (line_names, rt->name); rt->func = activation_cb; rt->flags = flags; rt->original = (flags & E2_COMMAND_LINE_ORIGINAL); //TRUE for commandline, FALSE for dirline rt->pane = (E2_PaneRuntime *) cl_data; //pane rt for dirline, NULL for commandline rt->history = NULL; E2_Cache *cache = e2_cache_list_register (rt->name, &rt->history); cache->sync_func = _e2_command_line_sync_history; cache->sync_data = rt; //now, create the widget E2_ComboBoxFlags flags2 = E2_COMBOBOX_HAS_ENTRY; //sort out some options for command line or dir line; //the options have been registered in the general init function if (rt->original) { //this is a command line if (e2_option_bool_get ("command-line-history-double")) flags2 |= E2_COMBOBOX_ALLOW_DOUBLE; if (e2_option_bool_get ("command-line-history-cycle")) flags2 |= E2_COMBOBOX_CYCLE_HISTORY; if (e2_option_bool_get ("command-line-menu-style")) flags2 |= E2_COMBOBOX_MENU_STYLE; flags2 |= E2_COMBOBOX_FOCUS_ON_CHANGE; //we need to access this often, thus we save the pointer to the //option set and will not have to look it up in the hashtable //each time rt->opt_history_last = e2_option_get ("command-line-history-last"); } else { //this is a dir line if (e2_option_bool_get ("dir-line-history-double")) flags2 |= E2_COMBOBOX_ALLOW_DOUBLE; if (e2_option_bool_get ("dir-line-history-cycle")) flags2 |= E2_COMBOBOX_CYCLE_HISTORY; if (e2_option_bool_get ("dir-line-menu-style")) flags2 |= E2_COMBOBOX_MENU_STYLE; rt->opt_history_last = e2_option_get ("dir-line-history-last"); } //actually create the comboboxentry and its history rt->cl = e2_combobox_get (activation_cb, rt, &rt->history, flags2); //allow the entry to find its rt data g_object_set_data (G_OBJECT (GTK_BIN (rt->cl)->child), "command-line-runtime", rt); rt->model = gtk_combo_box_get_model (GTK_COMBO_BOX (rt->cl)); //model is for a iiststore g_object_ref (rt->model); //select most-recent history entry if necessary if ((!rt->original) || (e2_option_bool_get ("command-line-history-last"))) { if (e2_combobox_has_history (GTK_COMBO_BOX (rt->cl))) { //suspend "activate" signals connected when combo was created if (!(flags2 & E2_COMBOBOX_NO_AUTO_HISTORY)) g_signal_handlers_block_matched ( G_OBJECT (GTK_BIN (rt->cl)->child), G_SIGNAL_MATCH_FUNC, 0, 0, NULL, e2_combobox_activated_cb, NULL); if (activation_cb != NULL) g_signal_handlers_block_matched ( G_OBJECT (GTK_BIN (rt->cl)->child), G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL, activation_cb, rt); gtk_combo_box_set_active (GTK_COMBO_BOX (rt->cl), 0); if (!(flags2 & E2_COMBOBOX_NO_AUTO_HISTORY)) g_signal_handlers_unblock_matched ( G_OBJECT (GTK_BIN (rt->cl)->child), G_SIGNAL_MATCH_FUNC, 0, 0, NULL, e2_combobox_activated_cb, NULL); if (activation_cb!= NULL) g_signal_handlers_unblock_matched ( G_OBJECT (GTK_BIN (rt->cl)->child), G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL, activation_cb, rt); } } gchar *bindingname; if (rt->original) { //this is a command line if (e2_option_bool_get ("command-line-show-output-on-focus-in")) g_signal_connect (G_OBJECT (GTK_BIN (rt->cl)->child), "focus-in-event", G_CALLBACK (e2_window_output_show), NULL); if (e2_option_bool_get ("command-line-hide-output-on-focus-out")) g_signal_connect (G_OBJECT (GTK_BIN (rt->cl)->child), "focus-out-event", G_CALLBACK (e2_window_output_hide), NULL); bindingname = g_strconcat (_C(17),".",_C(5),NULL); //_(general.command-line" } else { //this is a dir line g_signal_connect (G_OBJECT (GTK_BIN (rt->cl)->child), "key-press-event", G_CALLBACK (_e2_command_line_key_press_cb), rt); g_signal_connect (G_OBJECT (GTK_BIN (rt->cl)->child), "button-press-event", G_CALLBACK (_e2_command_line_button_press_cb), rt->pane); //register to change_dir hook for dir-lines so we always know the //latest directory to add it to the history and show it if wanted // gboolean DEBUGhookhere; // e2_hook_register (&rt->pane->hook_change_dir, // _e2_command_line_change_dir_hook, rt); bindingname = g_strconcat (_C(17),".",_C(12),NULL); //_(general.dir-line" } e2_keybinding_register (bindingname, GTK_BIN (rt->cl)->child); g_free (bindingname); return rt; } /* * @brief @param rt pointer to command line runtime data struct @return the commandline widget */ //static GtkWidget *_e2_command_line_create (E2_CommandLineRuntime *rt) //{ // return GTK_WIDGET (rt->cl); //} /** @brief clear runtime data for a command/dir line This is the 'destroy data' function supplied when command lines are created. It will sync the history and free all internal memory. @param rt the runtime object to destroy @return */ void e2_command_line_destroy (E2_CommandLineRuntime *rt) { line_names = g_list_remove (line_names, rt->name); /* if (rt->original) { //line is a command-line GList *list; for (list = app.command_lines; list != NULL ; list = g_list_next (list)) { if (list->data == rt) { app.command_lines = g_list_delete_link (app.command_lines, list); break; } } } else */ app.command_lines = g_list_remove (app.command_lines, rt); /* gboolean DEBUGhookhere; if (!rt->original) { //line is a dir line E2_PaneRuntime *pane_rt = rt->pane; e2_hook_unregister (&pane_rt->hook_change_dir, _e2_command_line_change_dir_hook, rt, TRUE); } */ _e2_command_line_sync_history (rt); //CHECKME a cache-unregister sync function? //now we can unregister e2_cache_unregister (rt->name); e2_list_free_with_data (&rt->history); g_free (rt->name); //and the combo box data model may be freed g_object_unref (rt->model); DEALLOCATE (E2_CommandLineRuntime, rt); } /** @brief clear runtime data for all command/dir lines @return */ void e2_command_line_clean (void) { GList *member; for (member = app.command_lines; member != NULL; member = member->next) DEALLOCATE (E2_CommandLineRuntime, (E2_CommandLineRuntime *)member->data); } /** @brief change-dirline contents function This is initiated with BGL closed/on @param path utf-8 string, path of the dir that is being opened, with trailer @param rt pointer to data struct for the pane being changed @return */ void e2_command_line_change_dir (gchar *utfpath, E2_PaneRuntime *rt) { GList *member; for (member = app.command_lines; member != NULL; member = member->next) { E2_CommandLineRuntime *cl = (E2_CommandLineRuntime *)member->data; if (cl->pane == rt) { _e2_command_line_change_dir_hook (utfpath, cl); break; } } } /** @brief initialize command line actions This function initializes the command line actions. It is, and should only be, called once at startup. @return */ void e2_command_line_actions_register (void) { gchar *action_name = g_strconcat(_A(1),".",_A(23),NULL); e2_action_register (action_name, E2_ACTION_TYPE_COMMAND_LINE, _e2_command_line_command_activated_cb, "command line", TRUE, //name not translated E2_ACTION_EXCLUDE_MENU | E2_ACTION_EXCLUDE_ACCEL, NULL); action_name = g_strconcat(_A(1),".",_A(42),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_command_line_focus_action, NULL, FALSE); action_name = g_strconcat(_A(1),".",_A(43),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_command_line_focus_toggle_action, NULL, FALSE); action_name = g_strconcat(_A(1),".",_A(29),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_command_line_clear_action, NULL, FALSE); action_name = g_strconcat(_A(1),".",_A(30),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_command_line_clear_history_action, NULL, FALSE); action_name = g_strconcat(_A(1),".",_A(31),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_command_line_complete_action, NULL, FALSE); action_name = g_strconcat(_A(1),".",_A(53),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_command_line_insert_action, NULL, FALSE); action_name = g_strconcat(_A(28),".",_A(24),NULL); e2_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2_commmand_line_insert_child_pid, NULL, FALSE, E2_ACTION_EXCLUDE_MENU, NULL); } /** @brief initialize command line options This function initializes the options for the various command lines. It is, and should only be, called once at startup. @return */ void e2_command_line_options_register (void) { //none of these require a rebuild after change //command line gchar *group_name = g_strconcat(_C(6),".",_C(5),":",_C(25),NULL); //_("commands.command line:misc" e2_option_bool_register ("command-line-history-last", group_name, _("show last"), _("If activated, the last-entered command will be displayed, instead of an empty line"), NULL, FALSE, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP); group_name = g_strconcat(_C(6),".",_C(5),":",_C(18),NULL); //_("commands.command line:history" e2_option_int_register ("command-line-history-max", group_name, _("maximum number of history entries"), _("This is the largest number of command-line history entries that will be recorded"), NULL, 10, 0, 9999999, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP); e2_option_bool_register ("command-line-history-double", group_name, _("double entries"), _("This allows entries to be recorded more than once in the history list, so the last entry is always close to hand"), NULL, FALSE, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_BUILDBARS); e2_option_bool_register ("command-line-history-cycle", group_name, _("cyclic list"), _("When scanning the history list, cycle from either end around to the other end, instead of stopping"), NULL, TRUE, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_BUILDBARS); e2_option_bool_register ("command-line-menu-style", group_name, _("show as a menu"), _("If activated, the history entries will be presented as a menu. " "For most Gtk+2 themes, this will not be as attractive as the list view"), NULL, FALSE, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_BUILDBARS); group_name =g_strconcat(_C(6),".",_C(5),":",_C(38),NULL); //_("commands.command line:tab completion" e2_option_bool_register ("command-line-complete-append-space", group_name, _("append space after unique items"), _("This appends a 'space' character to the end of a unique successful match of a file"), NULL, TRUE, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP); //dir line group_name = g_strconcat(_C(32),".",_C(12),":",_C(25),NULL); //_("panes.directory line:misc" e2_option_bool_register ("dir-line-history-last", group_name, _("show last entry"), _("If activated, the last-entered directory will be displayed, instead of an empty line"), NULL, TRUE, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP); e2_option_bool_register ("dir-line-pathname-hint", group_name, _("show pathname as a tooltip"), _("If activated, the full directory pathname will display as a tooltip. " "This is useful when the path is too long for the normal display"), NULL, TRUE, E2_OPTION_FLAG_ADVANCED); const gchar *opt_completions[] = {_(" only"), _("inserted"), _("selected"), NULL}; e2_option_sel_register ("dir-line-completion", group_name, _("directory path completion"), _("This determines the mode of completion when keying a directory-path"), NULL, 1, opt_completions, E2_OPTION_FLAG_ADVANCED); group_name = g_strconcat(_C(32),".",_C(12),":",_C(18),NULL); //_("panes.directory line:history" e2_option_int_register ("dir-line-history-max", group_name, _("maximum number of history entries"), _("This is the largest number of directory-line history entries that will be retained"), NULL, 10, 0, 999999, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP); e2_option_bool_register ("dir-line-history-double", group_name, _("double entries"), _("This allows entries to be recorded more than once in the history list, so the last entry is always close to hand"), NULL, FALSE, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_BUILDBARS); e2_option_bool_register ("dir-line-history-cycle", group_name, _("cyclic list"), _("When scanning the history list, cycle from either end around to the other end, instead of stopping"), NULL, TRUE, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_BUILDBARS); e2_option_bool_register ("dir-line-menu-style", group_name, _("show as a menu"), _("If activated, the directory line history will be presented as a menu. " "For most for most Gtk+2 themes, this will not be as attractive as the list view"), NULL, FALSE, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_BUILDBARS); } emelfm2-0.4.1/src/command/complete/0000700000175000017500000000000011015120161016006 5ustar cairocairoemelfm2-0.4.1/src/command/complete/e2_complete.c0000600000175000017500000002174510647002126020375 0ustar cairocairo/* $Id: e2_complete.c 534 2007-07-16 23:50:46Z tpgww $ Copyright (C) 2004-2007 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/command/complete/e2_complete.c @brief string auto completion This file contains functions to auto-complete strings, e.g. in a command line or dir line. It has a registry which makes it possible to add new completion checks. The type of the completion is then choosen from the registry when completing strings. */ #include "e2_complete.h" #include static GList *checks = NULL; /*****************/ /***** utils *****/ /*****************/ /** @brief check whether a competion of the kind associated with @a rt is applicable, for the current competion request @param flags bitflags in accord with the type(s) of competion to be done for the current request @param rt pointer to E2_CompleteRuntime data struct for a registered completion check @return TRUE if @a rt relates to a completion that is to be used in this completion request */ /*static gboolean _e2_complete_should_check (E2_CompleteFlags flags, E2_CompleteRuntime *rt) { return (flags & rt->flags); }*/ /** @brief find the word in @a line that should be completed this scans @a line backward from cursor position, looking for the most recent non-escaped ' ' character *word is set to a copy of @a line after that ' ', up to the cursor position. (if cursor is at the beginning of @a line, *word is set to a copy of "") @param line utf8 string containing the line to be checked @param pos the current cursor position (characters) in @a line @param word pointer set to the word @return a copy of the part of @a line before *word, copy of "" if cursor is at start of @a line or no space before cursor */ static gchar *_e2_complete_find_word (const gchar *line, gint pos, gchar **word) { //ignore leading whitespace gchar *str = e2_utils_pass_whitespace ((gchar *)line); if (str != NULL) pos -= (str-line); //quick exit if cursor is at the beginning if (pos == 0) { *word = g_strdup (""); return g_strdup (""); } //copy the part of the string to the cursor position str = e2_utf8_ndup (str, pos); //clear any trailing whitespace too (eg in a mount command) gchar *tail = (str + strlen (str) - 1); while (*tail == ' ' || *tail == '\t') //don't bother with utf8 get, whitespace is always ascii { *tail-- = '\0'; pos--; } //get the character offset index in str (line) where the word starts gint i; //search backwards from the current cursor position for the beginning //of the word for (i = pos; i > 0; i--) { gunichar cur = g_utf8_get_char (g_utf8_offset_to_pointer (str, i)); gunichar before = g_utf8_get_char (g_utf8_offset_to_pointer (str, i - 1)); //a non-escaped space character indicates the gap before the word if ((cur == ' ') && (before != '\\')) { i++; break; } } /* leading spaces now stripped, above //in case the word starts at the beginning of the line and the first character //is a space, increment i (this case is missed by the backward search above) if ((i == 0) && (g_utf8_get_char (str) == ' ')) i++; */ //now // gchar *tmp = g_strdup (g_utf8_offset_to_pointer (str, i)); gchar *tmp = g_utf8_offset_to_pointer (str, i); *word = e2_utf8_unescape (tmp, ' '); // g_free (tmp); gchar *retval = e2_utf8_ndup (str, i); g_free (str); return retval; } /** @brief store data for a string-completion "method" @param priority priority assigned to the method (not actually used) @param flags bit-flags indicating type of match(es) etc @param func the function which does the completion for the method @param data pointer to data to be sent to @a func @return */ static void _e2_complete_register_method (guint priority, E2_CompleteFlags flags, // const gchar *name, gchar *group, gchar *desc, gchar *tip, gpointer func, gpointer data) { E2_CompleteRuntime *rt = ALLOCATE (E2_CompleteRuntime); //FIXME never deallocated CHECKALLOCATEDWARN (rt, return); rt->priority = priority; rt->flags = flags; // rt->name = g_strdup (name); //CHECKME = need to dup when source is runtime-created // rt->group = g_strdup (group); //CHECKME used ? // rt->desc = g_strdup (desc); UNUSED // rt->tip = g_strdup (tip); UNUSED rt->func = func; rt->data = data; checks = g_list_append (checks, rt); } /******************/ /***** public *****/ /******************/ /** @brief create sorted list of strings which complete @a complete @param complete pointer to utf8 string containing the text to be completed @param pos pointer to 0-based index of cursor position (characters) in @a line @param found pointer to list of matching strings @param flags bit-flags indicating type of match(es) etc @param pane enumerator of pane to be used for getting dir string 1,2,0=current @return the number of successful matches */ gint e2_complete_str (gchar **complete, gint *pos, GList **found, E2_CompleteFlags flags, guint pane) { gchar *word; gchar *part1 = _e2_complete_find_word (*complete, *pos, &word); //part1 will be empty when no word-break is before the cursor //this is a hack to get around killing a (u)mount command string with no arg gint pos_hack; if (strstr (word, "mount") != NULL && *part1 == '\0') //_I( { g_free (part1); part1 = g_strconcat (word, " ", NULL); pos_hack = strlen (part1); } else pos_hack = 0; //FIXME search on the basis of completion priority ?? gint (*fun) (gchar *, gchar *, gint, GList **, E2_CompleteFlags *, gpointer, gint); gboolean all = flags & E2_COMPLETE_FLAG_ALL; gint count = 0; GList *member; for (member = checks; member != NULL; member = member->next) { E2_CompleteRuntime *crt = (E2_CompleteRuntime *)member->data; if (all || (flags & crt->flags)) { fun = crt->func; gint cur = fun (*complete, word, *pos, found, &flags, crt->data, pane); count += cur; if (flags & E2_COMPLETE_FLAG_STOP) break; } } if (*found != NULL) { gchar *repl = NULL; gchar *part2 = g_utf8_offset_to_pointer (*complete, *pos); gint common = 0; if ((*found)->next != NULL) { repl = g_strdup ((*found)->data); gint len1 = g_utf8_strlen ((*found)->data, -1); gint common = len1; for (member = (*found)->next; member != NULL; member = member->next) { gint len2 = g_utf8_strlen (member->data, -1); gint min = MIN (len1, len2); if (common > min) common = min; gint i; for (i = 0; i < min; i++) { if ((((gchar *) (*found)->data)[i]) != (((gchar *) member->data)[i])) break; } if (common > i) common = i; } if (common == 0) { g_free (repl); repl = NULL; } else repl[common] = '\0'; } else { repl = g_strdup ((*found)->data); } if (repl != NULL) { gchar *tmp = e2_utf8_escape (repl, ' '); gchar *comp_new = g_strconcat (part1, tmp, part2, NULL); *pos += g_utf8_strlen (tmp, -1) - g_utf8_strlen (word, -1) + pos_hack; //for [u]mount command g_free (tmp); g_free (*complete); *complete = comp_new; if (common != 0) repl[common] = ' '; g_free (repl); } } if (count > 1) *found = g_list_sort (*found, (GCompareFunc) g_utf8_collate); g_free (part1); g_free (word); return count; } /** @brief initialize the auto completion system These registrations are not config options, and do not appear in the config dialog, or anywhere else in the UI. They are not really actions, either @return */ void e2_complete_init (void) { RUN_ONCE_CHECK (); checks = NULL; //register default methods /*NOTE that the order of registration is relevant to option E2_COMPLETE_FLAG_STOP which can be set at any time These are registered in priority order, tho' priority is not explicitly examined, now */ #ifdef E2_FS_MOUNTABLE _e2_complete_register_method (5, E2_COMPLETE_FLAG_MOUNT, // "filesystems", "default", NULL, _("complete filesystems in (u)mount commands"), UNUSED e2_complete_mount, NULL); #endif _e2_complete_register_method (10, E2_COMPLETE_FLAG_PATH, // "executables from path", "default", NULL, _("search active directory or PATH for matching items"), UNUSED e2_complete_path, NULL); _e2_complete_register_method (100, E2_COMPLETE_FLAG_FILES | E2_COMPLETE_FLAG_DIRS, // "files & directories", "default", NULL, _("complete file and directory names"), UNUSED e2_complete_files, NULL); } /** @brief cleanup auto completion data @return */ void e2_complete_clear (void) { GList *member; for (member = checks; member != NULL; member = member->next) DEALLOCATE (E2_CompleteRuntime, (E2_CompleteRuntime *)member->data); } emelfm2-0.4.1/src/command/complete/e2_complete.h0000600000175000017500000000560511010340377020375 0ustar cairocairo/* $Id: e2_complete.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/command/complete/e2_complete.h @brief header for string auto completion This is the header file for the string auto completion functions. */ #ifndef __E2_COMPLETE_H__ #define __E2_COMPLETE_H__ #include "emelfm2.h" typedef enum { E2_COMPLETE_FLAG_DONT_ESCAPE = 1<<0, E2_COMPLETE_FLAG_STOP = 1<<1, E2_COMPLETE_FLAG_ALL = 1<<2, E2_COMPLETE_FLAG_FILES = 1<<3, E2_COMPLETE_FLAG_DIRS = 1<<4, E2_COMPLETE_FLAG_EXEC = 1<<5, E2_COMPLETE_FLAG_PATH = 1<<6, E2_COMPLETE_FLAG_HISTORY = 1<<7, E2_COMPLETE_FLAG_MOUNT = 1<<8, E2_COMPLETE_FLAG_SHELL = 1<<9, } E2_CompleteFlags; typedef struct _E2_CompleteRuntime { guint priority; E2_CompleteFlags flags; // gchar *name; //the category of completion to which this method applies // gchar *group; //"default" // gchar *desc; IRRELEVANT, NO UI PRESENCE // gchar *tip; IRRELEVANT, NO UI PRESENCE gpointer func; //the funtion which implements the competion method gpointer data; //available for method-specific data usage } E2_CompleteRuntime; gint e2_complete_str (gchar **complete, gint *pos, GList **found, E2_CompleteFlags flags, guint pane); void e2_complete_register_method (guint priority, E2_CompleteFlags flags, //const gchar *name, gchar *group, gchar *desc, gchar *tip, gpointer func, gpointer data); void e2_complete_init (void); void e2_complete_clear (void); /*************************************************/ /***** functions from e2_complete__*.c files *****/ /*************************************************/ gint e2_complete_files (gchar *line, gchar *word, gint pos, GList **found, E2_CompleteFlags *flags, gpointer data, guint pane); gint e2_complete_path (gchar *line, gchar *word, gint pos, GList **found, E2_CompleteFlags *flags, gpointer data); #ifdef E2_FS_MOUNTABLE gint e2_complete_mount (gchar *line, gchar *word, gint pos, GList **found, E2_CompleteFlags *flags, gpointer data); gboolean e2_complete_mount_menu_create (gpointer from, E2_ActionRuntime *art); void e2_complete_actions_register (void); #endif #if defined(__linux__) || defined(__FreeBSD__) GList *e2_complete_mount_get_fusemounts_list (void); #endif #endif //ndef __E2_COMPLETE_H__ emelfm2-0.4.1/src/command/complete/e2_complete__mount.c0000600000175000017500000001142610667416770021771 0ustar cairocairo/* $Id: e2_complete__mount.c 638 2007-09-05 03:09:44Z tpgww $ Copyright (C) 2005-2007 tooar Portions copyright (C) 2004 Florian Zaehringer This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/command/complete/e2_complete__mount.c @brief mountpoints command-completion functions */ #include "e2_complete.h" #ifdef E2_FS_MOUNTABLE #include /** @brief construct list of the items in @a points that complete @a word @param points list of mountpoint strings to be scanned @param word utf-8 string with string to be completed ("mount" or start of mountpoint path) @param found sore for pointer to list to hold matching items from @a points @return the number of matches found */ static guint _e2_complete_mount_find (GList *points, gchar *word, GList **found) { gchar *path, *match; GList *member; guint ret = 0; gboolean all = FALSE; //the mountpoint was started in the command if ((match = strstr (word, "mount")) != NULL) //_I( { match = e2_utils_find_whitespace (match); if (match == NULL) all = TRUE; //the command is at the end of "word" else { match = e2_utils_pass_whitespace (match); if (match == NULL) all = TRUE; //command + whitespace is at the end of "word" } } if (all) path = NULL; else { if (g_str_has_prefix (word, E2_COMMAND_PREFIX)) //current dir "./" //E2_VFSTMPOK path = g_strconcat (curr_pane->path, word + sizeof(E2_COMMAND_PREFIX), NULL); else path = g_strdup (word); } for (member = points; member != NULL; member = member->next) { gchar *thispoint; gchar **dir_last_part; thispoint = (gchar *) member->data; if (all || g_str_has_prefix (thispoint, path)) { //to conform to e2 'style', make sure all mountpoints have a trailing '/' // if (g_str_has_suffix (dir, G_DIR_SEPARATOR_S)) match = g_strdup (thispoint); // else // match = g_strconcat (thispoint, G_DIR_SEPARATOR_S, NULL); if (!all) { dir_last_part = g_strsplit (match, path, 2); g_free (match); //prepend word, coz it got killed in g_strsplit match = g_strconcat (word, dir_last_part[1], NULL); g_strfreev (dir_last_part); } *found = g_list_append (*found, match); ret++; //if this matches - we take it. no doubled entries. continue; } if (g_str_has_prefix (thispoint, word)) { // if (g_str_has_suffix (thispoint, G_DIR_SEPARATOR_S)) match = g_strdup (thispoint); // else // match = g_strconcat (thispoint, G_DIR_SEPARATOR_S, NULL); *found = g_list_append (*found, match); ret++; } } if (path != NULL) g_free (path); return ret; } /** @brief for a mount or umount command, create list of partitions that can be mounted or unmounted This does no namespace checking. This sort of mounting should only apply to the native filesystem The string to be completed extends from after the ' ' (if any) preceding the cursor position @a pos, to that position. But if @a pos = 0, the string will be empty @param line utf8 string containing the whole line to be completed @param word utf8 string, copy of part of @a line with the 'word' to be completed @param pos 0-based index of cursor position (characters, not bytes) in @a line @param found store for pointer to list of matching items (utf-8 paths) @param flags pointer to bit-flags indicating type of match @param data discretionary data pointer for this method @return the number of successful matches */ gint e2_complete_mount (gchar *line, gchar *word, gint pos, GList **found, E2_CompleteFlags *flags, gpointer data) { GList *candidates; //make sure this is an (un)mount command gchar *tmp = g_utf8_offset_to_pointer (line, pos); gint byte_pos = (tmp - line); if (g_strstr_len (line, byte_pos, "mount") == NULL) //_I( ?? return 0; gint ret = 0; *flags |= E2_COMPLETE_FLAG_STOP; // uid = getuid (); needed if checking permissions if (g_strstr_len (line, byte_pos, "umount") != NULL) //_I( ?? candidates = e2_fs_mount_get_mounts_list (); else candidates = e2_fs_mount_get_mountable_list (); if (candidates != NULL) { ret = _e2_complete_mount_find (candidates, word, found); e2_list_free_with_data (&candidates); } else ret = 0; return ret; } #endif //def E2_FS_MOUNTABLE emelfm2-0.4.1/src/command/complete/e2_complete__path.c0000600000175000017500000001535210775526210021554 0ustar cairocairo/* $Id: e2_complete__path.c 846 2008-04-04 22:32:40Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "e2_complete.h" #include /** @brief dir-foreach callback func This is called with BGL open/off, but no UI-change here (or downstream?) @param parent absolute path of dir being processed, localised string @param itemname name of discovered item @param entries pointer to store for list of data items for @a parent @param user_data pointer to data specified when the foreach was called, with members ->word and ->prefix @return TRUE to signal the read has not been aborted */ static gboolean _e2_complete_path_drcb_check_dir (VPATH *parent, const gchar *itemname, GList **found, E2_Duo *user_data) { gchar *utf, *localpath, *temp; gboolean is_exec, is_dir; utf = F_FILENAME_FROM_LOCALE (itemname); //not DISPLAY if (g_str_has_prefix (utf, user_data->a)) //word { localpath = g_build_filename (VPCSTR (parent), itemname, NULL); #ifdef E2_VFS VPATH ddata = { localpath, parent->spacedata }; is_exec = ! e2_fs_access (&ddata, X_OK E2_ERR_NONE()); is_dir = e2_fs_is_dir3 (&ddata E2_ERR_NONE()); #else is_exec = ! e2_fs_access (localpath, X_OK E2_ERR_NONE()); is_dir = e2_fs_is_dir3 (localpath E2_ERR_NONE()); #endif g_free (localpath); if (is_exec || is_dir) { printd (DEBUG, "match: %s - %s", utf, user_data->a); if (user_data->b == NULL) //prefix { temp = (is_dir) ? g_strconcat (utf, G_DIR_SEPARATOR_S, NULL): g_strdup (utf); } else { temp = (is_dir) ? g_strconcat (user_data->b, utf, G_DIR_SEPARATOR_S, NULL): g_strconcat (user_data->b, utf, NULL); } *found = g_list_append (*found, temp); } } F_FREE (utf); return TRUE; } /** @brief add the sub-dirs and executables in @a localpath that complete @a word, to list @a found @a localpath may be the active-pane dir or a descendant of that (of any sort) or a native dir in $PATH Downstream code expects BGL to be closed on arrival here @param localpath path of directory to scan, localised string, assumed no trailing / @param word the text to be completed, utf8 string @param prefix utf8 string to be prepended to each matching item, or NULL @param found store of list to which matching items will be appended @return the number of successful matches */ static gint _e2_complete_path_check_dir (VPATH *localpath, gchar *word, gchar *prefix, GList **found) { printd (DEBUG, "check_dir (dir:%s,word:%s,found:_,prefix:%s)", localpath, word, prefix); E2_Duo pair = { word, prefix }; gdk_threads_leave (); GList *checks = (GList *) e2_fs_dir_foreach (localpath, E2_DIRWATCH_CHECK, //this is irrelevant for non-local dirs _e2_complete_path_drcb_check_dir, &pair, NULL E2_ERR_NONE()); gdk_threads_enter (); if (E2DREAD_FAILED (checks)) return 0; if (*found == NULL) *found = checks; else *found = g_list_concat (*found, checks); return (g_list_length (checks)); } /** @brief create list of executable items that are valid completions of @a line The string to be completed extends from after the ' ' (if any) preceding the cursor position @a pos, to that position. But if @a pos = 0, the string will be empty If the string starts with "./", the scanning occurs in the active directory Otherwise the scan iterates over $PATH. @param line utf-8 string containing the whole line to be completed @param word utf-8 string, copy of part of @a line with the 'word' to be completed @param pos 0-based index of cursor position (characters, not bytes) in @a line @param found pointer to list to record matching items @param flags pointer to bit-flags indicating type of match (dirs or files) @param data discretionary data pointer for this method @return the number of successful matches */ gint e2_complete_path (gchar *line, gchar *word, gint pos, GList **found, E2_CompleteFlags *flags, gpointer data) { printd (DEBUG, "complete_path (line:%s,word:%s,pos:%d,found:_,flags:%d,data:_)", line, word, pos, *flags); if ((word == NULL) || (*word == '\0')) return 0; /* WRONG ! //the word to complete needs to be the whole line //(i.e. no ' ' in the line prior to the cursor position) //or else we can't match a path if (!g_str_equal (word, line)) //error message in this case? return 0; */ *found = NULL; gint ret; #ifdef E2_VFS VPATH ddata; #endif if (strchr (word, G_DIR_SEPARATOR) != NULL) //string-to-complete has path separator { gchar *p1, *p2, *localpath; p1 = g_utf8_next_char (strrchr (word, G_DIR_SEPARATOR)); //the 'real' word to complete p2 = g_strndup (word, p1 - word); //prefix for matches if (word[0] == G_DIR_SEPARATOR) { //word is an absolute path string localpath = F_FILENAME_TO_LOCALE (p2); #ifdef E2_VFS ddata.localpath = localpath; ddata.spacedata = curr_view->spacedata; ret = _e2_complete_path_check_dir (&ddata, p1, p2, found); #else ret = _e2_complete_path_check_dir (localpath, p1, p2, found); #endif } else //a relative-path string { gchar *p3 = e2_utils_translate_relative_path (curr_pane->path, p2); //E2_VFSTMPOK localpath = F_FILENAME_TO_LOCALE (p3); #ifdef E2_VFS ddata.localpath = localpath; ddata.spacedata = curr_view->spacedata; ret = _e2_complete_path_check_dir (&ddata, p1, p2, found); #else ret = _e2_complete_path_check_dir (localpath, p1, p2, found); #endif g_free (p3); } g_free (p2); F_FREE (localpath); *flags |= E2_COMPLETE_FLAG_STOP; //with a path, never keep on looking } else //no separator { //look everywhere in $PATH printd (DEBUG, "complete from path"); ret = 0; const gchar *p = g_getenv ("PATH"); //_I( = this ok for all languages? if (p != NULL) //CHECKME needed ? { gchar **paths = g_strsplit (p, G_SEARCHPATH_SEPARATOR_S, -1); //iterate over the path members gint i = 0; while (paths[i] != NULL) { #ifdef E2_VFS ddata.localpath = paths[i]; ddata.spacedata = NULL; //$PATH items must be local ret += _e2_complete_path_check_dir (&ddata, word, NULL, found); #else ret += _e2_complete_path_check_dir (paths[i], word, NULL, found); #endif i++; } g_strfreev (paths); } if (ret > 0) *flags |= E2_COMPLETE_FLAG_STOP; } return ret; } emelfm2-0.4.1/src/command/complete/e2_complete__files.c0000600000175000017500000001007610667416770021731 0ustar cairocairo/* $Id: e2_complete__files.c 638 2007-09-05 03:09:44Z tpgww $ Copyright (C) 2004-2007 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "e2_complete.h" #include #include /** @brief create list of files (and non-files) that are valid completions of @a word Only native completions are suported. The string to be completed extends from after the ' ' (if any) preceding the cursor position @a pos, to that position. But if @a pos = 0, the string will be empty If the string in question is from a dirline, the corresponding directory is scanned for completions For a dirline, @a line = @a word This function temporarily changes the filesystem CWD @param line utf8 string containing the whole line to be completed @param word utf8 string, copy of part of @a line with the 'word' to be completed @param pos 0-based index of cursor position (characters, not bytes) in @a line @param found pointer to list to record matching items @param flags pointer to bit-flags indicating type of match (dirs or files) @param data discretionary data pointer @param pane enumerator of pane to be used for getting dir string 1,2,0=current @return the number of successful matches */ gint e2_complete_files (gchar *line, gchar *word, gint pos, GList **found, E2_CompleteFlags *flags, gpointer data, guint pane) { printd (DEBUG, "complete_files (line:%s,word:%s,pos:%d,found:_,flags:%d,data:_)", line, word, pos, *flags); gchar *path, *pattern, *pattern_local; #ifdef E2_VFS PlaceInfo *spacedata; switch (pane) { case E2PANE1: spacedata = app.pane1_view.spacedata; break; case E2PANE2: spacedata = app.pane2_view.spacedata; break; default: spacedata = curr_view->spacedata; break; } if (spacedata != NULL) return 0; //no completion ATM for non-local places #ifdef E2_VFSTMP make this work ? #endif #endif if (!g_path_is_absolute (word) #ifdef E2_VFSTMP && spacedata == NULL //cd only for native filesystem #endif ) { switch (pane) { case E2PANE1: path = app.pane1_view.dir; break; case E2PANE2: path = app.pane2_view.dir; break; default: path = curr_view->dir; break; } if (!e2_fs_chdir (path E2_ERR_NONE())) return 0; } #ifdef VFS_TMP //FIXME use dir foreach func with callback to g_pattern_match* #endif gint count; pattern = g_strconcat (word, "*", NULL); pattern_local = F_FILENAME_TO_LOCALE (pattern); glob_t matches; if (glob (pattern_local, GLOB_MARK, NULL, &matches) == 0) //this doesn not support vfs matching { guint i; gboolean dirs = (*flags & E2_COMPLETE_FLAG_DIRS) && (!(*flags & E2_COMPLETE_FLAG_FILES)); gboolean files = (*flags & E2_COMPLETE_FLAG_FILES) && (!(*flags & E2_COMPLETE_FLAG_DIRS)); for (i = 0; i < matches.gl_pathc; i++) { if (dirs) { if (g_str_has_suffix (matches.gl_pathv[i], G_DIR_SEPARATOR_S)) *found = g_list_append (*found, D_FILENAME_FROM_LOCALE (matches.gl_pathv[i])); } else if (files) { if (!g_str_has_suffix (matches.gl_pathv[i], G_DIR_SEPARATOR_S)) *found = g_list_append (*found, D_FILENAME_FROM_LOCALE (matches.gl_pathv[i])); } else *found = g_list_append (*found, D_FILENAME_FROM_LOCALE (matches.gl_pathv[i])); } count = matches.gl_pathc; globfree (&matches); } else count = 0; //only relevant for native filesystem //#ifdef E2_VFS // if (spacedata == NULL) //#endif e2_fs_chdir (curr_view->dir E2_ERR_NONE()); g_free (pattern); F_FREE (pattern_local); return count; } emelfm2-0.4.1/src/command/e2_command.c0000600000175000017500000026431210776374631016412 0ustar cairocairo/* $Id: e2_command.c 853 2008-04-07 10:38:17Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/command/e2_command.c @brief functions for handling e2 commands */ /** \page commline the command line ToDo \section alias command aliases ToDo */ /** \page commands executing external commands ToDo - description of how this works \section alias alias operation ToDo */ #include "e2_command.h" #include #include #include #include #include #include "e2_task.h" #include "e2_filelist.h" #include "e2_alias.h" #include "e2_fs.h" #define CMDNAMELEN 30 extern pthread_mutex_t task_mutex; static gint waitflags; //waitpid() argument, tailored for user's system static gint block_count; //refcount for [un]blocking child signals const gchar *shellcmd; //the command interpreter to use for external shell //list of utf8 strings, each nAmE=string-value, no whitespace surrounding "=" static GList *variables; #ifdef E2_NEW_COMMAND //static void _e2_command_sigpoll_handler (gint num, siginfo_t *info, void *context); static gpointer _e2_command_watch (E2_TaskRuntime *rt); #else static gboolean _e2_command_watch (GIOChannel *ioc, GIOCondition cond, E2_TaskRuntime *rt, gboolean error); #endif static void _e2_command_sigchld_handler (gint num, siginfo_t *info, void *context); //CHECKME need SIGPIPE management ?? /** @brief set handler for SIGCHLD signals @return */ static void _e2_command_set_sigchild_handler (void) { struct sigaction sigdata; sigdata.sa_sigaction = _e2_command_sigchld_handler; sigemptyset (&sigdata.sa_mask); sigdata.sa_flags = SA_RESTART | SA_SIGINFO; //SA_NOCLDSTOP not allowed; sigaction (SIGCHLD, &sigdata, NULL); } /** @brief block incoming SIGCHLD signals @return */ static void _e2_command_block_childsignal (void) { if (++block_count == 1) { sigset_t set; //, oldset; sigemptyset (&set); sigaddset (&set, SIGCHLD); sigprocmask (SIG_BLOCK, &set, NULL); //&oldset); } } /** @brief unblock incoming SIGCHLD signals @return */ static void _e2_command_unblock_childsignal (void) { if (--block_count < 0) { printd (WARN, "child signal block refcount error"); block_count = 0; } if (block_count == 0) { sigset_t set; //, oldset; //re-install the signal handler (some systems need this?) WRONG // _e2_command_set_sigchild_handler (); //and unblock it sigemptyset (&set); sigaddset (&set, SIGCHLD); sigprocmask (SIG_UNBLOCK, &set, NULL); //&oldset); } } /** @brief handle SIGCHILD signals Note that opened pipes etc will come here, even though they're not logged as children @param num the signal number (17) @param info pointer to data struct with details about signal, or NULL @param context UNUSED @return */ static void _e2_command_sigchld_handler (gint num, siginfo_t *info, void *context) { printd (DEBUG, "signal handler (signal num:%d)", num); pid_t endpid; gint exitstatus; if (info != NULL) { //normally, the child's data is provided in signal data endpid = info->si_pid; // exitstatus = info->si_status; waitpid (endpid, &exitstatus, waitflags); //must reap } else { rewait: //get stopped, restarted and finished child(ren), if any //(any error will abort this loop, ECHILD (10) is normal loop terminator) endpid = waitpid (-1, &exitstatus, waitflags); printd (DEBUG, "waitpid() for any children returned %d", endpid); } if (endpid > 0) { E2_TaskRuntime *rt = e2_task_find_running_task ((glong) endpid); if (!(rt == NULL || rt->action)) { printd (DEBUG, "ending after waitpid returned %d", endpid); pthread_mutex_lock (&task_mutex); if (WIFEXITED (exitstatus)) { rt->pid = -2L; //prevent further matches for this rt rt->status = E2_TASK_COMPLETED; rt->ex.command.exit = WEXITSTATUS (exitstatus); printd (DEBUG, "signal handler detected '%s' ended normally with exit code %d", rt->ex.command.command, WEXITSTATUS (exitstatus)); printd (DEBUG, "child status stored"); } else if (WIFSTOPPED (exitstatus)) { # ifdef WCONTINUED //linux >= 2.6.10 rt->status = E2_TASK_PAUSED; # endif printd (DEBUG, "continuing after %d stopped by signal %d", (gint)endpid, WSTOPSIG (exitstatus)); } else if (WIFSIGNALED (exitstatus)) { gint signal = WTERMSIG (exitstatus); //CHECKME which other signals are acceptable ? if (signal == SIGTTOU || signal == SIGTTIN) { # ifdef WCONTINUED //linux >= 2.6.10 rt->status = E2_TASK_PAUSED; # endif //CHECKME do a select() and print messages after SIGTTOU/SIGTTIN signal ? printd (DEBUG, "continuing after %d stopped by signal %d", (gint)endpid, signal); } else { rt->pid = -2L; //prevent further matches for this rt rt->status = E2_TASK_INCOMPLETE; rt->ex.command.exit = signal; printd (DEBUG, "child-signal handler detected command '%s' terminated by signal %d (%s)", rt->ex.command.command, signal, g_strsignal (signal)); printd (DEBUG, "child status (%d) stored", signal); } } # ifdef WCONTINUED else if (WIFCONTINUED (exitstatus)) { rt->status = E2_TASK_RUNNING; printd (DEBUG, "process %d restarted", endpid); } # endif pthread_mutex_unlock (&task_mutex); } else //no matching running-command data (could be a sync command (old version)) // if (!rt->action) { printd (WARN, "received signal from unrecorded child process (%d)", (gint) endpid); //in this handler, current signal is blocked, pass it on for other handlers //kill (endpid, num); //CHECKME } if (info == NULL && !rt->action) goto rewait; } //end of endpid > 0 else if (endpid < 0) { if (errno == EINTR) { printd (DEBUG, "re-checking after waitpid was interrupted"); goto rewait; } else if (errno != ECHILD) { printd (DEBUG, "aborting after waitpid pid error %d (%s)", errno, g_strerror (errno)); //FIXME find which task it is and record error exit // rt->status = E2_TASK_INCOMPLETE; // rt->ex.command.exit = errno; } } //re-install the signal handler (some systems need this?) _e2_command_set_sigchild_handler (); } #ifdef E2_NEW_COMMAND //======================= /*DOES NOT WORK PROPERLY YET select() returns prematurely for linux 2.4 fork again and read from grandchild, like glib ? use pseudo forkpty ? sync commands print nothing until command ends additional thread for sync output to sync child ? event reinstatement after c checks with sync child SIGPOLL handler instead of polling in watch func? maybe only works when detached from session terminal ? */ //piping from here to child stdin not properly supported, not least because the //output fd is always selectable, which causes irrelevant returning from select() //#define OUTPUT_POLL enum { INWARDS, OUTWARDS }; //pipe fd enumerator /* In Linux versions before 2.6.11, the capacity of a pipe (== PIPE_BUF) was the same as the system page size (e.g., 4096 bytes on x86). Since Linux 2.6.11, the pipe capacity is 65536 bytes. Other OS's have different values */ #define READBUFSIZE 1024 //this is READBUFSIZE, less space for adding a trailing \0, then rounded down to multiple of 8 #define READMAX 1016 //#include //#include //for ioctl /* * @brief set handler for SIGPOLL/SIGIO signals @return */ /*static void _e2_command_set_sigpoll_handler (void) { struct sigaction sigdata; sigdata.sa_sigaction = _e2_command_sigpoll_handler; sigemptyset (&sigdata.sa_mask); sigdata.sa_flags = SA_RESTART | SA_SIGINFO; sigaction (SIGPOLL, &sigdata, NULL); } */ /** @brief tolerantly transfer data from file descriptor @a fd into @a buffer The buffer pointed to by @a buffer must be big enough to hold @a count bytes @param fd number of file descriptor to read @param buffer character-buffer pointer @param count maximum no. of bytes to read or write @return the no. of bytes read, -1 in case of some errors */ static ssize_t _e2_command_piperead (gint fd, gchar *buffer, gint count) { ssize_t i, n = 0; while (n < count) { i = read (fd, buffer + n, count - n); //vfs?? e2_fs_file_blockread; if (i >= 0) return (i+n); else { switch (errno) { //some errors we don't care about case EINTR: case EAGAIN: #ifdef ERESTART case ERESTART: #endif n += i; break; default: return -1; break; } } } return count; } /** @brief print @a buffer contents The printing is segmented into into lines, to prevent too-chunky scrolling The final line may or may not have a trailing '\n' @param buffer character-buffer pointer @param error TRUE to print error stream @param rt pointer to command data struct @return */ static void _e2_command_display (gchar *buffer, gboolean error, E2_TaskRuntime *rt) { gchar save; gchar *sep, *line; line = buffer; while (TRUE) { //always ascii \n, don't need utf8 search if ((sep = strchr (line, '\n')) != NULL) { sep++; save = *sep; *sep = '\0'; gdk_threads_enter (); if (error) e2_output_print (rt->current_tab, line, rt->pidstr, FALSE, E2_ERRORTAGS, NULL); else e2_output_print (rt->current_tab, line, rt->pidstr, FALSE, NULL); gdk_threads_leave (); *sep = save; line = sep; } else { if (*line != '\0') { gdk_threads_enter (); if (error) e2_output_print (rt->current_tab, line, rt->pidstr, FALSE, E2_ERRORTAGS, NULL); else e2_output_print (rt->current_tab, line, rt->pidstr, FALSE, NULL); gdk_threads_leave (); } break; } } } /* //FOR DEBUGGING static void _e2_command_sigpoll_handler (gint num, siginfo_t *info, void *context) { printd (DEBUG, "signal handler (signal num:%d)", num); if (info != NULL) { gint code = info->si_code; switch (code) { case POLL_IN: //data input available break; case POLL_OUT: //output buffers available break; case POLL_MSG: //input message available break; case POLL_ERR: //I/O error break; case POLL_PRI: //high priority input available break; case POLL_HUP: //device disconnected break; } glong band = info->si_band; //band event for POLL_IN, POLL_OUT, or POLL_MSG } / * gint fd = 0; gint owner = fcntl (fd, F_GETOWN); gint oldflags = fcntl (fd, F_GETFL); fcntl (fd, F_SETFL, oldflags | O_NONBLOCK | O_ASYNC); gint yesplease = 1; ioctl (fd, FIOASYNC, &yesplease); * / } */ #define E2CMDTYPEB /** @brief watch for and forward to output pane any stdout or stderr text from running command This is a thread function. Assumes BGL is off @param rt pointer to command data struct @return the exit code of the command, > 0 if error */ static gpointer _e2_command_watch (E2_TaskRuntime *rt) { // struct timeval wait; // struct timeval *waitptr; struct timespec wait; struct timespec *waitptr; gchar *buffer; #ifdef USE_GLIB2_10 buffer = g_slice_alloc (READBUFSIZE); #else buffer = g_try_malloc (READBUFSIZE); #endif CHECKALLOCATEDWARNT (buffer, return (GINT_TO_POINTER(1));); e2_utils_block_thread_signals (); //block all allowed signals to this thread (what about SIGPIPE?) E2_CommandTaskData *cmddata = &rt->ex.command; printd (DEBUG, "child output fd = %d, child error fd = %d, max monitorable fd = %d", cmddata->child_stdout_fd, cmddata->child_stderr_fd, FD_SETSIZE); //FIXME handle assigned fd > FD_SETSIZE gint maxfd = MAX(cmddata->child_stdout_fd, cmddata->child_stderr_fd); #ifdef OUTPUT_POLL if (cmddata->child_stdin_fd > maxfd) maxfd = cmddata->child_stdin_fd; #endif maxfd++; //as the pipes will be selected at least when the child closes it's ok to //block until select succeeds //and note, ouput pipe (if used) is always selectable waitptr = NULL; #ifdef E2CMDTYPEB gboolean outread, errread; outread = errread = TRUE; //we want to pselect() the fd's at least once #endif while (TRUE) { gint selcount; guint maxwait; ssize_t bytecount; gboolean outread, errread; fd_set in_fds; #ifdef OUTPUT_POLL fd_set out_fds; #endif reselect: /*#ifdef E2CMDTYPEB //FIXME handle case where outread and errread are both FALSE; if (!(outread || errread)) { selcount = 0; goto ? } #endif */ FD_ZERO (&in_fds); //FIXME find a way to may these tests work without stuffing up the completion reporting #ifdef E2CMDTYPEB if (outread) //don't select fd's that are closed #endif FD_SET (cmddata->child_stdout_fd, &in_fds); #ifdef E2CMDTYPEB if (errread) #endif FD_SET (cmddata->child_stderr_fd, &in_fds); #ifdef OUTPUT_POLL FD_ZERO (&out_fds); # ifdef E2CMDTYPEB if (?) # endif FD_SET (cmddata->child_stdin_fd, &out_fds); #endif //timer data undefined after last use, re-init if used now if (waitptr != NULL) { wait.tv_sec = maxwait; //0 for immediate return // wait.tv_usec = 0; wait.tv_nsec = 0; } #ifndef E2CMDTYPEB outread = errread = FALSE; //ensure var's are initialised #endif #ifdef OUTPUT_POLL selcount = pselect (maxfd, &in_fds, &out_fds, NULL, waitptr, NULL); #else selcount = pselect (maxfd, &in_fds, NULL, NULL, waitptr, NULL); #endif if (selcount == -1 && errno == EINTR) { usleep (1000); //wait awhile for any interrrupt processing goto reselect; } else if (selcount < 0) { printd (DEBUG, "pipe selection error %d (%s)", errno, g_strerror (errno)); // outread = errread = FALSE; //ensure no more selects selcount = 0; //fake value to trigger loop exit } else if (selcount > 0) { // printd (DEBUG, "pipe selection"); //at end-of-command, both input fd's report 0 bytes ready to read if (FD_ISSET (cmddata->child_stdout_fd, &in_fds)) { // printd (DEBUG, "child stdout ready"); //read input, starting with normal pipe #ifdef E2CMDTYPEB outread = FALSE; //in case this one fails #endif while ((bytecount = _e2_command_piperead (cmddata->child_stdout_fd, buffer, READMAX)) > 0) { printd (DEBUG, "child stdout buffer read"); outread = TRUE; *(buffer+bytecount) = '\0'; _e2_command_display (buffer, FALSE, rt); if (bytecount != READMAX) break; } if (!outread) { //pipe closed or closing printd (DEBUG, "FALSE positive child stdout ready"); usleep (10000); //allow for some cleanup } } //#ifdef E2CMDTYPEB // else // outread = TRUE; //ensure reselection happens //#endif //to minimise race-risk, check again for stderr if stdout was processed if (!FD_ISSET (cmddata->child_stderr_fd, &in_fds) && outread) { // printd (DEBUG, "re-check child stderr ready"); wait.tv_sec = 0; // wait.tv_usec = 0; wait.tv_nsec = 0; FD_ZERO (&in_fds); FD_SET (cmddata->child_stderr_fd, &in_fds); TEMP_FAILURE_RETRY (pselect (cmddata->child_stderr_fd+1, &in_fds, NULL, NULL, &wait, NULL)); } if (FD_ISSET (cmddata->child_stderr_fd, &in_fds)) { // printd (DEBUG, "child stderr ready"); //read input, starting with error pipe #ifdef E2CMDTYPEB errread = FALSE; //in case this one fails #endif while ((bytecount = _e2_command_piperead (cmddata->child_stderr_fd, buffer, READMAX)) > 0) { printd (DEBUG, "child stderr buffer read"); errread = TRUE; *(buffer+bytecount) = '\0'; _e2_command_display (buffer, TRUE, rt); if (bytecount != READMAX) break; } if (!errread) { //pipe closed or closing printd (DEBUG, "FALSE positive child stderr ready"); usleep (10000); //allow for some cleanup } } //#ifdef E2CMDTYPEB // else // errread = TRUE; //ensure reselection happens //#endif #ifdef OUTPUT_POLL //FIXME grab any commandline input and pass direct to running sync command //MAYBE only if child is stopped ? //FIXME child-pipe is ALWAYS ready, including at at end-of-command //reading a line with \0 termination seems not to work //multi reads can close the fd ? if (FD_ISSET (cmddata->child_stdin_fd, &out_fds)) { printd (DEBUG, "child ready for input"); /* gdk_threads_enter (); e2_output_print (rt->current_tab, "Child wants input", rt->pidstr, TRUE, E2_ERRORTAGS, NULL); gdk_threads_leave (); if (0) { //there is something to send ... child = fdopen (cmddata->child_stderr_fd, "w"); do { //DO SOMETHING TO GET MESSAGE INTO BUFFER } while (fputs (buf, child) != EOF); fclose (child); } */ } #endif } pthread_mutex_lock (&task_mutex); gboolean done = (rt->status >= E2_TASK_COMPLETED); pthread_mutex_unlock (&task_mutex); #ifdef E2CMDTYPEB if (done || selcount == 0 || !(outread || errread)) #else if (done) { if (selcount == 0 || !(outread || errread)) #endif { printd (DEBUG, "watch function cleanup after end of process %s", rt->pidstr); close (cmddata->child_stdin_fd); close (cmddata->child_stdout_fd); close (cmddata->child_stderr_fd); if (cmddata->show && e2_option_bool_get ("fileop-show")) { #ifdef E2CMDTYPEB //wait at most 5 secs for completion code to be logged guint i = 0; while (!done && i < 250) { usleep (20000); pthread_mutex_lock (&task_mutex); done = (rt->status >= E2_TASK_COMPLETED); pthread_mutex_unlock (&task_mutex); i++; } if (i < 250) { #endif gchar *message = g_strdup_printf ("%s>%s (%s) %s '%d'\n", (cmddata->extshell) ? "sh" : "", cmddata->command, rt->pidstr, _("returned"), cmddata->exit); gdk_threads_enter (); e2_output_print (rt->current_tab, message, rt->pidstr, TRUE, "small", (cmddata->exit == 0) ? "green" : "red", NULL); gdk_threads_leave (); g_free (message); #ifdef E2CMDTYPEB } #endif } //CHECKME sometimes (how?) we can end this func without breaking here printd (DEBUG, "Exiting command select loop"); break; #ifndef E2CMDTYPEB } else //CHECKME maybe not really needed ? { printd (DEBUG, "scanning child pipes again after watch function detected process %s completion flag", rt->pidstr); //do one last quick scan in case of race between SIGCHILD and pipes interrogation maxwait = 0; waitptr = &wait; goto reselect; } #endif } #ifdef DEBUG_MESSAGES else { // if (selcount == 0 || !(outread || errread)) // printd (DEBUG, "no pipe from child was actually read"); if (selcount == 0) printd (DEBUG, "selected 0 fd's"); if (!outread) printd (DEBUG, "flag outread FALSE"); if (!errread) printd (DEBUG, "flag errread FALSE"); } #endif //CHECKME can we do this without so many loops ? SIGPOLL ? //CHECKME for sync commands, omit WNOHANG ? /* if (maxwait < 10) { //this is the end of the first pipes-scan maxwait = 10; //revert to slower polling after 1st pass // cmddata->pollcount = -1; //flag that the pipes have been polled now, //(so it's ok for timer to re-enable main-thread SIGIO) //ostensibly this should have no impact on main-thread signals, but //for some kernels it does seem to speed up termination //note that this will probably stuff up the blockage refcount! // _e2_command_unblock_childsignal (); //allow any child's status change to be noticed // printd (DEBUG, "SIGCHILD re-enabled in watch function"); } THIS RELIES ON select() returning regularly ... if (cmddata->sync) { GdkDisplay *display = gdk_display_manager_get_default_display (gdk_display_manager_get()); GdkEvent *event, *event2; //check for cancellation request //callbacks do not work, we must poll the events queue if ((event = gdk_display_get_event (display)) != NULL) //peek won't work { if (event->type == GDK_KEY_PRESS) { if (((GdkEventKey*)event)->keyval == GDK_Control_L || ((GdkEventKey*)event)->keyval == GDK_Control_R) { event2 = event; if ((event = gdk_display_get_event (display)) != NULL && ((GdkEventKey*)event)->keyval == GDK_c && ((GdkEventKey*)event)->state & GDK_CONTROL_MASK) { printd (DEBUG, "SIGTERM sent to child process group"); kill (-pid, SIGTERM); } else { if (event != NULL) { gdk_display_put_event (display, event); gdk_event_free (event); } gdk_display_put_event (display, event2); gdk_event_free (event2); } } else if (((GdkEventKey*)event)->keyval == GDK_c && (((GdkEventKey*)event)->state & GDK_CONTROL_MASK) //&& ((GdkEventKey*)event)->window == ? ) { printd (DEBUG, "SIGTERM sent to child process group"); kill (-pid, SIGTERM); } //FIXME reinstating irrelevant events blocks handling of relevant ones else gdk_display_put_event (display, event); } else gdk_display_put_event (display, event); gdk_event_free (event); } } */ } #ifdef USE_GLIB2_10 g_slice_free1 (READBUFSIZE, buffer); #else g_free (buffer); #endif // printd (DEBUG, "End command watch"); return (GINT_TO_POINTER (rt->ex.command.exit)); } /** @brief run command @a command Assumes BGL is closed upon entry @param command the command string, used in messages, utf8 @param args pointer to NULL-terminated command arguments array, each localised @param cwd path of directory to use as CWD for the command, utf8 @param sync TRUE to run @a command in foreground, FALSE for background @param show TRUE to show @a command and its exit status in the output pane @param extshell TRUE if @a command is to be run in external shell @return the exit code of the command, > 0 if error */ static gint _e2_command_fork (gchar *command, gchar **args, const gchar *cwd, gboolean sync, gboolean show, gboolean extshell) { //there are no embedded tests for command existence, so we need to do that if (!g_path_is_absolute (args[0])) { #ifdef E2_VFSTMP if supporting get-and-exec, then local $PATH may be irrelevant need relevant PlaceInfo to sort this out, might not be curr_view->spacedata->workplace cwd needs to be a VPATH* #endif gchar *path = g_find_program_in_path (args[0]); if (path != NULL) { g_free (path); } else { gchar *utf = F_DISPLAYNAME_FROM_LOCALE (args[0]); gchar *msg = g_strdup_printf (_("Command '%s' - %s"), utf, g_strerror (errno)); e2_output_print_error (msg, TRUE); F_FREE (utf); return 1; } } #ifdef E2_VFS VPATH ddata; #endif gchar *message; pid_t pid; /* //#include struct termios shell_tmodes; pid_t shell_pgid; gboolean interactive_shell = isatty (STDIN_FILENO); if (interactive_shell) { //loop until we are in the foreground while (tcgetpgrp (STDIN_FILENO) != (shell_pgid = getpgrp ())) kill (-shell_pgid, SIGTTIN); / * //ignore interactive and job-control signals signal (SIGINT, SIG_IGN); signal (SIGQUIT, SIG_IGN); signal (SIGTSTP, SIG_IGN); signal (SIGTTIN, SIG_IGN); signal (SIGTTOU, SIG_IGN); signal (SIGCHLD, SIG_IGN); * / //put ourselves in our own process group shell_pgid = getpid (); if (setpgid (shell_pgid, shell_pgid) < 0) { printd (DEBUG,"Could not put shell into its own process group"); return FALSE; } //grab control of the terminal tcsetpgrp (STDIN_FILENO, shell_pgid); //save default terminal attributes for shell tcgetattr (STDIN_FILENO, &shell_tmodes); } */ //setup to write to child's stdin & read child's stdout, stderr gint stdin_pipe[2]; gint stdout_pipe[2]; gint stderr_pipe[2]; if (pipe (stdin_pipe) < 0) //no need to worry about VFS { goto launch_error; } if (pipe (stdout_pipe) < 0) { close (stdin_pipe[INWARDS]); close (stdin_pipe[OUTWARDS]); goto launch_error; } if (pipe (stderr_pipe) < 0) { close (stdin_pipe[INWARDS]); close (stdin_pipe[OUTWARDS]); close (stdout_pipe[INWARDS]); close (stdout_pipe[OUTWARDS]); goto launch_error; } E2_TaskRuntime *rt; //keep trying this until data set fn not busy //real pid is set later while ((rt = e2_task_set_data (0, (sync) ? E2_TASKTYPE_SYNC : E2_TASKTYPE_ASYNC, command)) == NULL); if (rt == GINT_TO_POINTER (1)) { close (stdin_pipe[INWARDS]); close (stdin_pipe[OUTWARDS]); close (stdout_pipe[INWARDS]); close (stdout_pipe[OUTWARDS]); close (stderr_pipe[INWARDS]); close (stderr_pipe[OUTWARDS]); goto launch_error; } _e2_command_block_childsignal (); gdk_threads_leave (); if ((pid = fork ()) == 0) { //child close (stdin_pipe[OUTWARDS]); close (stdout_pipe[INWARDS]); close (stderr_pipe[INWARDS]); pid = getpid (); //get real pid if (setpgid (pid, pid) < 0) { printd (DEBUG,"Could not put child into its own process group"); //FIXME warn user about this // _exit (2); } //arrange stdio via parent /* seems to do nothing Setting the O_ASYNC flag for the read end of a pipe causes a signal (SIGIO by default) to be generated when new input becomes available on the pipe. On Linux, O_ASYNC is supported for pipes only since kernel 2.6. If the pipe is full, then a write(2) will block or fail, depending on whether the O_NONBLOCK flag is set gint flags = fcntl (stdout_pipe[OUTWARDS], F_GETFL); if (flags != -1) fcntl (stdout_pipe[OUTWARDS], F_SETFL, flags | O_NONBLOCK | O_ASYNC); flags = fcntl (stderr_pipe[OUTWARDS], F_GETFL); if (flags != -1) fcntl (stderr_pipe[OUTWARDS], F_SETFL, flags | O_NONBLOCK | O_ASYNC); */ //(these fd assignments redirect child's stdin, stdout, stderr streams too) TEMP_FAILURE_RETRY (dup2 (stdin_pipe[INWARDS], STDIN_FILENO)); //redirect output to parent TEMP_FAILURE_RETRY (dup2 (stdout_pipe[OUTWARDS], STDOUT_FILENO)); TEMP_FAILURE_RETRY (dup2 (stderr_pipe[OUTWARDS], STDERR_FILENO)); //close on exit flags ? //setup crash if the parent exits and we later write to a pipe signal (SIGPIPE, SIG_DFL); _e2_command_unblock_childsignal (); #ifdef E2_VFS if (curr_view->spacedata != NULL) { #ifdef E2_VFSTMP if supporting get-and-exec, and getting is slow, some cd-race might occur need relevant PlaceInfo which might not be curr_view->spacedata->workplace #endif //there's no native analog for the vdir, so go here... //all command args need to have been modified accordingly, by the command interpreter ddata.localpath = curr_view->spacedata->workplace; ddata.spacedata = NULL; if (!e2_fs_chdir_local (&ddata E2_ERR_NONE())) execvp (args[0], args); //should not return } else //native execution #endif { if (cwd == NULL) cwd = curr_view->dir; if (e2_fs_chdir ((gchar *)cwd E2_ERR_NONE())) { // printd (DEBUG, "Child will run '%s'", args[0]); execvp (args[0], args); //should not return } } _exit (1); //end of child process } gdk_threads_enter (); if (pid < 0) { //fork error close (stdin_pipe[INWARDS]); close (stdin_pipe[OUTWARDS]); close (stdout_pipe[INWARDS]); close (stdout_pipe[OUTWARDS]); close (stderr_pipe[INWARDS]); close (stderr_pipe[OUTWARDS]); _e2_command_unblock_childsignal (); rt->status = E2_TASK_FAILED; rt->ex.command.exit = 128; goto launch_error; } //parent process rt->status = E2_TASK_RUNNING; rt->pid = (glong) pid; rt->pidstr = g_strdup_printf ("%ld", rt->pid); E2_CommandTaskData *cmddata = &rt->ex.command; cmddata->child_stdin_fd = stdin_pipe[OUTWARDS]; //setup for later sending to child cmddata->child_stdout_fd = stdout_pipe[INWARDS]; //setup for later receiving from child cmddata->child_stderr_fd = stderr_pipe[INWARDS]; //setup for later receiving from child // FILE *child_stdin_stream = fdopen (stdin_pipe[OUTWARDS], "w"); // FILE *child_stdout_stream = fdopen (stdout_pipe[INWARDS], "r"); // FILE *child_stderr_stream = fdopen (stderr_pipe[INWARDS], "r"); // cmddata->pollcount = 0; //not ready to enable SIGCHILD yet NOT USED NOW cmddata->sync = sync; cmddata->extshell = extshell; cmddata->show = show; #ifdef DEBUG_MESSAGES if (sync) printd (DEBUG, "synchronously run command %s", args[0]); else printd (DEBUG, "asynchronously run command %s", args[0]); #endif if (show && e2_option_bool_get ("fileop-show")) { message = g_strconcat ((extshell) ? "sh>" : ">", command, NULL); e2_output_print (rt->current_tab, message, rt->pidstr, FALSE, "bold", "blue", NULL); g_free (message); message = g_strdup_printf (" (%s)\n", rt->pidstr); e2_output_print (rt->current_tab, message, rt->pidstr, FALSE, "small", "blue", NULL); g_free (message); WAIT_FOR_EVENTS; } // _e2_command_unblock_childsignal (); //allow child exit to be noticed SEE TIMER close (stdin_pipe[INWARDS]); close (stdout_pipe[OUTWARDS]); close (stderr_pipe[OUTWARDS]); /* seems to do nothing Setting the O_ASYNC flag for the read end of a pipe causes a signal (SIGIO by default) to be generated when new input becomes available on the pipe. On Linux, O_ASYNC is supported for pipes only since kernel 2.6. //make pipes non-blocking, so we can read big buffers without having //to use fionread() to prevent blocking gint flags = fcntl (stdout_pipe[INWARDS], F_GETFL); if (flags != -1) fcntl (stdout_pipe[INWARDS], F_SETFL, flags | O_NONBLOCK | O_ASYNC); flags = fcntl (stderr_pipe[INWARDS], F_GETFL); if (flags != -1) fcntl (stderr_pipe[INWARDS], F_SETFL, flags | O_NONBLOCK | O_ASYNC); */ /* prefer to poll pipes at least once before unblocking SIGCHILD in this thread (ostensibly it's not valid to unblock SIGCHILD in another thread where watching happens, but for some kernels that does seem to help, so unblocking also happens in _e2_command_watch()) So for async at least we use a timer which checks a flag set in _e2_command_watch() CHECKME can timer persist past rt destruction at session end ? etc ? Timer interval is compromise between prompt cleanup when there is little or no initial input, and the amount of interruption of the initial input loop when that involves a lot of printing - so we use a 2-stage timer */ pthread_t wthreadID; if (sync) { gdk_threads_leave (); //BGL management happens downstream if (pthread_create (&wthreadID, NULL, (gpointer)_e2_command_watch, rt)) { _e2_command_unblock_childsignal (); gdk_threads_enter (); goto launch_error; } //FIXME unblock-timer won't work until we go back to gtk // usleep (10000); _e2_command_unblock_childsignal (); //allow any child's status change to be noticed //FIXME allow command output to be printed while waiting for completion //probably by running as for async, but blocking start of any other action // or command (except send to child) pthread_join (wthreadID, NULL); gdk_threads_enter (); return cmddata->exit; } else //async execution { // g_timeout_add_full (G_PRIORITY_HIGH, 2, // (GSourceFunc) _e2_command_checkblock, &cmddata->pollcount, NULL); pthread_attr_t attr; pthread_attr_init (&attr); pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); if (pthread_create (&wthreadID, &attr, (gpointer)_e2_command_watch, rt)) { _e2_command_unblock_childsignal (); goto launch_error; } _e2_command_unblock_childsignal (); return 0; } launch_error: #ifdef E2_VFS # ifdef E2_VFSTMP if supporting get-and-exec, and getting is slow, some cd-race might occur need relevant PlaceInfo which might not be curr_view->spacedata->workplace # endif ddata.localpath = args[0]; ddata.spacedata = curr_view->spacedata; #endif gdk_threads_leave (); //downstream does mutex management e2_fs_error_simple (_("Error while launching '%s'"), #ifdef E2_VFS &ddata); #else args[0]); #endif gdk_threads_enter (); return 1; } /** @brief pipe utf-8 string @a message to process @a pid The specified process must be a child of the current e2 instance @param pid the id of the destination process, or 0 for the last-created child @param message, utf8 string, to send to process @a pid @return TRUE if the string was sent without error */ static gboolean _e2_command_send_to_pid (glong pid, gchar *message) { printd (DEBUG, "_e2_command_send_to_pid (pid:%d,messge:%s)", pid, message); gchar *msg2 = NULL; E2_TaskRuntime *rt; if (pid == 0) { rt = e2_task_find_last_running_child (FALSE); if (rt == NULL) msg2 = g_strdup (_("Cannot find last child process")); else pid = rt->pid; //in case of error message } else { rt = e2_task_find_running_task (pid); if (rt == NULL) msg2 = g_strdup_printf (_("Cannot find child process with pid %ld"), pid); } if (rt != NULL && rt->action) msg2 = g_strdup_printf (_("Cannot communicate to process %ld"), pid); if (msg2 != NULL) { e2_output_print_error (msg2, TRUE); return FALSE; } //CHECKME required message encoding ? msg2 = g_strconcat (message, "\n", NULL); size_t size = strlen (msg2); ssize_t sent = write (rt->ex.command.child_stdin_fd, msg2, size); if (sent != size) //FIXME handle -1 error or keep trying to write it all { g_free (msg2); msg2 = _("Failed writing to child"); e2_output_print (rt->current_tab, msg2, rt->pidstr, TRUE, E2_ERRORTAGS, NULL); e2_utils_beep (); return FALSE; } else if (rt->ex.command.show) //report what has been sent FIXME what if it's a password ? e2_output_print (rt->current_tab, msg2, rt->pidstr, FALSE, NULL); g_free (msg2); return TRUE; } #else //ndef E2_NEW_COMMAND ======================= /** @brief process non-error-iochannel data @param ioc the channel passing the data @param cond flags indicating the condition(s) that have been satisfied @param rt pointer to data struct for the command to which the channel applies @return FALSE if the event source should be removed */ static gboolean _e2_command_watch_std (GIOChannel *ioc, GIOCondition cond, E2_TaskRuntime *rt) { return _e2_command_watch (ioc, cond, rt, FALSE); } /** @brief process error-iochannel data @param ioc the channel passing the data @param cond flags indicating the condition(s) that have been satisfied @param rt pointer to data struct for the command to which the channel applies @return FALSE if the event source should be removed */ static gboolean _e2_command_watch_err (GIOChannel *ioc, GIOCondition cond, E2_TaskRuntime *rt) { return _e2_command_watch (ioc, cond, rt, TRUE); } /** @brief process iochannel data Data are grabbed in parcels up to 4095 bytes, which are then separated into lines (\n), and those lines are sent to the output printer Will shut the channel down upon error @param ioc the channel event source @param cond flags indicating the condition(s) that have been satisfied @param rt pointer to data struct for the command to which the channel applies @param error TRUE if this call relates to the error channel @return FALSE if the event source should be removed */ static gboolean _e2_command_watch (GIOChannel *ioc, GIOCondition cond, E2_TaskRuntime *rt, gboolean error) { printd (DEBUG, "_e2_command_watch (ioc:,cond:%x,rt:,%s channel)", cond, (error) ? "stderr":"stdout"); E2_CommandTaskData *cmddata = &rt->ex.command; if (cond & (G_IO_IN | G_IO_PRI)) { gchar buf[5000]; //space (rounded) to read 4096 and add trailing \0 //buf[0] = buf[4096] = '\0'; gsize len = 0; // _e2_command_block_childsignal (); GIOStatus ret; reread: ret = g_io_channel_read_chars (ioc, buf, 4096 * sizeof(gchar), &len, NULL); if (ret == G_IO_STATUS_NORMAL && len > 0) //len==0 is also a proxy for ret == G_IO_STATUS_EOF { // if (cmddata->show || error) // { // if (len < 4096) *(buf + len) = '\0'; gdk_threads_enter (); if (error) e2_output_print (rt->current_tab, buf, rt->pidstr, FALSE, E2_ERRORTAGS, NULL); else e2_output_print (rt->current_tab, buf, rt->pidstr, FALSE, NULL); gdk_threads_leave (); /* line-by-line processing needed if we want to parse and handle CR's BS's etc { gchar save; //ignore compiler warning gchar *sep; gchar *print = buf; gboolean more = TRUE; while (more) { //always ascii \n, don't need utf8 search if ((sep = strchr (print, '\n')) != NULL) { sep++; save = *sep; *sep = '\0'; } else more = FALSE; //printd (DEBUG, "printing for tab %x", rt->current_tab); if (error) e2_output_print (rt->current_tab, print, rt->pidstr, FALSE, E2_ERRORTAGS, NULL); else e2_output_print (rt->current_tab, print, rt->pidstr, FALSE, NULL); if (more) { *sep = save; print = sep; } } } */ // } // _e2_command_unblock_childsignal (); return TRUE; } else if (ret == G_IO_STATUS_AGAIN) goto reread; // _e2_command_unblock_childsignal (); } if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) { gint fd; if (error) { //shutting down child's error channel normally with flag G_IO_HUP // printd (DEBUG, "stderr iochannel cleanup"); // rt->status = E2_TASK_COMPLETED; //possible race here - SIGCHILD handler may be called after this //so store a dummy exit value if that handler has not logged the real value if ((cond & (G_IO_ERR | G_IO_NVAL)) && rt->status != E2_TASK_COMPLETED) { rt->status = E2_TASK_INCOMPLETE; //ensure this cmddata->exit = 1; // printd (DEBUG, "child status 1 stored"); } // else // printd (DEBUG, "child status NOT stored here"); fd = g_io_channel_unix_get_fd (ioc); g_io_channel_shutdown (ioc, TRUE, NULL); // g_io_channel_unref (ioc); the return FALSE has this effect e2_fs_safeclose (fd); //shutdown the output channel // if (cmddata->to_child != NULL) // { fd = g_io_channel_unix_get_fd (cmddata->to_child); g_io_channel_shutdown (cmddata->to_child, FALSE, NULL); g_io_channel_unref (cmddata->to_child); //like source-removal e2_fs_safeclose (fd); // } } else { //shutting down child's stdout channel normally with flag G_IO_HUP // printd (DEBUG, "stdout iochannel cleanup"); // _e2_command_block_childsignal (); fd = g_io_channel_unix_get_fd (ioc); //shutdown the error input channel g_io_channel_shutdown (ioc, TRUE, NULL); // g_io_channel_unref (ioc); the return FALSE has this effect e2_fs_safeclose (fd); //irrelevant for *NIX g_spawn_close_pid (rt->pid); //possible race here - SIGCHILD handler may be called after this //so store a dummy exit value if that handler has not logged the real value if (!(cond & (G_IO_ERR | G_IO_NVAL)) && rt->status != E2_TASK_COMPLETED) { cmddata->exit = 0; // printd (DEBUG, "child status 0 stored"); } // else // printd (DEBUG, "child status NOT stored here"); if (cmddata->show && e2_option_bool_get ("fileop-show")) { gchar *message = g_strdup_printf ("%s%s (%s) %s '%d'\n", (cmddata->extshell) ? "sh>" : ">", cmddata->command, rt->pidstr, _("returned"), cmddata->exit); gdk_threads_enter (); e2_output_print (rt->current_tab, message, rt->pidstr, TRUE, "small", (cmddata->exit == 0) ? "green" : "red", NULL); gdk_threads_leave (); g_free (message); printd (DEBUG, "child status printed"); } else { //this is a hack to get small amounts of command-output text to //display when there's no completion message gdk_threads_enter (); e2_output_print (rt->current_tab, "", rt->pidstr, FALSE, NULL); gdk_threads_leave (); } // _e2_command_unblock_childsignal (); } return FALSE; //removes the source } return TRUE; //should never get to here } /* * @brief for a sync command, get the real pid before the child command is executed @param rt pointer to task data struct for the command @return */ /*not called in sync command, dunno why static void _e2_command_set_childdata (E2_TaskRuntime *rt) { if (1) //(rt->ex.command) { rt->pid = (glong) getpid (); printd (DEBUG, "command child pid is %ld", rt->pid); //FIXME setup to watch descendants stdout, stderr } } */ /** @brief set up channel for receiving data from a child process This is called twice for each async child (for child's stdout and stderr channels) @param fd descriptor for the channel (from g_spawn_async_with_pipes()) @param cond flags setting the mode of channel operation @param func the function to be called when @a cond is satisfied @param rt pointer to data struct for the command to which the channel applies @return the channel */ static GIOChannel *_e2_command_setup_watch_channel (gint fd, GIOCondition cond, gpointer func, E2_TaskRuntime *rt) { GIOChannel *ioc = g_io_channel_unix_new (fd); // Set IOChannel encoding to none, to make it fit for binary data //(which seems to mean that _any_ encoding will work) g_io_channel_set_encoding (ioc, NULL, NULL); g_io_channel_set_buffered (ioc, FALSE); g_io_channel_set_close_on_unref (ioc, TRUE); g_io_add_watch_full (ioc, e2_option_int_get ("command-watch-priority"), cond, (GIOFunc) func, rt, NULL); //undo reference added by g_io_add_watch () g_io_channel_unref (ioc); return ioc; } /* static gboolean _e2_command_watch_out (GIOChannel *ioc, GIOCondition cond, E2_TaskRuntime *rt) { printd (DEBUG, "command can be written to"); return TRUE; } */ /** @brief asynchronously run shell command This sets the executed command's CWD to curr_view->dir Expects BGL closed @param command the command string, used in messages, utf8 @param args pointer to command arguments array, each localised @param cwd path of directory to use as CWD for the command, utf8 @param show TRUE to show @a command and its pid in the output pane @param extshell TRUE when command is to be run in external shell @return the pid of process running the command, or 0 if there's an error */ //FIXME when this sort of mechanism is nested in a child process, that process hangs #if E2_DEBUG_LEVEL > 2 GIOChannel *stdoutchannel; GIOChannel *stderrchannel; #endif static gint _e2_command_run_async (gchar *command, gchar **args, const gchar *cwd, gboolean show, gboolean extshell) { printd (DEBUG, "asynchronously run command %s", command); _e2_command_block_childsignal (); //prevent feedback from child until ready to get it if (cwd == NULL) cwd = curr_view->dir; gchar *local; #ifdef E2_VFS //for a non-native place, there is no analog for curr_view->dir, so go elsewhere //all args need to have been conformed, by the command interpreter local = (curr_view->spacedata == NULL) ? F_FILENAME_TO_LOCALE (cwd) : curr_view->spacedata->workplace; #else local = F_FILENAME_TO_LOCALE (cwd); #endif GPid pid = 0; gint stdinfd = 0, stdoutfd = 0, stderrfd = 0; //file descriptors to write to and read from child GError *error = NULL; g_spawn_async_with_pipes (local, args, NULL, G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH, NULL, NULL, &pid, &stdinfd, &stdoutfd, &stderrfd, &error); #ifdef E2_VFS if (curr_view->spacedata == NULL) #endif F_FREE (local); //CHECKME revert CWD to curr_view->dir if that still exists ? if (error != NULL) { gchar *message = g_strconcat (extshell ? "sh>" : ">", command, " ", NULL); //always use the current tab at the start of the command e2_output_print (&app.tab, message, NULL, TRUE, "bold", "blue", NULL); g_free (message); message = g_strconcat (error->message, ".", NULL); e2_output_print_error (message, TRUE); g_error_free (error); _e2_command_unblock_childsignal (); return 0; } E2_TaskRuntime *rt; //keep trying this until data set fn not busy while ((rt = e2_task_set_data ((glong)pid, E2_TASKTYPE_ASYNC, command)) == NULL); if (rt == GINT_TO_POINTER (1)) { //CHECKME cleanup the spawn ? _e2_command_unblock_childsignal (); return 0; } rt->ex.command.show = show; rt->ex.command.extshell = extshell; //channel for getting the child's stdout #if E2_DEBUG_LEVEL > 2 stdoutchannel = #endif _e2_command_setup_watch_channel (stdoutfd, G_IO_IN | G_IO_PRI | G_IO_HUP, _e2_command_watch_std, rt); //channel for getting the child's stderr #if E2_DEBUG_LEVEL > 2 stderrchannel = #endif _e2_command_setup_watch_channel (stderrfd, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL, _e2_command_watch_err, rt); //channel for sending to child stdin //FIXME do this only if/when needed ? GIOChannel *ioc = g_io_channel_unix_new (stdinfd); g_io_channel_set_encoding (ioc, NULL, NULL); //binary (=any) encoding g_io_channel_set_buffered (ioc, FALSE); g_io_channel_set_close_on_unref (ioc, TRUE); // g_io_add_watch_full (ioc, e2_option_int_get ("command-watch-priority"), // G_IO_OUT, (GIOFunc) _e2_command_watch_out, rt, NULL); //undo reference added by g_io_add_watch () // g_io_channel_unref (ioc); rt->ex.command.to_child = ioc; if (show && e2_option_bool_get ("fileop-show")) { gchar *message = g_strconcat (extshell ? "sh>" : ">", command, NULL); e2_output_print (rt->current_tab, message, rt->pidstr, FALSE, "bold", "blue", NULL); g_free (message); message = g_strdup_printf (" (%d)\n", pid); e2_output_print (rt->current_tab, message, rt->pidstr, FALSE, "small", "blue", NULL); g_free (message); } _e2_command_unblock_childsignal (); return pid; } /** @brief run command @a command synchronously (block until it's finished) This sets the executed command's CWD to curr_view->dir Expects BGL to be closed @param command the command string, used in messages, utf8 @param args pointer to command arguments array, each localised @param cwd path of directory to use as CWD for the command, utf8 @param show TRUE to show @a command and its exit status in the output pane @param extshell TRUE when command is to be run in external shell @return the exit code of the command, > 0 if error */ static gint _e2_command_run_sync (gchar *command, gchar **args, const gchar *cwd, gboolean show, gboolean extshell) { printd (DEBUG, "synchronously run command %s", command); //we dont get real pid's from static commands, so we fake //by using -ve ones in simple sequence static glong fakepid = 0; fakepid--; gchar *message; E2_TaskRuntime *rt; //keep trying this until data set fn not busy //the child-signal handler will not be able to find this data, due to fake pid while ((rt = e2_task_set_data (fakepid, E2_TASKTYPE_SYNC, command)) == NULL); if (rt == GINT_TO_POINTER (1)) { //CHECKME warn user ? fakepid++; return 1; } //but mark-names starting with "-" are confusing so substitute "s" *(rt->pidstr) = 's'; rt->ex.command.show = show; rt->ex.command.extshell = extshell; if (show && e2_option_bool_get ("fileop-show")) { gchar *message = g_strconcat (extshell ? "sh>" : ">", command, NULL); e2_output_print (rt->current_tab, message, rt->pidstr, FALSE, "bold", "blue", NULL); g_free (message); message = g_strdup_printf (" (%s)\n", rt->pidstr); e2_output_print (rt->current_tab, message, rt->pidstr, FALSE, "small", "blue", NULL); g_free (message); WAIT_FOR_EVENTS; } if (cwd == NULL) cwd = curr_view->dir; // GError *error; gint exit; gchar *sout = NULL, *serr = NULL; gchar *local; #ifdef E2_VFS //for a non-native place, there is no analog for curr_view->dir, so go elsewhere //all args need to have been conformed, by the command interpreter local = (curr_view->spacedata == NULL) ? F_FILENAME_TO_LOCALE (cwd) : curr_view->spacedata->workplace; #else local = F_FILENAME_TO_LOCALE (cwd); #endif _e2_command_block_childsignal (); rt->status = E2_TASK_RUNNING; gboolean success = g_spawn_sync (local, args, NULL, G_SPAWN_SEARCH_PATH, //DOES NOTHING (GSpawnChildSetupFunc) _e2_command_set_childdata, rt, NULL, NULL, &sout, &serr, &exit, NULL); //&error); //no rt cleanup after error rt->status = (success) ? E2_TASK_COMPLETED : E2_TASK_FAILED; //CHECKME revert CWD to curr_view->dir if that still exists ? _e2_command_unblock_childsignal (); rt->ex.command.exit = exit; if (success && show && sout != NULL && *sout != '\0') { e2_output_print (rt->current_tab, sout, rt->pidstr, FALSE, NULL); } if (success && serr != NULL && *serr != '\0') { e2_output_print_error (serr, FALSE); } if (sout != NULL) g_free (sout); if (serr != NULL) g_free (serr); #ifdef E2_VFS if (curr_view->spacedata == NULL) #endif F_FREE (local); if (success && show && e2_option_bool_get ("fileop-show")) { message = g_strdup_printf ("%s%s (%s) %s '%d'\n", (extshell) ? "sh>" : ">", command, rt->pidstr, _("returned"), WEXITSTATUS (exit)); e2_output_print (rt->current_tab, message, rt->pidstr, TRUE, "small", (WEXITSTATUS (exit) == 0) ? "green" : "red", NULL); g_free (message); } if (!success) { message = g_strdup_printf (_("Strange error: could not run '%s'"), command); e2_output_print_error (message, TRUE); } return WEXITSTATUS (exit); } /** @brief pipe utf-8 string @a message to process @a pid The specified process must be a child of the current e2 instance @param pid the id of the destination process, or 0 for the last-created child @param message utf string to send to process @a pid @return TRUE if the string was sent without error */ static gboolean _e2_command_send_to_pid (glong pid, gchar *message) { gchar *errmsg; printd (DEBUG, "e2_command_send_to_pid (pid:%d,messge:%s)", pid, message); E2_TaskRuntime *rt; rt = (pid == 0) ? e2_task_find_last_running_child (FALSE): e2_task_find_running_task (pid); if (rt == NULL || rt->action) { errmsg = g_strdup_printf (_("The process with pid %ld is not our child"), pid); e2_output_print_error (errmsg, TRUE); return FALSE; } gsize size; GIOStatus stat; GError *error = NULL; //CHECKME what if the process doesn't understand utf-8 ? stat = g_io_channel_write_chars (rt->ex.command.to_child, message, -1, &size, &error); if (error != NULL) { errmsg = g_strdup_printf (_("Failed writing to child: %s"), error->message); printd (WARN, errmsg); e2_output_print (rt->current_tab, errmsg, rt->pidstr, TRUE, E2_ERRORTAGS, NULL); g_error_free (error); g_free (errmsg); return FALSE; } error = NULL; stat = g_io_channel_write_chars (rt->ex.command.to_child, "\n", 1, &size, &error); if (error != NULL) { errmsg = g_strdup_printf (_("Failed writing to child: %s"), error->message); printd (WARN, errmsg); e2_output_print (rt->current_tab, errmsg, rt->pidstr, TRUE, E2_ERRORTAGS, NULL); g_error_free (error); g_free (errmsg); return FALSE; } else if (rt->ex.command.show) { //signal what has been sent e2_output_print (rt->current_tab, message, rt->pidstr, TRUE, NULL); } return TRUE; } #endif //def E2_NEW_COMMAND ======================== /** @brief kill child process with id @a pid @param pid the id of the process we're looking for @return TRUE if a child process with that pid was found and killed */ gboolean e2_command_kill_child (guint pid) { E2_TaskRuntime *rt = e2_task_find_running_task ((glong)pid); if (!(rt == NULL || rt->action)) { //it's one of ours if (!kill ((pid_t) pid, SIGTERM)) //or SIGKILL { rt->pid = -2L; //no more matching rt->status = E2_TASK_ABORTED; return TRUE; } e2_output_print_strerrno (); } return FALSE; } /** @brief check whether process with id @a pid is running @param pid the id of the process we're looking for @return TRUE if a process with that pid is running */ gboolean e2_command_find_process (guint pid) { pid_t result = waitpid (pid, NULL, WNOHANG); return (result == 0); //will be -1 if finished before, result if finished now } /** @brief update all relevant child foreground-tab pointers after output pane tab change @param currenttab pointer to data for tab to be replaced @param replacetab pointer to data for tab to be substituted for @a currenttab @return */ void e2_command_retab_children (E2_OutputTabRuntime *currenttab, E2_OutputTabRuntime *replacetab) { E2_TaskRuntime *rt; GList *member; #ifndef E2_NEW_COMMAND _e2_command_block_childsignal (); #endif pthread_mutex_lock (&task_mutex); member = app.taskhistory; pthread_mutex_unlock (&task_mutex); while (member != NULL) { rt = (E2_TaskRuntime *) member->data; if (rt != NULL && (rt->status == E2_TASK_RUNNING || rt->status == E2_TASK_PAUSED)) { if (rt->current_tab == currenttab) rt->current_tab = rt->background_tab; else if (rt->background_tab == replacetab) rt->current_tab = currenttab; } pthread_mutex_lock (&task_mutex); member = member->next; pthread_mutex_unlock (&task_mutex); } #ifndef E2_NEW_COMMAND _e2_command_unblock_childsignal (); #endif } /** @brief update tab ptrs of children using a deleted tab @param currenttab the deleted tab's data struct @param replacetab the replacement tab's data struct @return */ void e2_command_retab2_children (E2_OutputTabRuntime *currenttab, E2_OutputTabRuntime *replacetab) { E2_TaskRuntime *rt; GList *member; #ifndef E2_NEW_COMMAND _e2_command_block_childsignal (); #endif pthread_mutex_lock (&task_mutex); member = app.taskhistory; pthread_mutex_unlock (&task_mutex); while (member != NULL) { rt = (E2_TaskRuntime *) member->data; if (rt != NULL && (rt->status == E2_TASK_RUNNING || rt->status == E2_TASK_PAUSED) && rt->background_tab == currenttab) { rt->background_tab = replacetab; rt->current_tab = replacetab; } pthread_mutex_lock (&task_mutex); member = member->next; pthread_mutex_unlock (&task_mutex); } #ifndef E2_NEW_COMMAND _e2_command_unblock_childsignal (); #endif } /** @brief clear pending commands from queue This is an action, results shown in the current output tab @param from UNUSED the activated widget @param art UNUSED runtime data sent to this action @return TRUE if anything pending is removed */ gboolean e2_command_clear_pending (gpointer from, E2_ActionRuntime *art) { E2_TaskRuntime *rt; GList *member; gboolean retval = FALSE; #ifndef E2_NEW_COMMAND _e2_command_block_childsignal (); #endif //to avoid iteration issues, a 2-stage process ... pthread_mutex_lock (&task_mutex); member = app.taskhistory; pthread_mutex_unlock (&task_mutex); while (member != NULL) { rt = (E2_TaskRuntime *) member->data; if (rt->status <= E2_TASK_QUEUED) //Q or none { //rt->pidstr not set yet for queued items if (rt->action) { g_free (rt->ex.action.currdir); g_free (rt->ex.action.othrdir); if (rt->ex.action.names != NULL) e2_fileview_clean_selected (rt->ex.action.names); if (rt->ex.action.rt_data != NULL) g_free (rt->ex.action.rt_data); } else { g_free (rt->ex.command.command); g_free (rt->ex.command.currdir); #ifdef E2_COMMANDQ g_free (rt->ex.command.othrdir); if (rt->ex.command.names != NULL) //active pane selected items array { g_ptr_array_foreach (rt->ex.command.names, (GFunc)g_free, NULL); g_ptr_array_free (rt->ex.command.names, TRUE); } if (rt->ex.command.othernames != NULL) //inactive pane selected items array { g_ptr_array_foreach (rt->ex.command.othernames, (GFunc)g_free, NULL); g_ptr_array_free (rt->ex.command.othernames, TRUE); } # ifdef E2_VFSTMP //FIXME clear "fstab" data for 2 panes if command is queued and needs CWD or has %fFdDpP # endif #endif } DEALLOCATE (E2_TaskRuntime, rt); member->data = NULL; retval = TRUE; } pthread_mutex_lock (&task_mutex); member = member->next; pthread_mutex_unlock (&task_mutex); } pthread_mutex_lock (&task_mutex); app.taskhistory = g_list_remove_all (app.taskhistory, NULL); pthread_mutex_unlock (&task_mutex); #ifndef E2_NEW_COMMAND _e2_command_unblock_childsignal (); #endif return retval; } /** @brief list queued pending commands This is an action, results shown in the current output tab @param from UNUSED the activated widget @param art UNUSED runtime data sent to this action @return TRUE if there is any pending command */ static gboolean _e2_command_list_pending (gpointer from, E2_ActionRuntime *art) { // printd (DEBUG, "e2_command_list_pending (data:)"); //find start of waiting item(s) in Q E2_TaskRuntime *rt; GList *member; //quick check for something relevant pthread_mutex_lock (&task_mutex); member = app.taskhistory; pthread_mutex_unlock (&task_mutex); while (member != NULL) { rt = (E2_TaskRuntime *) member->data; if (rt->status <= E2_TASK_QUEUED) break; pthread_mutex_lock (&task_mutex); member = member->next; pthread_mutex_unlock (&task_mutex); } if (member == NULL) { e2_output_print (&app.tab, _("nothing is waiting"), NULL, TRUE, NULL); return FALSE; } gchar *srcdir, *shortdir, *message; message = _("command"); e2_output_print (&app.tab, message, NULL, TRUE, "bold", "uline", NULL); #ifndef E2_NEW_COMMAND _e2_command_block_childsignal (); #endif while (member != NULL) { rt = (E2_TaskRuntime *) member->data; if (rt != NULL && rt->status <= E2_TASK_QUEUED) { if (rt->action) { E2_ActionTaskData *atask = &rt->ex.action; srcdir = F_FILENAME_FROM_LOCALE (atask->currdir); shortdir = e2_utils_str_shorten (srcdir, CMDNAMELEN, E2_DOTS_START); gchar *subject; if (g_str_has_prefix (atask->action->name, _A(5))) { //this was a "file-action" subject = (atask->rt_data == NULL || *((gchar *)atask->rt_data) == '\0') ? _("") : (gchar *)atask->rt_data; } else subject = ""; //this results in a redundant space in the line message = g_strdup_printf ("%s %s || %s", atask->action->name, subject, shortdir); F_FREE (srcdir); } else { shortdir = e2_utils_str_shorten (rt->ex.command.currdir, CMDNAMELEN, E2_DOTS_START); gchar *shortcmd = e2_utils_str_shorten (rt->ex.command.command, CMDNAMELEN, E2_DOTS_END); message = g_strdup_printf ("%s || %s", shortcmd, shortdir); g_free (shortcmd); } e2_output_print (&app.tab, message, NULL, TRUE, NULL); g_free (shortdir); g_free (message); } pthread_mutex_lock (&task_mutex); member = member->next; pthread_mutex_unlock (&task_mutex); } #ifndef E2_NEW_COMMAND _e2_command_unblock_childsignal (); #endif e2_output_print_end (&app.tab, FALSE); return TRUE; } /** @brief count the active child processes of this session This counts child processes, if @a countcmds is TRUE, and actions (queued or not) in the task history list and marked as currently-running, or (if @a countpaused is TRUE, paused @param countcmds TRUE to include all active commands in the count @param countpaused TRUE to include paused actions in the count @return the number of currently-running commands and actions */ guint e2_command_count_running_tasks (gboolean countcmds, gboolean countpaused) { guint i = 0; E2_TaskRuntime *rt; GList *member; pthread_mutex_lock (&task_mutex); member = app.taskhistory; pthread_mutex_unlock (&task_mutex); while (member != NULL) { rt = (E2_TaskRuntime *) member->data; if (rt != NULL && (rt->action || (countcmds && !rt->action)) && (rt->status == E2_TASK_RUNNING || (countpaused && (rt->status == E2_TASK_PAUSED)))) i++; pthread_mutex_lock (&task_mutex); member = member->next; pthread_mutex_unlock (&task_mutex); } return i; } /** @brief list the active child processes (commands and actions) of the current session This is an action, results shown in the current tab @param from UNUSED the activated widget @param art UNUSED runtime data sent to this action @return TRUE if there are any children */ static gboolean _e2_command_list_children (gpointer from, E2_ActionRuntime *art) { // printd (DEBUG, "_e2_command_list_children (data:)"); if (e2_command_count_running_tasks (TRUE, TRUE) == 0) { e2_output_print (&app.tab, _("nothing is running"), NULL, TRUE, NULL); return FALSE; } gchar *srcdir, *shortdir, *message; message = _(" pid || directory || command"); e2_output_print (&app.tab, message, NULL, TRUE, "bold", "uline", NULL); E2_TaskRuntime *rt; GList *member; #ifndef E2_NEW_COMMAND _e2_command_block_childsignal (); #endif pthread_mutex_lock (&task_mutex); member = app.taskhistory; pthread_mutex_unlock (&task_mutex); while (member != NULL) { rt = (E2_TaskRuntime *) member->data; if (rt != NULL && (rt->status == E2_TASK_RUNNING || rt->status == E2_TASK_PAUSED)) { if (rt->action) { E2_ActionTaskData *atask = &rt->ex.action; srcdir= F_FILENAME_FROM_LOCALE (atask->currdir); shortdir = e2_utils_str_shorten (srcdir, CMDNAMELEN, E2_DOTS_START); message = g_strdup_printf (" %6s || %s || %s %s", rt->pidstr, shortdir, atask->action->name, (atask->rt_data == NULL) ? _("") : (gchar *)atask->rt_data); F_FREE (srcdir); } else { shortdir = e2_utils_str_shorten (rt->ex.command.currdir, CMDNAMELEN, E2_DOTS_START); message = g_strdup_printf (" %6s || %s || %s", rt->pidstr, shortdir, rt->ex.command.command); } e2_output_print (&app.tab, message, NULL, TRUE, NULL); g_free (shortdir); g_free (message); } pthread_mutex_lock (&task_mutex); member = member->next; pthread_mutex_unlock (&task_mutex); } #ifndef E2_NEW_COMMAND _e2_command_unblock_childsignal (); #endif e2_output_print_end (&app.tab, FALSE); return TRUE; } /** @brief list the command history of the current session This is an action, results shown in the current output tab @param from UNUSED the activated widget @param art UNUSED data sent to this action @return TRUE if there is a history */ static gboolean _e2_command_list_history (gpointer from, E2_ActionRuntime *art) { // printd (DEBUG, "e2_command_list_history (data:)"); gchar *srcdir, *shortdir, *message; message = _("command || directory || result"); e2_output_print (&app.tab, message, NULL, TRUE, "bold", "uline", NULL); E2_TaskRuntime *rt; GList *member; #ifndef E2_NEW_COMMAND _e2_command_block_childsignal (); #endif pthread_mutex_lock (&task_mutex); member = app.taskhistory; pthread_mutex_unlock (&task_mutex); while (member != NULL) { rt = (E2_TaskRuntime *) member->data; if (rt != NULL && rt->status >= E2_TASK_COMPLETED) { if (rt->action) { E2_ActionTaskData *atask = &rt->ex.action; srcdir = F_FILENAME_FROM_LOCALE (atask->currdir); shortdir = e2_utils_str_shorten (srcdir, CMDNAMELEN, E2_DOTS_START); gchar *subject; if (g_str_has_prefix (atask->action->name, _A(5))) { //this was a "file-action" subject = (atask->rt_data == NULL || *((gchar *)atask->rt_data) == '\0') ? _("") : (gchar *)atask->rt_data; } else subject = ""; //this results in a redundant space in the line message = g_strdup_printf ("%s %s || %s || %s", atask->action->name, subject, shortdir, (atask->result) ? _("OK") : _("error")); F_FREE (srcdir); } else { E2_CommandTaskData *ctask = &rt->ex.command; shortdir = e2_utils_str_shorten ((gchar *)ctask->currdir, CMDNAMELEN, E2_DOTS_START); gchar *shortcmd = e2_utils_str_shorten (rt->ex.command.command, CMDNAMELEN, E2_DOTS_END); if (ctask->exit == 0) message = g_strdup_printf ( "%s || %s || %s", shortcmd, shortdir, _("OK")); else message = g_strdup_printf ( "%s || %s || %s %d", shortcmd, shortdir, _("error"), ctask->exit); g_free (shortcmd); } e2_output_print (&app.tab, message, NULL, TRUE, NULL); g_free (shortdir); g_free (message); } pthread_mutex_lock (&task_mutex); member = member->next; pthread_mutex_unlock (&task_mutex); } #ifndef E2_NEW_COMMAND _e2_command_unblock_childsignal (); #endif e2_output_print_end (&app.tab, FALSE); return TRUE; } /** @brief set or display value of @a variable stored in variables list If @a value is "" or NULL, any matching variable is removed from the list If @a var_name is "", any variable whose value is @a value is displayed. If @a var_name is and @a valuer are both "", all variables are displayed. @param var_name utf variable-name string @param value utf8 variable-value string @return */ static void _e2_command_handle_variable_value (gchar *var_name, gchar *value) { gchar *this; GList *member; g_strstrip (var_name); g_strstrip (value); if (*var_name == '\0') //display { if (*value == '\0') { //just "=" for (member = variables; member != NULL; member = member->next) e2_output_print (&app.tab, (gchar *)member->data, NULL, FALSE, NULL); e2_output_print_end (&app.tab, FALSE); } else { this = g_strconcat ("=", value, NULL); for (member = variables; member != NULL; member = member->next) { if (g_str_has_suffix ((gchar*)member->data, this)) { e2_output_print (&app.tab, (gchar *)member->data, NULL, FALSE, NULL); break; } } g_free (this); } } else //set or clear { gchar *prefix = g_strconcat (var_name, "=", NULL); for (member = variables; member != NULL; member = member->next) { this = (gchar *)member->data; if (g_str_has_prefix (this, prefix)) { g_free (this); if (*value == '\0') variables = g_list_delete_link (variables, member); else member->data = g_strconcat (prefix, value, NULL); return; } } this = g_strconcat (prefix, value, NULL); variables = g_list_append (variables, this); } } /** @brief get value of @a variable stored in variables list @param var_name utf variable-name string @return pointer to value string, or NULL if none found */ const gchar *e2_command_get_variable_value (gchar *var_name) { gchar *this, *retval; gchar *prefix = g_strconcat (var_name, "=", NULL); GList *member; for (member = variables; member != NULL; member = member->next) { this = (gchar *)member->data; if (g_str_has_prefix (this, prefix)) { retval = this + strlen (prefix); return retval; } } return NULL; } /** @brief check whether @a command has any "modifiers" @param command utf command string @param macros pointer to flag, set FALSE if leading '!' found @param shell pointer to flag, set TRUE if leading '>' found @param wait pointer to flag, set TRUE if leading '|' found @param show pointer to flag, set FALSE if trailing '&' found @return integer, the byte-offset into @a command where the next char to be processed is */ static gint _e2_command_run_checks (gchar *command, gboolean *macros, gboolean *shell, gboolean *wait, gboolean *show) { gint retval = 0; gboolean done = FALSE; //checks here are all for single-gchar characters, no need for utf functions while (!done) { switch (command[0]) { case ' ': //skip leading whitespace case '\t': break; case '!': *macros = FALSE; break; case '>': *shell = TRUE; break; case '|': *wait = TRUE; break; default: done = TRUE; break; } if (!done) { retval++; command++; } } if (!*shell) { //force shell for commands that we know must go to shell //(redirection chars etc) //this will be corrected later for gchar *s = e2_utils_bare_strchr (command, '>'); if (s != NULL && s > command && *(s+1) != '\0') *shell = TRUE; else if ((s = e2_utils_bare_strchr (command, '|')) != NULL && s > command && *(s+1) != '\0') *shell = TRUE; else if ((s = e2_utils_bare_strchr (command, '<')) != NULL && s > command && *(s+1) != '\0') *shell = TRUE; else if ((s = e2_utils_bare_strchr (command, '`')) != NULL && (s = e2_utils_bare_strchr (s+1, '`')) != NULL) *shell = TRUE; } if (!*shell && g_str_has_suffix (command, "&")) { *show = FALSE; command[strlen (command) - sizeof (gchar)] = '\0'; } return retval; } /** @brief run a single command @a raw, after interpreting its contents Downstream functions expect BGL to be closed This function does not set or use CWD, but downstream functions for commands (not actions) use curr_view->dir Command modifiers (!>&) are interpreted. $... variables are interpreted %... macros are expanded unless the "no-expand" modifier '!' is present at the start of @a raw. Any 'pid:' directive is interpreted. The 'pid' string must describe a base 10 integer. Any "\" that doesn't follows another "\" is simply removed. Any "~" followed by "/" is interpreted as $HOME, as is a "~" by itself. If * or ? is in any argument, that will be expanded, if possible, into a series of matching item-names in the active directory. "cd" commands that are not run in a separate shell just change the dir shown in the active pane. In that case the new dir must be separated by one or more ' ' chars (ie no tabs), or "cd" alone is interpreted as "cd $HOME". Command arguments for must be separated by one or more ' ' chars (ie no tabs) NOTE used to return pid of process running the command (if any) @param raw utf-8 command string @param cwd utf8 path string, maybe with trailer, to use as (native) CWD when running @a command NULL for curr_view->dir @param range flags for the scope of the command @return 0 for success, >0 for error */ //FIXME more consistent returned value static gint _e2_command_run_single (gchar *raw, const gchar *cwd, E2_CommandRange range) { //CHECKME should variables be volatilized for threaded usage ? #ifdef E2_VFSTMP //BIG FIXME handling command arguments etc when active pane is vdir #endif static gchar *prior_cmd = NULL; //for preventing recursive alias substitution gchar *command1, *command2, *freeme; gint result; gboolean expand_macros = TRUE; gboolean run_in_shell = FALSE; gboolean wait = FALSE; gboolean show = TRUE; //#ifdef E2_NEW_COMMAND gboolean free1 = FALSE; //#endif //1st, find how the command is to be run command1 = raw + _e2_command_run_checks (raw, &expand_macros, &run_in_shell, &wait, &show); //(wait and show will be overridden, later, if range == E2_COMMAND_RANGE_FILE_ACTION) //replace any alias if so desired if (//CHECKME range != E2_COMMAND_RANGE_FILE_ACTION //this is not an action on selected items //&& expand_macros //no instruction to not expand macros //&& e2_option_bool_get ("command-use-aliases") //alias use is expected && (prior_cmd == NULL || strstr (command1, prior_cmd) == NULL) //ok to recurse aliases ) { freeme = e2_alias_apply (command1, &app.aliases); //alias may have substituted a joined command if (e2_utils_bare_strchr (freeme, ';') != NULL) //if always ascii ;, don't need g_utf8_strchr() { //one or more elements of the joined command may have the alias that's //just been replaced, in which case we must prevent a recursive loop command2 = e2_utils_find_whitespace (command1); gint len = (command2 == NULL) ? strlen (command1) : (command2-command1); if (prior_cmd != NULL) g_free (prior_cmd); prior_cmd = g_strndup (command1, len); //go restart the command process #ifdef E2_COMMANDQ result = e2_command_run (freeme, range, TRUE); #else result = e2_command_run (freeme, range); #endif g_free (freeme); g_free (prior_cmd); prior_cmd = NULL; return result; } //check command operation mode again, in case alias changed it command1 = freeme + _e2_command_run_checks (freeme, &expand_macros, &run_in_shell, &wait, &show); } else freeme = NULL; E2_Action *action = e2_action_check (command1); gboolean command_is_action = (action != NULL); if (command_is_action) { run_in_shell = FALSE; //over-ride any explicit or applied flag if (action->exclude & E2_ACTION_EXCLUDE_ACCEL) { gchar *msg = g_strdup_printf (_("Cannot run '%s'"), command1); e2_output_print_error (msg, TRUE); if (freeme != NULL) g_free (freeme); return 1; } //set the correct action-range according to whether the //name begins with "file" and has no argument if (g_str_has_prefix (command1, _A(5))) { command2 = e2_utils_find_whitespace (command1); if (command2 == NULL || e2_utils_pass_whitespace (command2) == NULL) range = E2_COMMAND_RANGE_FILE_ACTION; } else if (range == E2_COMMAND_RANGE_FILE_ACTION) range = E2_COMMAND_RANGE_DEFAULT; } //#ifdef E2_NEW_COMMAND else //for commands, relative (and non-absolute?) paths in the command need to be interpreted { if (g_str_has_prefix (command1, "./") || g_str_has_prefix (command1, "../")) { command2 = e2_utils_find_whitespace (command1); if (command2 != NULL) *command2 = '\0'; gchar *s1 = e2_utils_translate_relative_path ( #ifdef E2_VFSTMP //FIXME which CWD ? OR should we "get" and run an executable in an active-pane vdir or some dir realtive to that? #else curr_view->dir, #endif command1); //strip trailer from the expansion *(s1 + strlen (s1) - sizeof(gchar)) = '\0'; if (command2 != NULL) { *command2 = ' '; //any whitespace will do command2 = g_strconcat (s1, command2, NULL); g_free (s1); } else command2 = s1; command1 = command2; free1 = TRUE; } } //#endif //set some default operating modes for selected-items commands //(overrides show flag in raw or alias, if any) if (!command_is_action && range == E2_COMMAND_RANGE_FILE_ACTION) { //CHECKME is this of any real use ? it's intended mainly for actions // wait = !e2_option_bool_get ("fileop-background"); show = e2_option_bool_get ("fileop-show"); } //if appropriate, arrange to append the selected item(s), //as the (or additional) command argument(s) if (range == E2_COMMAND_RANGE_FILE_ACTION //this is an action on selected items && expand_macros //no instruction to not expand macros && (!command_is_action || g_str_has_prefix (command1, _A(5))) //command or file action //there is no filelist macro already in the command // //FIXME support macros other than with english letters && strstr (command1, "%f") == NULL && strstr (command1, "%F") == NULL && strstr (command1, "%p") == NULL && strstr (command1, "%P") == NULL ) command2 = g_strconcat (command1, " %f", NULL); else command2 = g_strdup (command1); if (freeme != NULL) g_free (freeme); //#ifdef E2_NEW_COMMAND if (free1 && command1 != freeme) g_free (command1); //#endif //newly-allocated command2 is now the focus .. //replace variables etc command1 = e2_utils_replace_vars (command2); g_free (command2); //replace wildcards if (!run_in_shell && expand_macros) command1 = e2_utils_replace_wildcards (command1); //now newly-allocated command1 has focus, command2 is clean //if command is: cd [%]%f, when updir '../' line was clicked, that //item would not be available as a selected item, so ... if (g_str_has_prefix (command1, "cd ")) { //FIXME support macros other than with english letters if (strstr (command1, "%f") != NULL || strstr (command1, "%p") != NULL) { FileInfo *info = e2_fileview_get_selected_first_local (curr_view, TRUE); if (info != NULL //something selected && g_str_equal (info->filename, "..")) { g_free (command1); command1 = g_strdup ("cd .."); //no translation expand_macros = FALSE; //avoid error message } } } //expand macros, if wanted if (expand_macros) { command2 = e2_utils_expand_macros (command1, NULL); if (command2 == NULL) { e2_output_print_error (_("Failed to expand macros"), FALSE); //current tab result = 1; goto cleanup; } else if (command2 == GINT_TO_POINTER(1)) { //the user cancelled in a prompt macro result = 1; goto cleanup; } g_free (command1); command1 = command2; } //replace or clear any variable if so commanded if (!run_in_shell && (command2 = e2_utils_bare_strchr (command1, '=')) != NULL) { *command2 = '\0'; //ensure not "=" in a command parameter gchar *s = e2_utils_find_whitespace (command1); if (s != NULL) s = e2_utils_pass_whitespace (s+1); if (s == NULL) { _e2_command_handle_variable_value (command1, command2+1); return 0; } *command2 = '='; } //interpret any 'pid:' directive /* the 'pid' string must describe a base 10 integer, no bigger than the max integer for the current system i.e. should be 5 bytes long (16385) for 32-bit systems, 10 bytes for 64-bit systems #if sizeof(gint) == 4 # define E2_PID_SIZE 5 #else # define E2_PID_SIZE 10 #endif */ command2 = NULL; if (range & E2_COMMAND_RANGE_TOCHILD) { command2 = e2_utf8_unescape (command1, ' '); result = (_e2_command_send_to_pid (0, command2)) ? 0 : 1; g_free (command2); goto cleanup; } if (//range != E2_COMMAND_RANGE_FILE_ACTION //CHECKME why excluded ? //&& (command2 = strchr (command1, ':')) != NULL //if always ascii ':', don't need g_utf8_strchr() ) { if (command2 > command1) // there was something ahead of the ':' { *command2 = '\0'; gchar *end; glong i = strtol (command1, &end, 10); if (end == command2) //the whole string was used in conversion, so it was //a valid number and nothing else { command2 = e2_utf8_unescape (command2 + 1, ' '); printd (DEBUG, "sending '%s' to process '%s'", command2, command1); result = (_e2_command_send_to_pid (i, command2)) ? 0 : 1; //+1 to skip the '\0' g_free (command2); goto cleanup; } *command2 = ':'; } } //"cd" commands that are not run in a separate shell //just change the dir in the active pane if (!run_in_shell && g_str_has_prefix (command1, "cd")) //no translation { //note: tests here rely on ascii single-byte chars in command string gint len = strlen (command1); if (len > 3 && command1[2] == ' ') //FIXME or \t ? { command2 = e2_utf8_unescape (command1+3, ' '); e2_pane_change_dir (NULL, command2); //command1 is utf-8 or ascii g_free (command2); result = 0; goto cleanup; } if (len == 2) { /* gchar *home = (gchar *) g_get_home_dir (); e2_pane_change_dir (NULL, home); */ e2_pane_change_dir (NULL, "~"); result = 0; goto cleanup; } } else if /*(g_str_has_prefix (command1, "<")) { //action if (strchr (command1, '.') == NULL) //quick check { e2_output_print_error (_("You must specify an action to run, like 'message); e2_output_print_error (msg, TRUE); g_error_free (error); result = 1; goto cleanup; } } #ifndef E2_FILES_UTF8ONLY if (!app.utf8_filenames) { // free = TRUE; //free all args anyway //localise the actual command strings gchar *local; gchar **tmp = args; while (*tmp != NULL) { //we always take a copy and strfreev later local = D_FILENAME_TO_LOCALE (*tmp); g_free (*tmp); *tmp = local; tmp++; } } #endif #ifdef E2_NEW_COMMAND result = _e2_command_fork (command1, args, cwd, wait, show, run_in_shell); #else result = (wait) ? _e2_command_run_sync (command1, args, cwd, show, run_in_shell): _e2_command_run_async (command1, args, cwd, show, run_in_shell); #endif // if (free) g_free (command1); g_strfreev (args); #ifdef E2_NEW_COMMAND return result; //((result == 0) ? 0 : 1); //exit code, 0 = ok #else if (wait) return result; //exit code, 0 = ok else return ((result == 0) ? 1 : 0); //pid, >0 = ok #endif cleanup: g_free (command1); return result; } /** @brief action to send a string to last-started child process, if any Various checks are performed, to decide whether it's ok to send the command. The string may be empty. @param from the entry which has the string to send @param art action runtime data @return TRUE if action completed successfully, else FALSE */ static gboolean _e2_command_send_to_lastchild (gpointer from, E2_ActionRuntime *art) { gchar *msg; //get the command string const gchar *command_raw = gtk_entry_get_text (GTK_ENTRY (from)); //quick exit if (command_raw == NULL) //cannot happen ? return FALSE; // if (strchr (command_raw, ';') != NULL) // goto error; if (e2_task_find_last_running_child (FALSE) == NULL) goto error; gchar *command = g_strdup (command_raw); //assume that an action string is not really meant to send to running command if (e2_action_check (command) != NULL) { g_free (command); goto error; } // tochild = TRUE; //this is an action, can only return T/F, so we lose the result of the command _e2_command_run_single (command, NULL, E2_COMMAND_RANGE_DEFAULT | E2_COMMAND_RANGE_TOCHILD); // tochild = FALSE; //we don't want any history, just clear the text gtk_entry_set_text (GTK_ENTRY (from), ""); g_free (command); return TRUE; error: msg = g_strdup_printf (_("Cannot send \"%s\" to a child process"), command_raw); e2_output_print_error (msg, TRUE); return FALSE; } /****************/ /**** public ****/ /****************/ /** @brief run command @a command after interpreting its contents, and with CWD @a workdir @a command may be a single command or a series joined by ';' (any ';' inside single- or double-parentheses is ignored) Normally, commands are run with CWD set to curr_view->dir. Here, we borrow that string temporarily The change of CWD will probably not persist for the duration of command execution if the command is asynchronous @param command utf8 string containing the command(s) @param workdir utf8 path string, maybe with trailer, to use as (native) CWD when running @a command, NULL for curr_view->dir @param range flags for the scope of the command (may be over-ridden, for actions) @return integer return code from last command executed, 0 = success, > 0 on error */ gint e2_command_run_at (gchar *command, const gchar *workdir, E2_CommandRange range) { //E2_VFSTMP workdir probably needs to become a VPATH* //CHECKME should these variables be volatilized for threaded usage ? if ((command == NULL) || (*command == '\0')) return 1; g_strchug (command); //command arg may be a path with intended trailing whitespace if (*command == '\0') return 1; printd (DEBUG, "e2_command_run_at (command:%s,range:%d)", command, range); //FIXME umount commands sometimes should get eject appended - but what about aliases ? //parse & handle joined commands here //in particular, ignore separators inside parentheses if (strchr (command, ';') == NULL) //if always ascii ;, don't need g_utf8_strchr() //no joined commands return _e2_command_run_single (command, workdir, range); //don't split joined commands if ">>" modifier-prefix is provided gchar *s = command; gboolean run_in_shell = FALSE, done = FALSE; while (*s != '\0') { switch (*s++) { case ' ': case '\t': case '!': case '|': break; case '>': if (*s == '>') run_in_shell = TRUE; default: done = TRUE; break; } if (done) break; } if (run_in_shell) return _e2_command_run_single (command, workdir, range); gint result = 0; gchar *p; s = command; while ((p = e2_utils_bare_strchr (s, ';')) != NULL) { //process this command *p = '\0'; result = _e2_command_run_single (s, workdir, range); s = p; s++; //resume from the next spot if (*s == '\0') //the ';' was at end of line return result; } //process the last (or only) command return (_e2_command_run_single (s, workdir, range)); } /* * NOW a #define @brief run command @a command, after interpreting its contents @a command may be a single command or a series joined by ';' (any ';' inside single- or double-parentheses is ignored) NOTE used to return command's pid @param command utf string containing the command(s) @param range flags for the scope of the command (may be over-ridden, for actions) @return integer return code from last command executed, 0 = success, > 0 on error */ /*gint e2_command_run (gchar *command, E2_CommandRange range) { return (e2_command_run_at (command, NULL, range)); } */ /** @brief display commands help document The document name is hard-coded with the help-file name defined at build-time @return */ void e2_command_output_help (void) { //check whether there's still an alias for the help command //(and BTW get a copy of the string, which needs to be freeable for e2_command_run) gchar *help_cmd = _("help"); gchar *command = e2_alias_apply (help_cmd, &app.aliases); if (g_str_equal (help_cmd, command)) { //we can't find what the user has set for a help command //so use the default instead g_free (command); command = g_strconcat(_A(5),".",_A(103)," ", e2_option_str_get ("usage-help-doc"), " [commands]",NULL); //file.view_at _I( helpfile titles not (yet?) translated } #ifdef E2_COMMANDQ e2_command_run (command, E2_COMMAND_RANGE_DEFAULT, FALSE); #else e2_command_run (command, E2_COMMAND_RANGE_DEFAULT); #endif g_free (command); } /** @brief register command-related actions @return */ void e2_command_actions_register (void) { //not action stuff, but this is a convenient spot for other init //#ifndef E2_NEW_COMMAND _e2_command_set_sigchild_handler (); //#endif //#ifdef E2_NEW_COMMAND // _e2_command_set_sigpoll_handler (); //#endif struct passwd *pw; pw = getpwuid (getuid ()); if (pw != NULL) { gchar *baseshell = g_path_get_basename (pw->pw_shell); shellcmd = g_find_program_in_path (baseshell); g_free (baseshell); if (shellcmd == NULL) shellcmd = "/bin/sh"; } else shellcmd = "/bin/sh"; //setup waitpid() parameter, with check for glibc/kernel disconnect on WCONTINUED waitflags = WNOHANG #ifdef WCONTINUED | WCONTINUED #endif | WUNTRACED; #ifdef WCONTINUED if (waitpid (-1, NULL, waitflags) < 0 && errno == EINVAL) { printd (DEBUG, "this kernel does not support WCONTINUED"); waitflags = WNOHANG | WUNTRACED; } #endif #ifdef __linux__ # ifdef __WALL // waitflags = waitflags | __WALL | __WNOTHREAD; causes failure waitflags |= __WALL; # endif #endif gchar *action_name = g_strconcat(_A(7),".",_A(28),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_command_list_children, NULL, FALSE); action_name = g_strconcat(_A(7),".",_A(51),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_command_list_history, NULL, FALSE); action_name = g_strconcat(_A(7),".",_A(66),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_command_list_pending, NULL, FALSE); action_name = g_strconcat(_A(29),".",_A(66),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, e2_command_clear_pending, NULL, FALSE); action_name = g_strconcat(_A(1),".",_A(77),NULL); e2_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2_command_send_to_lastchild, NULL, FALSE, E2_ACTION_EXCLUDE_TOOLBAR | E2_ACTION_EXCLUDE_MENU, NULL); } /** @brief register command-related options @return */ void e2_command_options_register (void) { //no screen rebuilds needed after any change to these options gchar *group_name = g_strconcat(_C(6),":",_C(25),NULL); //_("commands:misc" e2_option_str_register ("command-xterm", group_name, _("x terminal emulator:"), _("This is the external command/application that will be be run when emelFM2 is asked to open a terminal"), NULL, "xterm", E2_OPTION_FLAG_BASIC | E2_OPTION_FLAG_FREEGROUP); e2_option_bool_register ("use-external-viewer", group_name, _("use external file-viewer"), _("If activated, the command entered below will be run to view file content, instead of launching the internal viewer"), NULL, FALSE, E2_OPTION_FLAG_BASIC); e2_option_str_register ("command-viewer", group_name, _("viewer command:"), _("This is a command to run an external application for viewing file content.\n" "The first selected item will be supplied as the first argument"), //CHECKME = this is wrong ?? "use-external-viewer", "gview", E2_OPTION_FLAG_BASIC); e2_option_bool_register ("use-external-editor", group_name, _("use external file-editor"), _("If activated, the command entered below will be run to edit file content, instead of launching the internal editor"), NULL, FALSE, E2_OPTION_FLAG_BASIC); e2_option_str_register ("command-editor", group_name, _("editor command:"), _("This is a command to run an external application for editing file content.\n" "The first selected item will be supplied as the first argument"), //CHECKME = this is wrong ?? "use-external-editor", "leafpad", E2_OPTION_FLAG_BASIC); e2_option_bool_register ("use-external-encoder", group_name, _("use external encoding converter"), _("If activated, the command entered below will be run, instead of using the internal conversion functions, to convert file character encoding when needed"), NULL, FALSE, E2_OPTION_FLAG_ADVANCED); gchar *defcmd = g_strconcat ("enca -L %{(languages)@", _("File encoding:"),"} -x UTF-8 <%p", NULL); e2_option_str_register ("command-encoder", group_name, _("converter command:"), _("A command which runs an external application to convert text encoding to UTF-8"), "use-external-encoder", defcmd, E2_OPTION_FLAG_ADVANCED); g_free (defcmd); e2_option_bool_register ("command-use-aliases", group_name, _("use aliases"), _("This is a general switch to turn on/off alias handling for commands"), NULL, TRUE, E2_OPTION_FLAG_ADVANCED); e2_option_bool_register ("transparent-dir-links", group_name, _("interpret 'relative' paths"), _("This enables correct interpretation of paths containing '..' etc. These might be typed in, or attached to a button, say"), NULL, TRUE, E2_OPTION_FLAG_ADVANCED); e2_option_bool_register ("command-persist", group_name, _("running commands survive shutdown"), _("If activated, commands that are still running will not be terminated at end of emelFM2 session"), NULL, TRUE, E2_OPTION_FLAG_ADVANCED); #ifndef E2_NEW_COMMAND e2_option_int_register ("command-watch-priority", group_name, _("watch priority"), _("The watch priority of commands influences how fast program\n" "output is read from i/o channels. A too-high priority might\n" "decrease gui responsiveness and break automatic scrolling.\n" "(note: negative values mean high priority, positive values mean low priority)"), NULL, 300, -200, 400, E2_OPTION_FLAG_ADVANCED); #endif //these options relate to actions, not commands group_name = g_strconcat(_C(6),":",_C(14),NULL); //_("commands:file operations" e2_option_bool_register ("task-timeout-checks", group_name, _("stop after timeout"), _("If activated, each file operation will be terminated if not finished within the time-interval set below"), NULL, FALSE, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP); e2_option_int_register ("task-timeout-interval", group_name, _("timeout interval"), _("The interval (seconds) allowed to complete any file operation"), "task-timeout-checks", 20, 1, 3600, E2_OPTION_FLAG_ADVANCED); e2_option_bool_register ("confirm-delete", group_name, _("confirm any delete"), _("If activated, you will be asked for confirmation before actually deleting anything"), NULL, TRUE, E2_OPTION_FLAG_BASIC); e2_option_bool_register ("confirm-overwrite", group_name, _("confirm any overwrite"), _("If activated, you will be asked for confirmation before actually overwriting anything"), NULL, TRUE, E2_OPTION_FLAG_BASIC); e2_option_bool_register ("relative-symlinks", group_name, _("relative symlinks"), _("This gives each created symlink a relative path to its source, like '../..//', instead of a full path referenced to /"), NULL, TRUE, E2_OPTION_FLAG_ADVANCED); } emelfm2-0.4.1/src/e2_plugins.h0000600000175000017500000001031411010340377015011 0ustar cairocairo/* $Id: e2_plugins.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_PLUGINS_H__ #define __E2_PLUGINS_H__ #include "emelfm2.h" /*values for lookup table of 'main program' functions, usable by plugins NOTE: not all actually-used main-program functions are accessible via this API, so that plugins need to be compiled with the main program, ans this interface is fairly nominal, more for example than core use ! */ /*enum { E2API_ACTION_REGISTER = 0, E2API_ACTION_UNREGISTER, E2API_ACTION_GET, E2API_OPTION_GET, E2API_OPTION_REGISTER, E2API_REFRESH_DISABLE, E2API_REFRESH_ENABLE, E2API_POINTER_COUNT //the last value, used for size checks }; */ typedef enum _E2_PlugCleans { E2P_CLEANNONE = 0, //this is default value when Plugin is allocated E2P_CLEANICON = 1, E2P_CLEANLABEL = 1 << 1, E2P_CLEANTIP = 1 << 2, E2P_CLEANCHILD = 1 << 3, } E2_PlugCleans; //for cleaning all strings #define E2P_CLEANALL 0x7 /* To provide for multiple-actions in any plugin, this runtime data struct is used in 2 ways. When there is more than one action, a "parent" Plugin struct contains a list of "child" Plugin structs, one for each action. All Plugins, parents and children, are added to app.plugins as a flat list */ typedef struct _E2_Plugin { // gchar *name; //action name FOR debugging only const gchar *signature; //ID string, never alters after it's set //this group of members will be NULL in any Plugin listed in action_list GModule *module; gboolean (*plugin_init)(); //fn called by main program, to let the plugin initialize itself GList *child_list; //list of Plugins, each with action-specific data, //or NULL if only 1 action, and then the following will be used ... gchar *icon; //user-editable context-menu icon file path (or "gtk-<...>") gchar *menu_name; //user-editable name that appears in the plugins menu gchar *description; //user-editable context-menu tooltip (useless for a sub-menu item) gboolean show_in_menu; //TRUE if the plugin is to appear in the file-pane context menu E2_Action *action; //what to run E2_PlugCleans cleanflags; //what things to handle when cleaning up } Plugin; typedef struct _E2P_DirEnt { gchar *path; mode_t mode; } E2P_DirEnt; // Plugins //void *e2_plugins_api_lookup (gint type); Plugin *e2_plugins_open1 (gchar *filepath); gboolean e2_plugins_unload1 (Plugin *p, gboolean force); void e2_plugins_load_all (void); void e2_plugins_unload_all (gboolean force); void e2_plugins_store_child_data (GtkTreeModel *model, GtkTreeIter *iter, Plugin *p); void e2_plugins_update (void); gboolean e2_plugins_configure (gpointer from, E2_ActionRuntime *art); E2_Action *e2_plugins_action_register (gchar *name, E2_ACTION_TYPE type, void *func, gpointer data, gboolean has_arg, E2_ACTION_EXCLUDE exclude, gpointer data2); gboolean e2_plugins_action_unregister (gchar *name); void e2_plugins_do_action (GtkWidget *widget, E2_Action *action); //Plugin *get_plugin_by_name (gchar *name); Plugin *e2_plugins_check_installed (const gchar *signature); Plugin *e2_plugins_load_plugin (const gchar *signature); gboolean e2_plugins_find_function (const gchar *signature, const gchar *func_name, gpointer *address); Plugin *e2_plugins_create_child (Plugin *parent); GList *e2_plugins_get_list (void); E2_OptionSet *e2_plugins_option_register (E2_OptionType type, gchar *name, gchar *group, gchar *desc, gchar *tip, gchar *depends, E2_OptionFlags flags); gboolean e2_plugins_option_unregister (gchar *name); void e2_plugins_options_register (void); #endif //ndef __E2_PLUGINS_H__ emelfm2-0.4.1/src/e2_bookmark.h0000600000175000017500000000231311010340377015135 0ustar cairocairo/* $Id: e2_bookmark.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelfm2. emelfm2 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 3, or (at your option) any later version. emelfm2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_BOOKMARK_H__ #define __E2_BOOKMARK_H__ #include "emelfm2.h" gboolean e2_bookmark_open (gpointer from, E2_ActionRuntime *art); void e2_bookmark_add_cb (GtkWidget *widget, gchar *arg); gboolean e2_bookmark_click_cb (GtkWidget *widget, GdkEventButton *event, GtkTreePath* path); //void e2_bookmark_clean (); void e2_bookmark_actions_register (); void e2_bookmark_options_register (); #endif //ndef __E2_BOOKMARK_H__ emelfm2-0.4.1/src/e2_output.h0000600000175000017500000001023711010340377014674 0ustar cairocairo/* $Id: e2_output.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_OUTPUT_H__ #define __E2_OUTPUT_H__ #include "e2_option.h" typedef enum { E2_OUTPUT_NEWLINE = 1<<1, E2_OUTPUT_PREPEND_NEWLINE = 1<<2, E2_OUTPUT_STYLE_ERROR = 1<<3, E2_OUTPUT_STYLE_LINK = 1<<4, E2_OUTPUT_STYLE_IMPORTANT = 1<<5, E2_OUTPUT_STYLE_UNIMPORTANT = 1<<6, E2_OUTPUT_DEBUG = 1<<7, E2_OUTPUT_LINE_FEED = 1<<8, } E2_OUTPUT_PRINT_FLAGS; //these things are tab-specific, swapped in and out typedef struct _E2_OutputTabRuntime { GtkWidget *scroll; //scrolled window containing the textview GtkTextView *text; GtkTextBuffer *buffer; GtkTextMark *mark; //scroll helper mark in the text buffer gboolean onscreen; //whether scroll mark is presently visible in the textview // GHashTable *origins; //UNUSED table of textbuffer contexts & data //WARNING this must be protected by print_mutex, against threaded misuse gchar *origin_lastime; //origin used for the previous message in this tab #ifdef USE_GTK2_10 guint labelnum; //integer form of tab label, for getting new labels #endif #ifdef E2_TABS_DETACH GtkWidget *dropwindow; gboolean detached; //TRUE when the tab has been dropped to a separate window #endif E2_OptionSet *opt_wrap; } E2_OutputTabRuntime; typedef struct _E2_OutputRuntime { E2_OptionSet *opt_show_on_new; E2_OptionSet *opt_show_on_focus_in; E2_OptionSet *opt_hide_on_focus_out; E2_OptionSet *opt_jump; //option for scrolling to new output E2_OptionSet *opt_jump_follow; //option for scrolling to new output when not manually scrolled away E2_OptionSet *opt_jump_end; //option for scrolling only when new output is into the last context in the textbuffer gboolean visible; //whether output pane is visible gboolean showhide_blocked; gint font_size; //can be points or pixels } E2_OutputRuntime; /* UNUSED typedef struct _E2_OutputOrigin { gchar *name; GtkTextMark *mark_start; GtkTextMark *mark_insert_start; GtkTextMark *mark_insert_end; GtkTextMark *mark_end; GString *str; glong num_in_str; glong num_in_buffer; } E2_OutputOrigin; */ #define E2_ERRORTAGS "red", "bold" #define TABDEFINE E2_OutputTabRuntime *_e2t1_; #define TABLOGCURRENT E2_OutputTabRuntime *_e2t1_ = curr_tab; #define TABLOG(tab) E2_OutputTabRuntime *_e2t1_ = tab; #define e2_output_print_same(msg) \ e2_output_print ((_e2t1_ == curr_tab) ? &app.tab : _e2t1_, msg, NULL, FALSE, NULL); //#define e2_output_print_default(msg) e2_output_print (&app.tab, msg, NULL, FALSE, NULL); //#define status_messagef(msg, first_tag, args...) e2_output_print (msg, NULL, FALSE, first_tag, ## args); void e2_output_print_end (E2_OutputTabRuntime *tab, gboolean beep); void e2_output_print_error (gchar *msg, gboolean freemsg); void e2_output_print_strerrno (void); void e2_output_print (E2_OutputTabRuntime *tab, gchar *msg, gchar *origin, //gboolean error, gboolean debug, gboolean newline, const gchar *first_tag, ...); void e2_output_print2 (E2_OutputTabRuntime *tab, const gchar *msg, const gchar *origin, E2_OUTPUT_PRINT_FLAGS flags, const gchar *first_tag, ...); void e2_output_get_bottom_iter (E2_OutputTabRuntime *rt); void e2_output_scroll_to_bottom (E2_OutputTabRuntime *rt); void e2_output_update_style (void); GtkWidget *e2_output_initialise (void); void e2_output_set_menu_position (GtkWidget *menu, gint *x, gint *y, gboolean *push_in, GtkWidget *textview); void e2_output_actions_register (void); void e2_output_options_register (void); #endif //ndef __E2_OUTPUT_H__ emelfm2-0.4.1/src/e2_plugins.c0000600000175000017500000012752110667421451015027 0ustar cairocairo/* $Id: e2_plugins.c 650 2007-09-05 03:31:53Z tpgww $ Copyright (C) 2003-2007 tooar Portions copyright (C) 1999 Michael Clark. This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/e2_plugins.c @brief plugins-related functions This file contains infrastructure for running plugins, but no actual plugins. */ /** \page plugin_writing creating plugins Here's an outline of what a plugin file must contain.\n\n 1. A suitable header. To enable versioning by subversion, the first header line \b must have just $ Id $ (without the spaces between the $'s, they're presented here just to prevent subversion from putting version info into this text). You should also provide a copyright-assertion statement, if not for yourself, then for tooar. Finally, a licence statement. EmelFM2 is licensed under the GPL. Get advice if you need to use something else.\n\n 2. Includes, to access required external functions and data. The first of these is mandatory. \code #include "e2_plugins.h" #include "e2_other-needed-stuff.h" #include \endcode \n 3. The function that performs what you want the plugin to do when activated. This type of function \b must take two parameters: - a pointer to the button, menu item etc which was activated to initiate the action - a pointer to an E2_ActionRuntime data struct which will provide any action argument etc and \b must return a \c gboolean indicating whether the action succeeded. The function need not be static if it's to be used from outside the plugin.\n For example, if you want a plugin that prints "Hello World", then you would write this function. \code static gboolean _e2p_hello_world (gpointer from, E2_ActionRuntime *art) { e2_output_print (&app.tab, _("Hello World"), NULL, TRUE, NULL); return TRUE; //or FALSE if the action was not completed successfully } \endcode \n 4. An initialisation function like the following. This function \b must be called \c init_plugin, take a Plugin * argument, and return a \c gboolean which indicates whether the initialisation succeeded. \code //aname must be confined to this module static gchar *aname; //a translated component of the action-name for the plugin #define ANAME "HelloWorld" //part of the registered action name, no spaces, not translated gboolean init_plugin (Plugin *p) { aname = _("demonstration"); p->signature = ANAME VERSION; //for detecting whether the plugin is loaded p->menu_name = _("_Hello World"); //the name for the plugins menu item, capitalize according to HIG is best p->description = _("prints \"Hello World\" on the output window"); //the tooltip for the plugins menu item p->icon = "plugin_"ANAME"_48.png"; //a non-standard path may be prepended. Just put "" for no icon //sometimes we load the plugin to get the data above, but don't want to use it ... if (p->action == NULL) { //No need to free this string, that's done as part of the registration / *If the action is to apply to active-pane selected items, the first part of the name must be _A(5) which is (in english) "file", in order to make the plugins context-menu work properly. And the converse, do not use _A(5) unless that condition applies * / gchar *action_name = g_strconcat (_A(13),".",aname,NULL); p->action = e2_plugins_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2p_hello_world, NULL, FALSE, 0, NULL); //other initialization stuff, as appropriate ... return TRUE; //or FALSE, if the initialisation failed } return FALSE; } \endcode \n 5. A cleanup function like the following. This function \b must be called \c unload_plugin, take a Plugin * argument, and return a \c gboolean which indicates whether the cleanup succeeded. \code static gboolean unload_plugin (Plugin *p) { gchar *action_name = g_strconcat (_A(13),"."aname,NULL); //the same name as was registered gboolean ret = e2_plugins_action_unregister (action_name); g_free (action_name); if (ret) { //other stuff, as appropriate ... } return ret; } \endcode \n Plugins may have more than one action, which requires some extra detail in the \c init_plugin and \c unload_plugin functions. Refer to the cpbar plugin code for an example of this. */ /** \page plugins plugins A specific interface is required to anable plugins to be loaded and unloaded - see \ref plugin_writing In general, plugins use functions and data from core e2, and therefore, such plugins are specific to the version of e2 used for building ?? Plugins may use bits of each other if appropriate (but this has never been tested, current plugins have little need for this). A desired-but-not-loaded plugin will be loaded if its path/name are in config data (the code is not smart enough to find the missing plugin, otherwise). Plugins are not smart enough to auto-unload other(s), if the plugin has previously auto-loaded those other(s). In case there is some need for a plugin that the user is not aware of, loaded plugins are ref-counted, and any user instruction to unload is obeyed only when the count allows. Historically, each plugin could perform only a single action or command, but now, more than one can be provided in any plugin. To minimise disruption, a hacky approach to this has been used. If more than one action is available, a "parent" plugin data struct is created, and that parent has "child" data structs each with one action. The children are listed in the parent's data struct. As of now, only the cpbar plugin uses this. Refer to its code for examples of tailored initialisation and unloading. Plugins don't have to be user-action-oriented. For example, new-version automatic upgrades of config data are performed using a plugin that is discarded immediately after use. Plugins' configuration data are loaded into the corresponding config treestore at session-start, and that store is incrementally updated after any plugin change via a config dialog. In addition, some plugin data are stored in a list (app.plugins). That's partly a relic from emelFM1, but it enables storage of data (plugin signature and action name) not stored in the config treestore (all data there are user-editable via a config dialog, we don't want the signature or action altered, it'd be better to fix the config treeview ...) The list is also used for plugins-menu creation (but that could readily be converted to treestore data if all relevant data were there). */ #include "e2_plugins.h" #include #include "e2_action.h" #include "e2_output.h" #include "e2_dialog.h" #include "e2_filelist.h" typedef struct _E2P_Ref { gchar *name; guint refcount; } E2P_Ref; static GList *action_refs = NULL; static GList *option_refs = NULL; /** @brief find member of @a list which matches @a name @param list list of E2P_Ref's to scan @param name name of option or action to match @return the matching list member, or NULL if no match */ static GList *_e2_plugins_find_ref (GList *list, gchar *name) { GList *member; for (member = list; member != NULL; member = member->next) { E2P_Ref *data = member->data; if (g_str_equal (data->name, name)) break; } return member; } /** @brief increase refcount for action/item named @a name A list member is added when needed @param list address of list of E2P_Ref's to scan @param name name of option or action to ref @return the new refcount, or 0 in case of error */ static guint _e2_plugins_ref (GList **list, gchar *name) { guint newrefcount; E2P_Ref *data; GList *member = _e2_plugins_find_ref (*list, name); if (member == NULL) { data = MALLOCATE (E2P_Ref); //too small for slice CHECKALLOCATEDWARN (data, return 0;) if (data != NULL) { *list = g_list_append (*list, data); data->name = g_strdup (name); data->refcount = 1; return 1; } return 0; } else { data = (E2P_Ref *)member->data; newrefcount = ++(data->refcount); return newrefcount; } } /** @brief decrease refcount for action/item named @a name The list member is cleared when the new count reaches 0 @param list address of list of E2P_Ref's to scan @param name name of option or action to ref @return the new refcount, or -1 in case of error */ static gint _e2_plugins_unref (GList **list, gchar *name) { guint newrefcount; E2P_Ref *data; GList *member = _e2_plugins_find_ref (*list, name); if (member == NULL) return -1; else { data = (E2P_Ref *)member->data; newrefcount = data->refcount - 1; if (newrefcount == 0) { g_free (data->name); DEMALLOCATE (E2P_Ref, data); *list = g_list_delete_link (*list, member); } else data->refcount = newrefcount; return (gint)newrefcount; } } /* * @brief get function address This enables a plugin to get the address of a main-program function that the plugin wishes to use, which allows plugins to be version-independent BUT coverage is too limited @param type integer defining which function to get @return ptr to function sought, or NULL if not found */ /*void *e2_plugins_api_lookup (gint type) { if (type >= E2API_POINTER_COUNT) return NULL; //these are in the same order as the enerator //FIXME add other relevant fns gpointer interface [E2API_POINTER_COUNT] = { e2_action_register_simple, e2_action_unregister, e2_action_get, e2_option_get, e2_option_register, e2_filelist_disable_refresh, e2_filelist_enable_refresh }; return interface [type]; } */ /** @brief obliterate specified "non-child" plugin The plugin's action is unregistered, any 'unload' function in the plugin is called, then the module is dumped and memory freed Any child plugins are cleared, also from app.plugins Adjusts app.plugins, so is not very suited to use during a walk of that list Any related config treestore data are not affected - any update of those must be done before coming here Expects BGL on/closed @param p plugin data struct @param force TRUE to force removal even if not cleaned up by the plugin @return TRUE if the plugin was unloaded */ gboolean e2_plugins_unload1 (Plugin *p, gboolean force) { printd (DEBUG, "unload plugin: %s", p->menu_name); gboolean (*clean)(Plugin *); if (p->module != NULL) //this is not a child plugin { //FIXME handle plugins that are not allowed to be unloaded if ((g_module_symbol (p->module, "clean_plugin", (gpointer) &clean) && clean (p)) || force) { g_module_close (p->module); GList *member; Plugin *pc; for (member = p->child_list; member != NULL; member = member->next) { pc = member->data; if (pc->cleanflags & E2P_CLEANICON) g_free (pc->icon); if (pc->cleanflags & E2P_CLEANLABEL) g_free (pc->menu_name); if (pc->cleanflags & E2P_CLEANTIP) g_free (pc->description); DEALLOCATE (Plugin, pc); app.plugins = g_list_remove (app.plugins, pc); } if (p->child_list != NULL) g_list_free (p->child_list); if (p->cleanflags & E2P_CLEANICON) g_free (p->icon); if (p->cleanflags & E2P_CLEANLABEL) g_free (p->menu_name); if (p->cleanflags & E2P_CLEANTIP) g_free (p->description); DEALLOCATE (Plugin, p); return TRUE; } gchar *msg = g_strdup_printf ("%s \"%s\"", _("Cannot unload plugin"), p->menu_name); e2_output_print_error (msg, TRUE); } return FALSE; } /** @brief unload all plugins To the extent possible, each plugin listed in app.plugins is removed, and the list itself is cleared. Related config treestore data for plugins are not affected. @param force TRUE to force removal even if not cleaned up by the plugin @return */ void e2_plugins_unload_all (gboolean force) { gboolean (*clean)(Plugin *); Plugin *p; GList *member; for (member = app.plugins; member != NULL; member = member->next) { //FIXME handle plugins that are not allowed to be unloaded p = (Plugin *)member->data; if (p != NULL //not a child plugin already cleared && p->module != NULL) //not a child plugin to be cleared { GList *childp, *mainmember; Plugin *pc; if ((g_module_symbol (p->module, "clean_plugin", (gpointer) &clean) && clean (p)) || force) { g_module_close (p->module); for (childp = p->child_list; childp != NULL; childp = childp->next) { pc = childp->data; if (pc->cleanflags & E2P_CLEANICON) g_free (pc->icon); if (pc->cleanflags & E2P_CLEANLABEL) g_free (pc->menu_name); if (pc->cleanflags & E2P_CLEANTIP) g_free (pc->description); mainmember = g_list_find (app.plugins, pc); if (mainmember != NULL) mainmember->data = NULL; //avoid double-cleans DEALLOCATE (Plugin, pc); //FIXME leaks strings } if (p->child_list != NULL) g_list_free (p->child_list); if (p->cleanflags & E2P_CLEANICON) g_free (p->icon); if (p->cleanflags & E2P_CLEANLABEL) g_free (p->menu_name); if (p->cleanflags & E2P_CLEANTIP) g_free (p->description); DEALLOCATE (Plugin, p); member->data = NULL; } } } app.plugins = g_list_remove_all (app.plugins, NULL); if (app.plugins != NULL) printd (WARN, "Some plugin(s) data not cleared"); } /* * @brief cleanup all plugins data @return */ /*uncomment this if proper session-end cleanups are implemented void e2_plugins_clean (void) { GList *member; for (member = app.plugins; member != NULL; member = member->next) { if (member->data != NULL) { //FIXME some strings in plugin data struct may sometimes be allocated DEALLOCATE (Plugin, member->data); } } } */ /** @brief open a specified plugin, if possible @param filepath path+name string for the plugin, probably localised (API doc is silent) @return plugin data struct for the loaded plugin, or NULL if plugin not found or has no initialize fn */ Plugin *e2_plugins_open1 (gchar *filepath) { printd (DEBUG, "e2_plugins_open1: %s", filepath); gboolean (*init)(Plugin *); GModule *module = g_module_open (filepath, 0); if (module == NULL) { printd (DEBUG, "failed to open plugin file: %s", g_module_error()); return NULL; } if (!g_module_symbol (module, "init_plugin", (gpointer)&init)) { printd (DEBUG, "no initialise-function in plugin file: %s", g_module_error()); return NULL; } Plugin *p = ALLOCATE0 (Plugin); CHECKALLOCATEDWARN (p, return NULL;) p->module = module; p->plugin_init = init; return p; } /** @brief process child UI data into config store and cleanup This does not affect app.plugins @param model model for plugins config treestore @param iter pointer to parent-iter in @a model @param p pointer to data struct for parent plugin @return */ void e2_plugins_store_child_data (GtkTreeModel *model, GtkTreeIter *iter, Plugin *p) { if (p->child_list != NULL) { GtkTreeIter iter2; GList *member; for (member = p->child_list; member != NULL; member = member->next) { E2_Sextet *uidata = (E2_Sextet *)member->data; gtk_tree_store_insert_before (GTK_TREE_STORE (model), &iter2, iter, NULL); //loaded flag is irrelevant for child plugins, set to match on_menu gtk_tree_store_set (GTK_TREE_STORE (model), &iter2, 0, p->show_in_menu, 1, p->show_in_menu, 2, uidata->a, 3, uidata->b, 4, uidata->c, 5, "", 6, uidata->d, -1); e2_utils_sextet_destroy (uidata); } g_list_free (p->child_list); //finished interim usage p->child_list = NULL; } } /** @brief check if plugin data @a child has signature @a signature @param child pointer to child plugin data struct @param signature string to be found in @a child @return 0 if @a child has signature @a signature */ static gint _e2_plugins_match_child (Plugin *child, gchar *signature) { return (!g_str_equal (child->signature, signature)); } /** @brief process "child plugin" data when a plugin has > 1 action As needed, this updates the config treestore data, and appends "child" plugins to app.plugins It doesn't matter whether or not the store already has child iters for @a iter If there are child iters, they are checked for validity and cleaned If there are no child iters, the data in @a p 's child-list are used to populate the store If there are no children for the plugin, any child store iter is removed @param model treemodel for the plugins config data @param iter ptr to iter referring to model/store row for parent plugin being processed @param p ptr to parent-plugin data corresponding to @a iter @param loaded TRUE if @a p represents a plugin that is being loaded, FALSE if just logging the data @return */ static void _e2_plugins_record_children (GtkTreeModel *model, GtkTreeIter *iter, Plugin *p) { GtkTreeIter iter2; GList *member; Plugin *pc; //check for multiple-commands in the plugin if (gtk_tree_model_iter_children (model, &iter2, iter)) { //there are child row(s) in the config treestore (whether or not still relevant) if (p->child_list != NULL) //children recorded (in plugin setup) { //multiple actions in the plugin, described by a list of Plugins /*reconcile model data with list ... (the number may be < = >, any may be bogus after user editing, and the order may be different) principle is - treestore data ORDER prevails over plugin init data in p->child_list */ GList *reordered = NULL; //transfer matches from child_list to here do { gchar *childsig; gtk_tree_model_get (model, &iter2, 6, &childsig, -1); //from original p->signature //plugin init creates p->child_list in same order as child-signature-index //that index is a string of the form "n-ANAME" where n= 0,1, ... //but in the config store, the lines can be in any order member = g_list_find_custom (p->child_list, childsig, (GCompareFunc)_e2_plugins_match_child); if (member != NULL) //found a child plugin matching the store iter (member->data = NULL handled in search func)) { gboolean childon; gtk_tree_model_get (model, iter, 1, &childon, -1); if (childon) //parent is on-menu gtk_tree_model_get (model, &iter2, 1, &childon, -1); //keep original value else p->show_in_menu = FALSE; pc = (Plugin *)member->data; gtk_tree_store_set (GTK_TREE_STORE (model), &iter2, 0, TRUE, //for cosmetic reasons only, set this flag same as for parent 1, childon, 5, "", //clear any default filename string //this repetition not really needed, but just in case the user edited the sig ... 6, pc->signature, //formatted as n-ANAME, n=0,1,.... -1); pc->show_in_menu = childon; if (pc->cleanflags & E2P_CLEANICON) g_free (pc->icon); if (pc->cleanflags & E2P_CLEANLABEL) g_free (pc->menu_name); if (pc->cleanflags & E2P_CLEANTIP) g_free (pc->description); gtk_tree_model_get (model, &iter2, // 1, &pc->show_in_menu, 2, &pc->menu_name, 3, &pc->icon, 4, &pc->description, -1); pc->cleanflags = E2P_CLEANALL; //progressively build replacement list in same order as config data reordered = g_list_append (reordered, pc); } else //no matching child plugin for this row { if (gtk_tree_store_remove (GTK_TREE_STORE (model), &iter2)) //counter the loop-end iter_next e2_tree_iter_previous (model, &iter2); } g_free (childsig); } while (gtk_tree_model_iter_next (model, &iter2)); //now add any additional plugin's data to reordered list and to treestore for (member = p->child_list; member != NULL; member = member->next) { pc = (Plugin *)member->data; if (!g_list_find (reordered, pc)) { //this one not already processed pc->show_in_menu = FALSE; reordered = g_list_append (reordered, pc); #ifdef USE_GTK2_10 gtk_tree_store_insert_with_values (GTK_TREE_STORE (model), &iter2, iter, -1, #else gtk_tree_store_append (GTK_TREE_STORE (model), &iter2, iter); gtk_tree_store_set (GTK_TREE_STORE (model), &iter2, #endif 0, TRUE, //just for config dialog cosmetics 1, FALSE, 2, (pc->menu_name != NULL) ? pc->menu_name : "", 3, (pc->icon != NULL) ? pc->icon : "", 4, (pc->description != NULL) ? pc->description : "", 5, "", //clear the default filename string //instead of path (the parent will cover that) log the matching-signature 6, pc->signature, //formatted as n-ANAME, n=0,1,.... -1); } } g_list_free (p->child_list); //everything now in reordered p->child_list = reordered; //append a copy, so that later plugins will not be appended to //this children list app.plugins = g_list_concat (app.plugins, g_list_copy (reordered)); } else //should be no child iters, clear them { do { gtk_tree_store_remove (GTK_TREE_STORE (model), &iter2); } while (gtk_tree_model_iter_children (model, &iter2, iter)); } } else //no child iter in the config store if (p->child_list != NULL) { //multiple actions in the plugin, but not previously recorded //CHECKME create and set child signature here instead of in each plugin ? for (member = p->child_list; member != NULL; member = member->next) { pc = (Plugin *)member->data; pc->show_in_menu = p->show_in_menu; //match the parent's setting gtk_tree_store_append (GTK_TREE_STORE (model), &iter2, iter); gtk_tree_store_set (GTK_TREE_STORE (model), &iter2, 0, TRUE, 1, pc->show_in_menu, 2, (pc->menu_name != NULL) ? pc->menu_name : "", 3, (pc->icon != NULL) ? pc->icon : "", 4, (pc->description != NULL) ? pc->description : "", 5, "", //clear the default filename string //instead of path (the parent will cover that) provide a matching-string 6, pc->signature, //formatted as n-ANAME, n=0,1,.... -1); } //append children to the main list, in the order defined by the init func app.plugins = g_list_concat (app.plugins, g_list_copy (p->child_list)); } } /** @brief load plugin described in plugins config-data @a model at row for @a iter If the specified plugin file is found (at custom or default path), it is loaded, relevant data is recorded, then the plugin is discarded if it is not wanted. This is to be applied only to top-level iters in @a model. Any children are detected and processed here. @param model treemodel for the plugins config data @param iter ptr to iter referring to model/store row being processed @return */ static void _e2_plugins_load1 (GtkTreeModel *model, GtkTreeIter *iter) { void (*unload)(Plugin *); gboolean load_this, in_menu; gchar *label, *icon, *tip; //these are for checking if currently in config gchar *utf_name, *utf_path, *localdir, *localpath; gtk_tree_model_get (model, iter, 0, &load_this, 1, &in_menu, 2, &label, 3, &icon, 4, &tip, 5, &utf_name, 6, &utf_path, -1); if (*utf_path != '\0') localdir = F_FILENAME_TO_LOCALE (utf_path); else //no path specified, so use the default plugins path localdir = PLUGINS_DIR; //localised #ifdef E2_VFS VPATH data = { localdir, NULL }; //only local dirs for plugins if (e2_fs_is_dir3 (&data E2_ERR_NONE())) //only local places for plugins #else if (e2_fs_is_dir3 (localdir E2_ERR_NONE())) //only local places for plugins #endif { gchar *file_name = F_FILENAME_TO_LOCALE (utf_name); localpath = g_build_filename (localdir, file_name, NULL); F_FREE (file_name); if (load_this || in_menu //this is one we want to keep || *label == '\0') //no data logged CHECKME is this test ok ? { Plugin *p; if ((p = e2_plugins_open1 (localpath)) != NULL) { if (load_this || in_menu) { // p->api_lookup = e2_plugins_api_lookup; p->action = NULL; //ensure "real" init if (p->plugin_init (p)) { //update the UI data if need be p->show_in_menu = in_menu; if (*label == '\0' && p->menu_name != NULL) gtk_tree_store_set (GTK_TREE_STORE (model), iter, 2, p->menu_name, -1); else if (p->menu_name == NULL || !g_str_equal (p->menu_name, label)) { if (p->menu_name != NULL && (p->cleanflags & E2P_CLEANLABEL)) g_free (p->menu_name); p->menu_name = g_strdup (label); p->cleanflags |= E2P_CLEANLABEL; } if (*icon == '\0' && p->icon != NULL) gtk_tree_store_set (GTK_TREE_STORE (model), iter, 3, p->icon, -1); else if (p->icon == NULL || !g_str_equal (p->icon, icon)) { if (p->icon != NULL && (p->cleanflags & E2P_CLEANICON)) g_free (p->icon); p->icon = g_strdup (icon); p->cleanflags |= E2P_CLEANICON; } if (*tip == '\0' && p->description != NULL) gtk_tree_store_set (GTK_TREE_STORE (model), iter, 4, p->description, -1); else if (p->description == NULL || !g_str_equal (p->description, tip)) { if (p->description != NULL && (p->cleanflags & E2P_CLEANTIP)) g_free (p->description); p->description = g_strdup (tip); p->cleanflags |= E2P_CLEANTIP; } app.plugins = g_list_append (app.plugins, p); //parent listed before any children if (p->child_list == NULL) { //no child plugin(s) if (*tip == '\0' && p->description != NULL) gtk_tree_store_set (GTK_TREE_STORE (model), iter, 4, p->description, -1); else if (p->description == NULL || !g_str_equal (p->description, tip)) p->description = g_strdup (tip); //FIXME if there were children before, make sure gone //from store too } else { //when child plugin(s) exist, parent tip won't show in menu //so make submenu item's tip blank gtk_tree_store_set (GTK_TREE_STORE (model), iter, 4, "", -1); //setup children data _e2_plugins_record_children (model, iter, p); } } else //init function failed { printd (WARN, "can't initialize plugin: %s", utf_name); //gboolean QQQQcheckmestore; //CHECKME does this also remove any children from store gtk_tree_store_remove (GTK_TREE_STORE (model), iter); if (g_module_symbol (p->module, "clean_plugin", (gpointer) &unload)) //CHECKME handle case where want to retain ?? unload (p); g_module_close (p->module); DEALLOCATE (Plugin, p); } } else { //this is one we don't want but we do want its data for the config dialog p->action = GINT_TO_POINTER (1); //set to non-NULL to just get config data //if ( p->plugin_init (p); //) //{ if (p->menu_name != NULL) gtk_tree_store_set (GTK_TREE_STORE (model), iter, 2, p->menu_name, -1); if (*icon == '\0' && p->icon != NULL) gtk_tree_store_set (GTK_TREE_STORE (model), iter, 3, p->icon, -1); if (*tip == '\0') { const gchar *faketip = (p->description != NULL) ? p->description : _("Plugin not loaded"); //some extra user info gtk_tree_store_set (GTK_TREE_STORE (model), iter, 4, faketip, -1); } //get store and list data for children, if any p->show_in_menu = FALSE; //set flags in store e2_plugins_store_child_data (model, iter, p); //junk it again, now, if not otherwise wanted //e2_plugins_unload1 (p, FALSE); g_module_close (p->module); DEALLOCATE (Plugin, p); /* } else { //junk unitialised module g_module_close (p->module); DEALLOCATE (Plugin, p); } */ } } else //plugin file not loaded { printd (WARN, "plugin doesn't exist: %s", utf_name); gtk_tree_store_remove (GTK_TREE_STORE (model), iter); } } g_free (localpath); } else //no valid dir for the plugin { printd (WARN, "plugin dir doesn't exist: %s", utf_path); gtk_tree_store_remove (GTK_TREE_STORE (model), iter); } if (*utf_path != '\0') F_FREE (localdir); g_free (label); g_free (icon); g_free (tip); g_free (utf_name); g_free (utf_path); } /** @brief load all plugins that are recorded in plugins config data Plugins that are marked for loading are registered and kept. For the rest, we just get missing data to show in the config dialog, then discard @return */ void e2_plugins_load_all (void) { E2_OptionSet *set = e2_option_get_simple ("plugins"); GtkTreeIter iter; if (set != NULL && gtk_tree_model_get_iter_first (set->ex.tree.model, &iter)) { do { _e2_plugins_load1 (set->ex.tree.model, &iter); } while (gtk_tree_model_iter_next (set->ex.tree.model, &iter)); } else printd (WARN, "plugins config doesn't exist"); } /** @brief update loaded plugins This is the 'apply' fn for the plugins dialog, also used in general config dialog It performs a differential load/unload relative to the status quo Status quo is determined by checking menu name, which is in both Plugin and model ( so if a user edits the menu name, the plugin will be improperly, but not fatally, reloaded ) @return */ void e2_plugins_update (void) { gchar *icon, *name, *tip; gboolean load, menu, freestrings; GList *oldlist, *member; Plugin *p; E2_OptionSet *set = e2_option_get_simple ("plugins"); GtkTreeIter iter, iter2; if (gtk_tree_model_get_iter_first (set->ex.tree.model, &iter)) { oldlist = app.plugins; //work with copy in case store row(s) deleted app.plugins = NULL; do { freestrings = TRUE; gtk_tree_model_get (set->ex.tree.model, &iter, 0, &load, 1, &menu, 2, &name, 3, &icon, 4, &tip, -1); if (menu && !load) //fix any mistake by user { gtk_tree_store_set (GTK_TREE_STORE (set->ex.tree.model), &iter, 0, TRUE, -1); load = TRUE; } //check if this one is already in play //FIXME menu_name is not a reliable indicator. Enable the signature to //be stored in treestore, but not displayed so user can't change it //(it's already stored, in column 6, for child plugins, but that's editable) if (name == NULL) member = NULL; else { for (member = oldlist; member != NULL; member = member->next) { if (g_str_equal (name, ((Plugin *)member->data)->menu_name)) break; } } if (load && member != NULL) { //this one is to keep oldlist = g_list_remove_link (oldlist, member); app.plugins = g_list_concat (app.plugins, member); p = (Plugin *)member->data; //update on-menu status in case was edited p->show_in_menu = menu; //update strings in case they were edited if (p->icon != NULL && (p->cleanflags & E2P_CLEANICON)) g_free (p->icon); p->icon = icon; p->cleanflags |= E2P_CLEANICON; if (p->menu_name != NULL && (p->cleanflags & E2P_CLEANLABEL)) g_free (p->menu_name); p->menu_name = name; p->cleanflags |= E2P_CLEANLABEL; if (p->description != NULL && (p->cleanflags & E2P_CLEANTIP)) g_free (p->description); p->description = tip; p->cleanflags |= E2P_CLEANTIP; freestrings = FALSE; //process any child plugins if (p->child_list != NULL) { /*_e2_plugins_record_children() expects the child plugins not to be in app.plugins, and the parent plugin to be at the end of app.plugins. Should not be any child back in app.plugins yet, but in case the user has moved a child badly out of order ... */ GList *childmember; for (childmember = p->child_list; childmember != NULL; childmember = childmember->next) app.plugins = g_list_remove_all (app.plugins, childmember->data); _e2_plugins_record_children (set->ex.tree.model, &iter, p); } } else // !load and/or member == NULL if (member != NULL) //we need to unload this one if we can { //process any child plugins' flags before dumping p = (Plugin *)member->data; if (p->child_list != NULL) { GList *node; for (node = p->child_list; node != NULL; node = node->next) { p = (Plugin *)node->data; iter2 = iter; if (e2_tree_find_iter_from_str_simple (set->ex.tree.model, 6, p->signature, &iter2, FALSE)) gtk_tree_store_set (GTK_TREE_STORE (set->ex.tree.model), &iter2, 0, FALSE, 1, FALSE, -1); } } if (e2_plugins_unload1 (member->data, FALSE)) oldlist = g_list_delete_link (oldlist, member); else //some strange unload problem, revert the status gtk_tree_store_set (GTK_TREE_STORE (set->ex.tree.model), &iter, 0, TRUE,-1); //TOO BAD ABOUT ANY CHILD PLUGINS' FLAGS, HERE } else //member == NULL, load = ? if (load) //we need to load this one { _e2_plugins_load1 (set->ex.tree.model, &iter); } if (freestrings) { g_free (name); g_free (icon); g_free (tip); } } while (gtk_tree_model_iter_next (set->ex.tree.model, &iter)); if (oldlist != NULL) { //process plugins which are loaded but deleted from treestore for (member = oldlist; member != NULL; member = member->next) { e2_plugins_unload1 ((Plugin *)member->data, TRUE); } g_list_free (oldlist); } } else //nothing in the store e2_plugins_unload_all (FALSE); //cleanup any existing runtime data } /** @brief get the list of installed plugins This is intended to be used by plugins themselves, in case they want to interact @return pointer to glist, in which each ->data is a Plugin* for a loaded plugin */ GList *e2_plugins_get_list (void) { return app.plugins; } /** @brief check whether plugin with specified @a signature is loaded This is intended to be used by plugins, if they want to interact @param signature unique name string for the desired plugin @return pointer to data struct for plugin which has the specified ID, else NULL */ Plugin *e2_plugins_check_installed (const gchar *signature) { GList *tmp; Plugin *p; for (tmp = app.plugins; tmp != NULL; tmp = tmp->next) { p = tmp->data; if (g_str_equal (p->signature, signature)) return p; } return NULL; } /** @brief load plugin with specified @a signature, if it's not loaded already @param signature unique ID string for the desired plugin, may be localised @return pointer to data struct for plugin which has the specified ID, else NULL */ Plugin *e2_plugins_load_plugin (const gchar *signature) { Plugin *p = e2_plugins_check_installed (signature); if (p == NULL) { gchar *ppath, *pname, *local, *s; ppath = NULL; s = strstr (signature, VERSION); s = g_strndup (signature, s-signature); pname = g_strconcat ("e2p_", s, ".so", NULL); g_free (s); GtkTreeIter iter; E2_OptionSet *set = e2_option_get ("plugins"); GtkTreeModel *mdl = set->ex.tree.model; gboolean inconfig = FALSE; if (gtk_tree_model_get_iter_first (mdl, &iter)) { if (e2_tree_find_iter_from_str_same (mdl, 5, pname, &iter)) { inconfig = TRUE; gtk_tree_model_get (mdl, &iter, 6, &ppath, -1); if (ppath != NULL && *ppath != '\0') { //plugin is recorded in config data s = ppath; local = F_FILENAME_TO_LOCALE (ppath); ppath = g_build_filename (local, pname, NULL); //create path from config data g_free (s); F_FREE (local); } else if (ppath != NULL) { g_free (ppath); ppath = NULL; } } } if (ppath == NULL) { //try to get from default place ppath = e2_utils_strcat (PLUGINS_DIR G_DIR_SEPARATOR_S, pname); //localised path } p = e2_plugins_open1 (ppath); g_free (ppath); if (p != NULL) { if (!p->plugin_init (p)) { printd (ERROR, "Can't initialize plugin %s", pname); e2_plugins_unload1 (p, FALSE); p = NULL; } if (inconfig) gtk_tree_store_set (GTK_TREE_STORE (set->ex.tree.model), &iter, 0, TRUE, -1); else { //add to config data const gchar *label = (p->menu_name != NULL) ? p->menu_name : ""; const gchar *icon = (p->icon != NULL) ? p->icon : ""; const gchar *tip = (p->description != NULL) ? p->description : ""; gchar *name = F_FILENAME_FROM_LOCALE (pname); #ifdef USE_GTK2_10 gtk_tree_store_insert_with_values (GTK_TREE_STORE (set->ex.tree.model), &iter, NULL, -1, #else gtk_tree_store_insert (GTK_TREE_STORE (set->ex.tree.model), &iter, NULL, -1); gtk_tree_store_set (GTK_TREE_STORE (set->ex.tree.model), &iter, #endif //if path is not already in config, then it must be default 0, TRUE, 1, FALSE, 2, label, 3, icon, 4, tip, 5, name, 6, "", -1); F_FREE (name); } } else printd (ERROR, "Can't find plugin %s", pname); } return p; } /** @brief find address of a function in another plugin This is intended to be used by main code or other plugins which want to interact. If the plugin with the desired function is not presently loaded, any unloaded plugins recorded in config data are polled for a matching signature, and if so, the plugin will be loaded @param signature unique name string for the desired (parent) plugin @param func_name the name of the function to find @param address store for the located address @return TRUE if the address is found */ gboolean e2_plugins_find_function (const gchar *signature, const gchar *func_name, gpointer *address) { Plugin *p = e2_plugins_load_plugin (signature); if (p == NULL) return FALSE; if (!g_module_symbol (p->module, func_name, address)) { printd (WARN, "couldn't find %s in module %s: %s", func_name, signature, g_module_error()); return FALSE; } return TRUE; } /** @brief create "child" Plugin data for use when @a parent has > 1 action @param parent pointer to the parent @return created child plugin, or NULL if error */ Plugin *e2_plugins_create_child (Plugin *parent) { Plugin *p = ALLOCATE0 (Plugin); CHECKALLOCATEDWARN (p, return NULL;) p->show_in_menu = parent->show_in_menu; //may be varied later via config dialog parent->child_list = g_list_append (parent->child_list, p); return p; } /** @brief run a dialog showing plugins config data @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if the dialog was established */ gboolean e2_plugins_configure (gpointer from, E2_ActionRuntime *art) { return ( e2_config_dialog_single ("plugins", e2_plugins_update, TRUE) //internal name, no translation != NULL); } /** @brief register plugin action This is a wrapper for the standard action registration function It also manages a refcount for the action @param name freeable string with possible '.' separating 'parent/ and 'child' parts of the action @param type flags for one of: item (?) submenu, file_actions, custom_command, plugins, separator, tear_off_menu, toggle, command_line, bookmarks, dummy @param func the function to be called when the action is run @param data pointer to data to be supplied to @a func, or NULL @param has_arg boolean flag signalling whether ??WHAT has argument(s) @param exclude flags indicating whether to exclude this action from toolbars, menus &| keybindings @param data2 additional data needed by the action, or NULL @return registered action E2_Action */ E2_Action *e2_plugins_action_register (gchar *name, E2_ACTION_TYPE type, void *func, gpointer data, gboolean has_arg, E2_ACTION_EXCLUDE exclude, gpointer data2) { E2_Action *action; guint refcount = _e2_plugins_ref (&action_refs, name); if (refcount == 1) action = e2_action_register (name, type, func, data, has_arg, exclude, data2); else action = e2_action_get (name); return action; } /** @brief unregister plugin action named @a name This is a wrapper for the standard action deregistration function. It also manages a refcount for the action. @param name action name string @return TRUE if the action was actually unregistered */ gboolean e2_plugins_action_unregister (gchar *name) { guint refcount = _e2_plugins_unref (&action_refs, name); if (refcount == 0) return (e2_action_unregister (name)); else return FALSE; } /** @brief perform a plugin's action This is the callback for all plugins-menu items @param menu_item the activated item @param action plugin-action data struct @return */ void e2_plugins_do_action (GtkWidget *menu_item, E2_Action *action) { //pdata this will generally be NULL, tho' action->data may be relevant gpointer pdata = g_object_get_data (G_OBJECT (menu_item), "e2-action-data"); E2_ActionRuntime *rt = e2_action_pack_runtime (action, pdata, NULL); e2_action_run (menu_item, rt); //completion code ignored e2_action_free_runtime (rt); } /** @brief register a plugin config option This is a wrapper for the standard option registration function It also manages a refcount for the option @param type flag for the type of set that it is @param name name of the option, a constant string @param group group the option belongs to, used in config dialog, a r-t string FREEME @param desc textual description of the option used in config dialog, a r-t _() string FREEME ? @param tip tooltip used when displaying the config dialog, a _() string, or NULL @param depends name of another option this one depends on, or NULL @param flags bitflags determining how the option data will be handled @return the option data struct */ E2_OptionSet *e2_plugins_option_register (E2_OptionType type, gchar *name, gchar *group, gchar *desc, gchar *tip, gchar *depends, E2_OptionFlags flags) { E2_OptionSet *set; guint refcount = _e2_plugins_ref (&option_refs, name); if (refcount == 1) set = e2_option_register (type, name, group, desc, tip, depends, flags); else set = e2_option_get (name); return set; //NOTE caller needs special care for tree-options } /** @brief unregister plugin option named @a name This is a wrapper for the standard option deregistration function It also manages a refcount for the option @param name option name string @return TRUE if the option was actually unregistered */ gboolean e2_plugins_option_unregister (gchar *name) { guint refcount = _e2_plugins_unref (&option_refs, name); if (refcount == 0) return (e2_option_unregister (name)); else return FALSE; } /** @brief install default tree options for plugins This function is called only if the default is missing from the config file This is essentially a list of all the expected plugin filenames the menu name and description at collected from the plugin file the 'path' field at the end is empty if the plugins default dir is to be used @param set pointer to set data @return */ static void _e2_plugins_tree_defaults (E2_OptionSet *set) { e2_option_tree_setup_defaults (set, g_strdup("plugins=<"), //internal name g_strdup ("true|true||||e2p_glob.so|"), g_strdup ("true|true||||e2p_rename_ext.so|"), g_strdup ("true|true||||e2p_for_each.so|"), g_strdup ("true|true||||e2p_pack.so|"), g_strdup ("true|true||||e2p_names_clip.so|"), g_strdup ("false|false||||e2p_sort_by_ext.so|"), g_strdup ("true|true||||e2p_du.so|"), g_strdup ("true|false||||e2p_unpack.so|"), g_strdup ("true|true||||e2p_find.so|"), g_strdup ("false|false||||e2p_track.so|"), g_strdup ("false|false||||e2p_cpbar.so|"), g_strdup ("false|false||||e2p_mvbar.so|"), g_strdup ("false|false||||e2p_times.so|"), g_strdup ("false|false||||e2p_dircmp.so|"), g_strdup ("false|false||||e2p_config.so|"), g_strdup ("false|false||||e2p_clone.so|"), g_strdup ("false|false||||e2p_view.so|"), #ifdef E2_VFS g_strdup ("false|false||||e2p_vfs.so|"), g_strdup ("false|false||||e2p_gvfs.so|"), #endif g_strdup(">"), NULL); //FIXME setup some mapping between filename and signature name to enable //lookup by signature } /** @brief register plugins actions @return */ void e2_plugins_options_register (void) { //no screen rebuilds needed after any change to this option gchar *group_name = _C(33); //_("plugins") E2_OptionSet *set = e2_option_tree_register ("plugins", group_name, group_name, NULL, NULL, NULL, E2_OPTION_TREE_LIST | E2_OPTION_TREE_UP_DOWN | E2_OPTION_TREE_ADD_DEL, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_BUILDPLUGS); e2_option_tree_add_column (set, _("Loaded"), E2_OPTION_TREE_TYPE_BOOL, TRUE, "true", 0, NULL, NULL); e2_option_tree_add_column (set, _("Menu"), E2_OPTION_TREE_TYPE_BOOL, TRUE, "true", 0, NULL, NULL); e2_option_tree_add_column (set, _("Label"), E2_OPTION_TREE_TYPE_STR, 0, "", 0, NULL, NULL); e2_option_tree_add_column (set, _("Icon"), E2_OPTION_TREE_TYPE_ICON, 0, "", 0, NULL, NULL); e2_option_tree_add_column (set, _("Tooltip"), E2_OPTION_TREE_TYPE_STR, 0, "", 0, NULL, NULL); e2_option_tree_add_column (set, _("Filename"), E2_OPTION_TREE_TYPE_STR, 0, "?.so", 0, NULL, NULL); e2_option_tree_add_column (set, _("Path"), E2_OPTION_TREE_TYPE_STR, 0, "", 0, NULL, NULL); e2_option_tree_create_store (set); e2_option_tree_prepare_defaults (set, _e2_plugins_tree_defaults); } emelfm2-0.4.1/src/actions/0000700000175000017500000000000011015120161014220 5ustar cairocairoemelfm2-0.4.1/src/actions/e2_action_option.h0000600000175000017500000000166511010340377017646 0ustar cairocairo/* $Id: e2_action_option.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelfm2. emelfm2 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 3, or (at your option) any later version. emelfm2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_ACTION_OPTION_H__ #define __E2_ACTION_OPTION_H__ void e2_action_option_actions_register (); #endif /* ndef __E2_ACTION_OPTION_H__ */ emelfm2-0.4.1/src/actions/e2_action.c0000600000175000017500000007367010776374631016300 0ustar cairocairo/* $Id: e2_action.c 853 2008-04-07 10:38:17Z tpgww $ Copyright (C) 2003-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/actions/e2_action.c @brief action system This file contains functions for the action system. */ /** \page actions the action system ToDo - description of how actions work \section file_operations operations on selected items ToDo */ #include "emelfm2.h" #include #include "e2_action.h" #include "e2_action_option.h" #include "e2_task.h" #include "e2_plugins.h" #include "e2_filetype.h" #include "e2_dialog.h" #include "e2_mkdir_dialog.h" #include "e2_complete.h" //static GHashTable *actions_hash; //static GPtrArray *actions_array; GtkTreeStore *actions_store; //for action-names in config dialog combo renderers /*******************/ /***** actions *****/ /*******************/ /** @brief process a toggle-button click This expects as data the key of the relevant toggle data struct in the toggles hash @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if task completed successfully, else FALSE */ static gboolean _e2_action_do_toggle (gpointer from, E2_ActionRuntime *art) { gboolean newstate, retval; gchar *hash_name = (gchar *) art->data; E2_ToggleData *ex = g_hash_table_lookup (toggles_hash, hash_name); //check if action name is in the array of "self-managed" toggles gint i; for (i = 0; i < E2_TOGGLE_COUNT; i++) { if (g_str_equal (toggles_array [i], ex->true_action) && g_str_equal (ex->true_action, ex->false_action)) break; } if (i < E2_TOGGLE_COUNT) //this is a self-mangaged toggle newstate = !ex->current_state; else newstate = e2_toolbar_button_toggle_custom (hash_name); //get the operation corresponding to the un-toggled state gchar *cc = g_strdup ((newstate) ? ex->false_action : ex->true_action); if (e2_action_check (cc) != NULL) { gchar *gap = e2_utils_find_whitespace (cc); if (gap != NULL) { *gap = '\0'; gap = e2_utils_pass_whitespace (gap+1); } retval = e2_action_run_simple_from (cc, gap, from); } else { #ifdef E2_COMMANDQ gint res = e2_command_run (cc, E2_COMMAND_RANGE_DEFAULT, TRUE); #else gint res = e2_command_run (cc, E2_COMMAND_RANGE_DEFAULT); #endif retval = (res == 0); } g_free (cc); return retval; } /** @brief process a constructed custom command This expects data string of the form " realcmd [realargs]" @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if command returned 0 */ static gboolean _e2_action_custom_command (gpointer from, E2_ActionRuntime *art) { gchar *command = g_strdup ((gchar *)art->data); #ifdef E2_COMMANDQ gint res = e2_command_run (command, E2_COMMAND_RANGE_DEFAULT, TRUE); #else gint res = e2_command_run (command, E2_COMMAND_RANGE_DEFAULT); #endif g_free (command); return (res == 0); } #ifdef E2_VFS /** @brief dummy action for plugins which do not really belong on a plugins menu @param from the button, menu item etc which was activated @param art action runtime data @return FALSE always */ gboolean e2_action_inaction (gpointer from, E2_ActionRuntime *art) { return FALSE; } #endif /*********************/ /***** callbacks *****/ /*********************/ /** @brief actions treestore sort-function This sorts on column 1 of the store @param model the GtkTreeModel for the store being sorted @param a a GtkTreeIter in @a model @param b another GtkTreeIter in @a model @param userdata UNUSED data specified when the compare func was assigned @return <0, 0, >0 if a sorts before b, with b, or after b, respectively */ static gint _e2_action_tree_compare_cb (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer userdata) { gint ret; gchar *name1, *name2; gtk_tree_model_get (model, a, 1, &name1, -1); gtk_tree_model_get (model, b, 1, &name2, -1); if (name1 == NULL || name2 == NULL) { if (name1 == NULL && name2 == NULL) return 0; if (name1 == NULL) { g_free (name2); return -1; } else { g_free (name1); return 1; } } else { ret = g_utf8_collate (name1,name2); g_free(name1); g_free(name2); } return ret; } /** @brief actions treestore visibility-function This determines which actions are visible in a filter model for the store, based on data stored in column 2 @param model the filtermodel for the actions store @param iter pointer to data for row in @a model to be evaluated @param data pointerised flags specified when @a model was created @return TRUE if the row is visible */ static gboolean _e2_action_visible_cb (GtkTreeModel *model, GtkTreeIter *iter, gpointer data) { gboolean retval; E2_ACTION_EXCLUDE ex1 = GPOINTER_TO_INT (data); E2_ACTION_EXCLUDE ex2; gchar *name; //, *child; // gboolean notfake, col5; gtk_tree_model_get (model, iter, 0, &name, //1, &child, 2, &ex2, // 3, ¬fake, 4, &col5, -1); retval = !(ex1 & ex2 & 0xffff); //special cases if ((ex1 & E2_ACTION_INCLUDE_FILES) && name != NULL) retval = (retval && g_str_has_prefix (name, _A(5))); g_free (name); // g_free (child); return retval; } /******************/ /***** public *****/ /******************/ /** @brief option helper for action columns This function is used to source a model for config-dialog cell renderers in E2_OPTION_TREE_TYPE_SEL columns of tree-options @param ex pointerised flags which specify categories of actions * that should not be visible @return GtkTreeFilterModel of all actions */ GtkTreeModel *e2_action_filter_store (gpointer ex) { GtkTreeModel *filter = gtk_tree_model_filter_new ( GTK_TREE_MODEL (actions_store), NULL); gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter), (GtkTreeModelFilterVisibleFunc) _e2_action_visible_cb, ex, NULL); return filter; } /** @brief short-form register action function See e2_action_register function description. This one has no exclude flags or data2 @return registered action E2_Action */ E2_Action *e2_action_register_simple (gchar *name, E2_ACTION_TYPE type, void *func, gpointer data, gboolean has_arg) { return e2_action_register (name, type, func, data, has_arg, 0, NULL); } gint entry_level = 0; /** @brief register action names This function registers 'public' action names, and corresponding parameters, for later use as needed. It replaces any previously-registered action with the same name. Also adds the action name etc to the options store, as a convenience for the option system Action names need to be freed (= action removed from the hash) when the session ends, if not before. Potentially re-entrant. @a has_arg is stored here, but never used elsehwhere ! As another convenience, the non-constant name string used to register the action is freed at exit. @param name freeable string with possible '.' separating 'parent/ and 'child' parts of the action @param type flags for one of: item (?) submenu, file_actions, custom_command, plugins, separator, tear_off_menu, toggle, command_line, bookmarks, dummy @param func the function to be called when the action is run @param data pointer to data to be supplied to @a func, or NULL @param has_arg boolean flag signalling whether the action-string uses context-specific argument(s) @param exclude flags indicating whether to exclude this action from toolbars, menus &| keybindings @param data2 additional data needed by the action, or NULL @return registered action E2_Action */ E2_Action *e2_action_register (gchar *name, E2_ACTION_TYPE type, void *func, gpointer data, gboolean has_arg, E2_ACTION_EXCLUDE exclude, gpointer data2) { //unregister any previously registered actions with that name //(for convenience) entry_level++; //keep note of whether re-entrant //now allocate the action structure and initialize it from the //function parameters #ifdef USE_GLIB2_10 E2_Action *action = (E2_Action *) g_slice_alloc (sizeof (E2_Action)); // E2_Action *action = ALLOCATE (E2_Action); #else E2_Action *action = ALLOCATE (E2_Action); #endif CHECKALLOCATEDFATAL (action); //freed if the action is removed from the actions_hash table //because the names are used as keys action->name = name; action->type = type; action->func = func; action->has_arg = has_arg; action->data = data; action->exclude = exclude; action->data2 = data2; //add the action to the static hash table and pointer array register g_hash_table_replace (actions_hash, action->name, action); // g_ptr_array_add (actions_array, action); //to get structured dropdown lists, find and register the 'parent' action, if any gboolean registered_parent = FALSE; E2_Action *parent = NULL; gchar *search = g_strdup (name); gchar *child = strrchr (search, '.'); //always ascii '.', don't need g_utf8_strrchr() // if (child == NULL) // child = strrchr (search, ':'); //ditto if (child != NULL) { //also register a new parent, if need be *child = '\0'; parent = e2_action_get (search); if (parent == NULL) { //re-entry !! parent = e2_action_register_simple (search, E2_ACTION_TYPE_DUMMY, NULL, NULL, FALSE); registered_parent = TRUE; } } //now add the action to the treestore GtkTreeIter iter; GtkTreeIter iter_parent; GtkTreeIter *parentptr; //setup store //(sortable, with filter-model for selection-type cell renderers in config dialog trees) if (parent == NULL) parentptr = NULL; else { parentptr = &iter_parent; if (!e2_tree_ref_to_iter (actions_store, parent->ref, parentptr)) parentptr = NULL; } //CHECKME only columns 0 and 2 are used ? #ifdef USE_GTK2_10 gtk_tree_store_insert_with_values (actions_store, &iter, parentptr, -1, #else gtk_tree_store_append (actions_store, &iter, parentptr); gtk_tree_store_set (actions_store, &iter, #endif 0, action->name, 1, (child == NULL) ? action->name : g_strstrip (child + 1), 2, action->exclude, 3, action->type != E2_ACTION_TYPE_DUMMY, 4, TRUE, -1); //keep reference around to be able to to find out the parent iter //for possible children later on action->ref = e2_tree_iter_to_ref (actions_store, &iter); //cleanup if (!registered_parent) g_free (search); //registered actions are kept, but this one was not needed // else if (child != NULL) // *child = '.'; //what about ':' ? // if (--entry_level == 0) //revert re-entrant level, for next time // g_free (name); //and this is not re-entrant, so free the original name-space entry_level--; return action; } /** @brief unregister action named @a name This is for plugin actions, essentially @param name action name string @return TRUE if the action was registered */ gboolean e2_action_unregister (gchar *name) { E2_Action *action = g_hash_table_lookup (actions_hash, name); if (action == NULL) return FALSE; GtkTreeIter iter, parent; if (e2_tree_ref_to_iter (actions_store, action->ref, &iter) && gtk_tree_model_iter_parent (GTK_TREE_MODEL (actions_store), &parent, &iter) && gtk_tree_model_iter_n_children (GTK_TREE_MODEL (actions_store), &parent) == 1) gtk_tree_store_remove (actions_store, &parent); g_hash_table_remove (actions_hash, name); return TRUE; } /** @brief find an action by name The action with the name @a name is looked up in the internal hash table and a pointer to the action object is returned. The name should be the complete name of the action including any parent categories. If the action cannot be found, NULL is returned. @param name the name of the action @return pointer to an action object or NULL */ E2_Action *e2_action_get (gchar *name) { return (g_hash_table_lookup (actions_hash, name)); } /** @brief find an action by name, reverting to custom if not a registered action @param taskname action or external command string @param arg the argument to be supplied to the action or command, or NULL @param use_arg pointer to store newly-allocated "real" argument string @return pointer to an action object, or NULL if @a taskname is NULL */ E2_Action *e2_action_get_with_custom (gchar *taskname, gchar *arg, gchar **use_arg) { if (taskname == NULL) return NULL; E2_Action *action = e2_action_get (taskname); if (action == NULL) { //fallback to custom command action = e2_action_get (_A(17)); if (arg == NULL || *arg == '\0') *use_arg = g_strdup (taskname); else *use_arg = g_strconcat (taskname, " ", arg, NULL); } else { if (arg == NULL) *use_arg = g_strdup (""); else *use_arg = g_strdup (arg); } return action; } /** @brief check whether @a command is an action @param command the command string @return pointer to the action, or NULL if @a command not an action */ E2_Action* e2_action_check (gchar *command) { if (command == NULL) //this should never happen, but ... return NULL; //#warning ignore compiler warning about unitialized usage of c gchar c; gchar *gap = e2_utils_find_whitespace (command); if (gap != NULL) { c = *gap; *gap = '\0'; } E2_Action *action = e2_action_get (command); if (gap != NULL) *gap = c; if (action == NULL) return NULL; if (action->type == E2_ACTION_TYPE_DUMMY) return NULL; return action; } /** @brief run a certain action by name This function is to run a certain action and wraps around e2_action_run_simple_from(). It sets the @a from widget of e2_action_run_simple_from to NULL. @param name the name of the action to run @param arg additional user data, may be NULL @return TRUE on success, FALSE on failure */ gboolean e2_action_run_simple (gchar *name, gpointer arg) { return e2_action_run_simple_from (name, arg, NULL); } /** @brief run a named 'task' (may be a command or an action) This function is to run a certain action. First, the action specified by @a name is looked up. Then a runtime object is created and the action is run. Afterwards the runtime object is freed again. If the action could not be found, an error message is printed to the output pane. @param name action name or command, string @param arg pointer to data for the command, or possibly arguments or more of the command, or NULL @param from the widget activated to initiate the action @return TRUE on success, FALSE on failure */ gboolean e2_action_run_simple_from (gchar *name, gpointer arg, gpointer from) { //CHECKME should these variables be volatilized ? if ((name == NULL || *name == '\0') //chained keybindings may have no command && (arg == NULL || *(gchar *)arg == '\0')) return FALSE; gchar *real_arg; E2_Action *action = e2_action_get_with_custom (name, arg, &real_arg); E2_ActionRuntime *rt = e2_action_pack_runtime (action, real_arg, g_free); gboolean retval = e2_action_run (from, rt); e2_action_free_runtime (rt); return retval; } /** @brief callback to run an action This function wraps the real action run function, with thread unlocking so that any such locking can be managed locally, without mutux reentrance. @param from the item (GtkButton GtkMenuItem etc) activated to trigger the action @param rt runtime data for the action @return */ /*void e2_action_run_cb (gpointer from, E2_ActionRuntime *rt) { gdk_threads_leave (); e2_action_run (from, rt); gdk_threads_enter (); }*/ /** @brief run a certain action This function is the real action run function. It may be used as callback for gtk signals, if downstream thread [un]locks are managed. There are several wrappers to simplify access. Runtime data @a rt may contain additional user data. @a from may be used by the action handler to determine the context. @param from the item activated to trigger the action (eg toolbar GtkButton, maybe NULL) @param rt runtime data for the action @return TRUE on success of the action, FALSE on failure */ gboolean e2_action_run (gpointer from, E2_ActionRuntime *rt) { //some quick checks if ((rt == NULL) || (rt->action == NULL) || (rt->action->func == NULL)) { printd (DEBUG, "cannot run action, it doesn't exist"); return FALSE; } printd (DEBUG, "e2_action_run (from:, rt:%s)", rt->action->name); gboolean (*fun) (gpointer, E2_ActionRuntime *) = rt->action->func; #ifndef DEBUG_MESSAGES return fun (from, rt); #else gboolean res = fun (from, rt); printd (DEBUG,"e2_action_run () ends"); return res; #endif } /** @brief create an action runtime object This function creates an action runtime object and initializes it with supplied user data. @param action the action to create the runtime object for, or command string for a "custom command" @param data action user data @param data_free function to free data when the runtime object is freed @return newly allocated action runtime object that should be freed */ E2_ActionRuntime *e2_action_pack_runtime (E2_Action *action, gpointer data, gpointer data_free) { // printd (DEBUG, "action pack runtime for %s", action->name); E2_ActionRuntime *rt = ALLOCATE (E2_ActionRuntime); CHECKALLOCATEDFATAL (rt); rt->action = action; rt->data = data; rt->data_free = (data == NULL) ? NULL : data_free; return rt; } /** @brief free action runtime object The runtime data is freed if a data free function has been supplied. In addition the runtime object is freed and must not be used anymore after calling this function. @param rt the action runtime object to free @return */ void e2_action_free_runtime (E2_ActionRuntime *rt) { // printd (DEBUG, "e2_action_free_runtime"); if (rt == NULL) return; if (rt->data_free != NULL) { void (*fun) (gpointer) = rt->data_free; fun (rt->data); } DEALLOCATE (E2_ActionRuntime, rt); } /** @brief clear data for an entry in the toggles hash @param ex pointer to hash table data item @return */ static void _e2_action_free_toggle (E2_ToggleData *ex) { e2_list_free_with_data (&ex->boxes); if (ex->true_action != NULL) g_free (ex->true_action); if (ex->false_action != NULL) g_free (ex->false_action); DEALLOCATE (E2_ToggleData, ex); } /** @brief internal action system memory cleanup @return */ static void _e2_action_clean1 (E2_Action *action) { //action name is the hash key, cleared by g_free GtkTreeIter iter; if (e2_tree_ref_to_iter (actions_store, action->ref, &iter)) gtk_tree_store_remove (actions_store, &iter); gtk_tree_row_reference_free (action->ref); #ifdef USE_GLIB2_10 g_slice_free1 (sizeof (E2_Action), action); // DEALLOCATE (E2_Action, action); #else DEALLOCATE (E2_Action, action); #endif } /* presently unused static void free_name (gchar *key, E2_Action *value, gpointer data) { g_free (key); //=value->name return; } */ /** @brief internal action system cleanup After calling the function, the action system may not be used anymore. It is only called internally just before exit. @return */ /* this done only at session end, don't bother void e2_actions_clean () { //CHECKME = should this be able to handle ->data &| ->data2 ? g_object_unref (actions_store); //clean up non-constant action name strings in the hash g_hash_table_foreach (actions_hash, (GFunc) free_name, NULL); g_hash_table_destroy (actions_hash); //CHECKME = array cleanup ok for non-constant strings? could this replace the hash foreach? // g_ptr_array_free (actions_array, TRUE); } */ /** @brief internal action system initialization This function is only called once at startup to initialize the action system. Afterwards, it has no effect. It also registers most actions (and "pseudo-actions" for toolbars). Some other actions are registered in the pane create func. @return */ void e2_actions_init (void) { //ensure that this function is only performed once RUN_ONCE_CHECK (); //initialize local data structures that are used to keep track of //the action parameters //note - this needs to be done before any plugin is loaded actions_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) _e2_action_clean1); // actions_array = g_ptr_array_new (); actions_store = gtk_tree_store_new (5, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN); //CHECKME is this still necessary? // g_object_ref (actions_store); toggles_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) _e2_action_free_toggle); toggles_array [E2_TOGGLE_PANE1FULL] = g_strconcat (_A(11),".",_A(108),NULL); toggles_array [E2_TOGGLE_PANE2FULL] = g_strconcat (_A(12),".",_A(108),NULL); toggles_array [E2_TOGGLE_PANE1HIDDEN] = g_strconcat (_A(11),".",_A(80),NULL); toggles_array [E2_TOGGLE_PANE2HIDDEN] = g_strconcat (_A(12),".",_A(80),NULL); toggles_array [E2_TOGGLE_PANE1FILTERS] = g_strconcat (_A(11),".",_A(22),NULL); toggles_array [E2_TOGGLE_PANE2FILTERS] = g_strconcat (_A(12),".",_A(22),NULL); toggles_array [E2_TOGGLE_OUTPUTFULL] = g_strconcat (_A(9),".",_A(108),NULL); toggles_array [E2_TOGGLE_OUTPUTSHADE] = g_strconcat (_A(9),".",_A(79),NULL); #ifdef E2_VFS toggles_array [E2_TOGGLE_PANE1SPACE] = g_strconcat (_A(11),".",_A(115),NULL); toggles_array [E2_TOGGLE_PANE2SPACE] = g_strconcat (_A(12),".",_A(115),NULL); #endif //register most actions //note translated action labels needed here, i.e. before config loaded E2_Action homeless_actions[] = { //here we don't bother explicitly setting 3 trailing NULL parameters { NULL, NULL, FALSE, E2_ACTION_TYPE_FILE_ACTIONS, E2_ACTION_EXCLUDE_ACCEL | E2_ACTION_EXCLUDE_TOOLBAR | E2_ACTION_EXCLUDE_LAYOUT }, { NULL, NULL, FALSE, E2_ACTION_TYPE_PLUGINS, E2_ACTION_EXCLUDE_ACCEL | E2_ACTION_EXCLUDE_TOOLBAR }, { NULL, NULL, FALSE, E2_ACTION_TYPE_SEPARATOR, E2_ACTION_EXCLUDE_ACCEL | E2_ACTION_EXCLUDE_LAYOUT }, { NULL, NULL, FALSE, E2_ACTION_TYPE_SUBMENU, E2_ACTION_EXCLUDE_ACCEL | E2_ACTION_EXCLUDE_LAYOUT }, { NULL, e2_main_user_shutdown, E2_ACTION_TYPE_ITEM, FALSE, 0 }, { NULL, e2_task_configure, TRUE, E2_ACTION_TYPE_ITEM, 0 }, { NULL, e2_task_configure_default, FALSE, E2_ACTION_TYPE_ITEM, 0 }, { NULL, e2_plugins_configure, FALSE, E2_ACTION_TYPE_ITEM, 0 }, { NULL, _e2_action_custom_command, TRUE, E2_ACTION_TYPE_ITEM, E2_ACTION_EXCLUDE_GENERAL }, { NULL, _e2_action_do_toggle, TRUE, E2_ACTION_TYPE_TOGGLE, E2_ACTION_EXCLUDE_TOGGLE }, { NULL, _e2_action_do_toggle, TRUE, E2_ACTION_TYPE_TOGGLE, E2_ACTION_EXCLUDE_TOGGLE }, }; //all action names must be freeable homeless_actions[0].name = g_strconcat(_A(5),".",_A(20),NULL); homeless_actions[1].name = g_strconcat(_A(14),".",_A(24),NULL); homeless_actions[2].name = g_strdup(_A(18)); homeless_actions[3].name = g_strdup(_A(19)); homeless_actions[4].name = g_strconcat(_A(1),".",_A(69),NULL); //configuration homeless_actions[5].name = g_strconcat(_A(2),".",_A(32),NULL); homeless_actions[6].name = g_strconcat(_A(2),".",_A(37),NULL); homeless_actions[7].name = g_strconcat(_A(2),".",_C(33),NULL); homeless_actions[8].name = g_strdup(_A(17)); homeless_actions[9].name = g_strconcat(_A(15),".",_A(110),NULL); homeless_actions[10].name = g_strconcat(_A(15),".",_A(111),NULL); gint count = sizeof (homeless_actions) / sizeof (E2_Action); gint i; for (i = 0; i < count; i++) { e2_action_register ( homeless_actions[i].name, homeless_actions[i].type, homeless_actions[i].func, NULL, homeless_actions[i].has_arg, homeless_actions[i].exclude, NULL); } //register most other actions // (some others are in pane create fn) e2_task_actions_register (); e2_output_actions_register(); // e2_context_menu_actions_register(); e2_bookmark_actions_register (); e2_filetype_actions_register (); e2_command_line_actions_register (); e2_command_actions_register (); e2_window_actions_register (); e2_pane_actions_register (); // e2_toolbar_actions_register (); e2_about_dialog_actions_register (); e2_edit_dialog_actions_register (); e2_view_dialog_actions_register (); // e2_search_dialog_actions_register (); e2_mkdir_dialog_actions_register (); #ifdef E2_KEYALIAS e2_keybinding_actions_register (); #endif e2_action_option_actions_register (); #ifdef E2_FS_MOUNTABLE e2_fs_mount_actions_register (); #endif //sort the actions tree store GtkTreeSortable *sortable = GTK_TREE_SORTABLE (actions_store); gtk_tree_sortable_set_sort_func (sortable, 0, _e2_action_tree_compare_cb, NULL, NULL); gtk_tree_sortable_set_sort_column_id (sortable, 0, GTK_SORT_ASCENDING); } /** @brief setup array of translated action labels No spaces in names, so action arguments can be separated by a space Some of these are the same as config dialog labels Array size defined in e2_action.h @return */ void e2_action_setup_labels (void) { //CHECKME= which of these names can be rationalised ? //'parent' names action_labels[0] = _("bookmark"); action_labels[1] = _("command"); action_labels[2] = _("configure"); action_labels[3] = _("dialog"); action_labels[4] = _("dirline"); action_labels[5] = _("file"); action_labels[6] = _("find"); action_labels[7] = _("list"); //was action_labels[8] = _("option"); action_labels[9] = _("output"); // = _C(27) action_labels[10] = _("pane_active"); action_labels[11] = _("pane1"); // ~ _C(28) action_labels[12] = _("pane2"); // ~ _C(30) action_labels[13] = _("panes"); // = _C(32) action_labels[14] = _("plugin"); //this is also used in menus - see #define PLUGIN in emelfm2.h action_labels[15] = _("toggle"); action_labels[16] = _("separator"); //not really an action, but not a config either action_labels[17] = _(""); action_labels[18] = _(""); action_labels[19] = _(""); //'child' names action_labels[20] = _(""); action_labels[21] = _(""); action_labels[22] = _(""); action_labels[23] = _(""); action_labels[24] = _(""); action_labels[25] = _("about"); action_labels[26] = _("add"); action_labels[27] = _("adjust_ratio"); action_labels[28] = _("children"); action_labels[29] = _("clear"); action_labels[30] = _("clear_history"); action_labels[31] = _("complete"); action_labels[32] = _("application"); action_labels[33] = _("copy"); action_labels[34] = _("copy_as"); action_labels[35] = _("copy_merge"); action_labels[36] = _("copy_with_time"); action_labels[37] = _("default"); action_labels[38] = _("delete"); action_labels[39] = _("edit"); action_labels[40] = _("edit_again"); action_labels[41] = _("filetype"); action_labels[42] = _("focus"); action_labels[43] = _("focus_toggle"); action_labels[44] = _("fullscreen"); action_labels[45] = _("go_back"); action_labels[46] = _("go_forward"); action_labels[47] = _("go_up"); action_labels[48] = _("goto_bottom"); action_labels[49] = _("goto_top"); action_labels[50] = _("help"); action_labels[51] = _("history"); action_labels[52] = _("info"); action_labels[53] = _("insert_selection"); action_labels[54] = _("invert_selection"); action_labels[55] = _("mirror"); action_labels[56] = _("mkdir"); action_labels[57] = _("mountpoints"); action_labels[58] = _("move"); action_labels[59] = _("move_as"); action_labels[60] = _("open"); action_labels[61] = _("open_in_other"); action_labels[62] = _("open_with"); action_labels[63] = _("owners"); action_labels[64] = _("page_down"); action_labels[65] = _("page_up"); action_labels[66] = _("pending"); action_labels[67] = _("permissions"); action_labels[68] = _("print"); action_labels[69] = _("quit"); action_labels[70] = _("refresh"); action_labels[71] = _("refreshresume"); action_labels[72] = _("refreshsuspend"); action_labels[73] = _("rename"); action_labels[74] = _("scroll_down"); action_labels[75] = _("scroll_up"); action_labels[76] = _("search"); //= _C(35) action_labels[77] = _("send"); action_labels[78] = _("set"); action_labels[79] = _("show"); action_labels[80] = _("show_hidden"); action_labels[81] = _("show_menu"); action_labels[82] = _("sortaccesssed"); action_labels[83] = _("sortchanged"); action_labels[84] = _("sortgroup"); action_labels[85] = _("sortmodified"); action_labels[86] = _("sortname"); action_labels[87] = _("sortpermission"); action_labels[88] = _("sortsize"); action_labels[89] = _("sortuser"); action_labels[90] = _("switch"); action_labels[91] = _("symlink"); action_labels[92] = _("symlink_as"); action_labels[93] = _("sync"); action_labels[94] = _("toggle_direction"); // action_labels[95] = _("toggle_full"); //changed to _A(108)"expand" with new toggles action_labels[95] = _("toggle_select_all"); action_labels[96] = _("toggle_selected"); action_labels[97] = _("trash"); action_labels[98] = _("trashempty"); #ifdef E2_TREEDIALOG action_labels[99] = _("tree"); #endif action_labels[100] = _("unpack"); action_labels[101] = _("view"); //= _C(40) action_labels[102] = _("view_again"); action_labels[103] = _("view_at"); //these are action _parameter_ strings action_labels[104] = _("child"); action_labels[105] = _("ctrl"); action_labels[106] = _("dirs"); action_labels[107] = _("escape"); action_labels[108] = _("expand"); action_labels[109] = _("files"); action_labels[110] = _("off"); action_labels[111] = _("on"); action_labels[112] = _("quote"); action_labels[113] = _("shift"); action_labels[114] = _("top"); //late entries, out of order //maybe more space here, see array definition in header file #ifdef E2_VFS action_labels[115] = _(""); action_labels[116] = _("dummy"); action_labels[117] = _("namespace"); action_labels[118] = _("unpack_in_other"); #endif #ifdef E2_KEYALIAS action_labels[119] = _("key"); action_labels[120] = _("alias"); #endif } /* //copy all actions, for help document void build_list (gpointer key, gpointer value, GString *text) { E2_Action *action = (E2_Action *) value; if (action->type != E2_ACTION_TYPE_DUMMY) { text = g_string_append (text, action->name); text = g_string_append_c (text, '\t'); text = g_string_append_c (text, (action->has_arg) ? 'Y' : 'N'); text = g_string_append_c (text, '\n'); } return; } void e2_action_list_all (void) { GString *cmds_list = g_string_new (""); g_hash_table_foreach (actions_hash, (GHFunc) build_list, cmds_list); GtkClipboard* clipper = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); gtk_clipboard_set_text (clipper, cmds_list->str, cmds_list->len); ls e2* g_string_free (cmds_list, TRUE); } */ emelfm2-0.4.1/src/actions/e2_action_option.c0000600000175000017500000000274010776374631017656 0ustar cairocairo/* $Id: e2_action_option.c 853 2008-04-07 10:38:17Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelfm2. emelfm2 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 3, or (at your option) any later version. emelfm2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "emelfm2.h" //#include #include "e2_action_option.h" /** @brief set the value of a non-tree config option requires arg string in config-file format like "option-name=option_value" @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_action_option_set (gpointer from, E2_ActionRuntime *art) { gchar *tmp[2] = { (gchar *)art->data, NULL }; e2_option_read_array (tmp); return TRUE; } void e2_action_option_actions_register () { gchar *action_name = g_strconcat (_A(8),".",_A(78),NULL); //_(option.set") e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_action_option_set, NULL, FALSE); } emelfm2-0.4.1/src/actions/e2_action.h0000600000175000017500000001110211010340377016241 0ustar cairocairo/* $Id: e2_action.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2003-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/actions/e2_action.h @brief action system header This is the header file for the action system. */ #ifndef __E2_ACTION_H__ #define __E2_ACTION_H__ //these guide the creation of toolbars and menus typedef enum { E2_ACTION_TYPE_ITEM, //all "non-special" (pseudo)actions default to this E2_ACTION_TYPE_SUBMENU, E2_ACTION_TYPE_FILE_ACTIONS, //this is the context-menu widget E2_ACTION_TYPE_CUSTOM_COMMAND, //UNUSED E2_ACTION_TYPE_PLUGINS, E2_ACTION_TYPE_SEPARATOR, E2_ACTION_TYPE_TEAR_OFF_MENU, E2_ACTION_TYPE_TOGGLE, E2_ACTION_TYPE_COMMAND_LINE, E2_ACTION_TYPE_BOOKMARKS, E2_ACTION_TYPE_CHECK_ITEM, //UNUSED E2_ACTION_TYPE_FILTERS, //UNUSED E2_ACTION_TYPE_DUMMY } E2_ACTION_TYPE; //flags for controlling display and/or use of actions typedef enum { E2_ACTION_EXCLUDE_GENERAL = 1 << 0, E2_ACTION_EXCLUDE_TOOLBAR = 1 << 1, //not allowed as a toolitem action E2_ACTION_EXCLUDE_MENU = 1 << 2, //not allowed as a menu-item action E2_ACTION_EXCLUDE_LAYOUT = 1 << 3, //this is one of the pseudo-actions E2_ACTION_EXCLUDE_ACCEL = 1 << 4, //not allowed as a key-binding or command E2_ACTION_EXCLUDE_TOGGLE = 1 << 5, E2_ACTION_INCLUDE_FILES = 1 << 16, } E2_ACTION_EXCLUDE; //all of the above #define E2_ACTION_EXCLUDE_ALL 0x3f typedef enum { E2_TOGGLE_NONE, E2_TOGGLE_INIT, E2_TOGGLE_DESTROYED, E2_TOGGLE_REINIT, } E2_TOGGLE_STATUS; typedef struct _E2_Action { gchar *name; //string which identifies (i.e. actions hash key) //and/or runs the action, utf-8, translated gpointer func; //function to run when action is initiated gboolean has_arg; //TRUE if action-string includes context-specific argument(s) (stored BUT UNUSED!) E2_ACTION_TYPE type; //enumerator which instructs toolbar creation E2_ACTION_EXCLUDE exclude; //flags controlling use and display of the action gpointer data; //data specified when action was registered, //sent to each instance as argument "action_data" gpointer data2; //more such data eg for toolbar usage GtkTreeRowReference *ref; //reference to action's row in actions_store } E2_Action; typedef struct _E2_ActionRuntime { E2_Action *action; gpointer data; gpointer data_free; } E2_ActionRuntime; GHashTable *toggles_hash; #ifdef E2_TREEDIALOG //make sure that the array size works for the extry action label(s) //_A(109) may be used #endif #ifdef E2_KEYALIAS //make sure this is big enough for all labels, 2 extra for this option #define ACTION_LABEL_COUNT 121 #else //make sure this is big enough for all labels #define ACTION_LABEL_COUNT 119 #endif typedef gchar *_action_labels[ACTION_LABEL_COUNT]; _action_labels action_labels; #define _A(d) action_labels[d] GtkTreeModel *e2_action_filter_store (gpointer data); E2_Action *e2_action_register_simple (gchar *name, E2_ACTION_TYPE type, void *func, gpointer data, gboolean has_arg); E2_Action *e2_action_register (gchar *name, E2_ACTION_TYPE type, void *func, gpointer data, gboolean has_arg, E2_ACTION_EXCLUDE exclude, gpointer data2); gboolean e2_action_unregister (gchar *name); E2_Action *e2_action_get (gchar *name); E2_Action *e2_action_get_with_custom (gchar *name, gchar *arg, gchar **use_arg); E2_Action* e2_action_check (gchar *command); gboolean e2_action_run_simple (gchar *name, gpointer arg); gboolean e2_action_run_simple_from (gchar *name, gpointer arg, gpointer from); #ifdef E2_VFS gboolean e2_action_inaction (gpointer from, E2_ActionRuntime *art); #endif //void e2_action_run_cb (gpointer from, E2_ActionRuntime *rt); #define e2_action_run_cb e2_action_run gboolean e2_action_run (gpointer from, E2_ActionRuntime *rt); E2_ActionRuntime *e2_action_pack_runtime (E2_Action *action, gpointer data, gpointer data_free); void e2_action_free_runtime (E2_ActionRuntime *rt); //void e2_actions_clean (); //initialization things void e2_action_setup_labels(void); void e2_actions_init (void); #endif //ndef __E2_ACTION_H__ emelfm2-0.4.1/src/e2_window.h0000600000175000017500000000424711010340377014647 0ustar cairocairo/* $Id: e2_window.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_WINDOW_H__ #define __E2_WINDOW_H__ #include "emelfm2.h" typedef struct _E2_WindowRuntime { GtkWidget *panes_outer_box; GtkWidget *panes_inner_box; GtkWidget *panes_paned; gdouble panes_paned_ratio; gdouble panes_paned_ratio_last; gint panes_paned_pos; GtkWidget *output_paned; gdouble output_paned_ratio; gdouble output_paned_ratio_last; gint output_paned_pos; gboolean panes_horizontal; } E2_WindowRuntime; #ifdef E2_COMPOSIT void e2_window_set_opacity (GtkWidget *window, gint level); #endif gboolean e2_window_output_show (GtkWidget *widget, gpointer data); gboolean e2_window_output_hide (GtkWidget *widget, GdkEventFocus *event, gpointer data); void e2_window_set_title (GtkWidget *widget, const gchar *title); void e2_window_set_cursor(GdkCursorType type); gboolean e2_window_update_status_bar (gpointer userdata); void e2_window_enable_status_update (gint interval); void e2_window_disable_status_update (void); //void e2_window_show_status_message (gchar *message); //void e2_window_remove_status_message (void); void e2_window_show_status_message (gchar *message); void e2_window_clear_status_message (void); //UNUSED void e2_window_adjust_output_pane_ratio (gchar *arg); void e2_window_adjust_pane_ratio (gchar *arg); void e2_window_create (E2_WindowRuntime *rt); void e2_window_recreate (E2_WindowRuntime *rt); void e2_window_actions_register (void); #endif //ndef __E2_WINDOW_H__ emelfm2-0.4.1/src/e2_fileview.c0000600000175000017500000044757511014516433015166 0ustar cairocairo/* $Id: e2_fileview.c 893 2008-05-20 09:42:51Z tpgww $ Portions copyright (C) 2004-2008 tooar . This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/e2_fileview.c @brief directory content view functions This file contains functions related to creation of treeviews to display directory content, plus related selections and sorting. */ /** \page treeviews filelist treeviews ToDo - description of how to work with the filelist treeviews \section cell_rend cell-renderers ToDo */ #include "emelfm2.h" //#include #include #include #include #include "e2_dnd.h" #include "e2_filelist.h" #include "e2_context_menu.h" #include "e2_option.h" //#include "e2_tree_dialog.h" #include "e2_dialog.h" #include "e2_task.h" //allow in-place renaming of items TOO ANNOYING AND CLUMSY //(initiated by left-click on filename when line is not selected) //#define EDIT_INPLACE //do incremental filelist refreshes //#define E2_NEWREFRESH //#ifdef E2_NEWREFRESH typedef enum { REFRESH_ADD = 0, REFRESH_DELETE, //unused REFRESH_CHANGE, REFRESH_KEEP, } E2_RefreshMode; //one of these for each entry in a new list of FileInfo's created to check //against current FileInfo's in a view typedef struct _E2_RefreshInfo { guint newindx; //index of row in changes liststore guint oldindx; //index of row in current liststore (ex. when adding) E2_RefreshMode oldmode; //what to do with that row } E2_RefreshInfo; //#endif typedef struct _E2_DirHistoryEntry { gchar path[PATH_MAX]; //must be at start of stuct (struct ptr sometimes used for lists of strings) gint toprow; //index of top visible row gint selrow; //index of 1st selected row, -1 if no selection E2_FSSensitive case_sensitive_names; } E2_DirHistoryEntry; extern GtkTargetEntry target_table[]; extern guint n_targets; //this holds, in fixed column order (used in model & config, cache data), // the displayed column widths, a row for each pane gint col_width_store[2][MAX_COLUMNS]; //this holds, in fixed column order (used in model & config, cache data), // the displayed column order, a row for each pane gint stored_col_order[2][MAX_COLUMNS]; //this holds column data from the above arrays, for cache reading/writing //GList *cols_data; //this holds, in displayed column order // the fixed column order, a row for each pane gint displayed_col_order[2][MAX_COLUMNS]; // Colors //GdkColor LIST_COLOR; #ifdef E2_SELTXT_RECOLOR GdkColor selectedtext; #endif gboolean btn2_released; //flag used to decide if middle-btn DnD menu is wanted //local copy of flag static gboolean button2updir; //number of last mouse button press, for distinguishing drags static gboolean pane1_all = FALSE, pane2_all=FALSE; //these are related to column dragging static gint header_button; static gboolean block; #ifdef E2_ALTLEFTMOUSE /*this provides a quick check whether the left btn has been pressed set and cleared in button-press and -release callbacks, respectively*/ gboolean left_pressed; /*flag for whether selection-by-dragging is underway set TRUE in drag-begin cb, cleared in drag-data-get cb*/ gboolean drag_sel_now; //max msec between clicks when checking for a doubleclick //extern guint click_interval; #endif //static //gboolean cd_blocked = FALSE; //TRUE will block re-entrant and parallel cd's //pthread_mutex_t list_mutex = PTHREAD_MUTEX_INITIALIZER; //or PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP //GStaticRecMutex list_mutex; //these data should only be accessed with mutex protection //E2_CDdata p1cd_data; //E2_CDdata p2cd_data; static gpointer _e2_fileview_change_dir (E2_Listman *cddata); //functions for sorting of treeviews static gint _e2_fileview_name_sort (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction); static gint _e2_fileview_size_sort (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction); static gint _e2_fileview_perm_sort (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction); static gint _e2_fileview_user_sort (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction); static gint _e2_fileview_group_sort (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction); static gint _e2_fileview_mdate_sort (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction); static gint _e2_fileview_adate_sort (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction); static gint _e2_fileview_cdate_sort (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction); E2_Column e2_all_columns[MAX_COLUMNS] = { {N_("Filename"), 120, _e2_fileview_name_sort}, {N_("Size"), 80, _e2_fileview_size_sort}, {N_("Permissions"), 80, _e2_fileview_perm_sort}, {N_("Owner"), 60, _e2_fileview_user_sort}, {N_("Group"), 60, _e2_fileview_group_sort}, {N_("Modified"), 120, _e2_fileview_mdate_sort}, {N_("Accessed"), 120, _e2_fileview_adate_sort}, {N_("Changed"), 120, _e2_fileview_cdate_sort} }; /** @brief item-name sort-order comparison function This compares 'sort-key' row data, which is utf-8 compatible If the case-insensitive sorting option was in force when the data was loaded into the model, the keys are 'case-folded' ie this is not so dynamic as we might want Directories are placed before other things. NB utf8 collate keys for ascii text are case insensitive !! @param model the data model to be interrogated @param a pointer to model iter for the first row to be compared @param b pointer to model iter for the second row to be compared @param direction pointer to the views's sort direction @return integer <0, 0 or >0 according to whether a belongs before, = or after b */ static gint _e2_fileview_name_sort (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction) { gchar *namea, *nameb, *keya, *keyb; gint result; gboolean order = (*direction == GTK_SORT_ASCENDING); //TRUE for ascending, FALSE for descending gtk_tree_model_get (model, a, FILENAME, &namea, NAMEKEY, &keya, -1); gtk_tree_model_get (model, b, FILENAME, &nameb, NAMEKEY, &keyb, -1); if (g_str_equal (namea, "../")) result = (order) ? -1 : 1; else if (g_str_equal (nameb, "../")) result = (order) ? 1 : -1; else if (e2_fs_is_dir_fast (namea)) { if (e2_fs_is_dir_fast (nameb)) result = strcmp (keya, keyb); else result = (order) ? -1 : 1; } else if (e2_fs_is_dir_fast (nameb)) result = (order) ? 1 : -1; else result = strcmp (keya, keyb); g_free (namea); g_free (nameb); g_free (keya); g_free (keyb); return result; } /** @brief item-extension sort-order comparison function This compares 'sort-key' row data, which is utf-8 compatible Directories are placed before other things. @param model the data model to be interrogated @param a pointer to model iter for the first row to be compared @param b pointer to model iter for the second row to be compared @param direction pointer to the views's sort direction @return integer <0, 0 or >0 according to whether a belongs before, = or after b */ //FIXME = support case-insensitive sorting gint e2_fileview_ext_sort (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction) { gchar *namea, *nameb, *keya, *keyb, *ea, *eb; gint result; gboolean order = (*direction == GTK_SORT_ASCENDING); //TRUE for ascending, FALSE for descending gtk_tree_model_get (model, a, FILENAME, &namea, NAMEKEY, &keya, -1); gtk_tree_model_get (model, b, FILENAME, &nameb, NAMEKEY, &keyb, -1); if (g_str_equal (namea, "../")) result = (order) ? -1 : 1; else if (g_str_equal (nameb, "../")) result = (order) ? 1 : -1; else if (e2_fs_is_dir_fast (namea)) { if (e2_fs_is_dir_fast (nameb)) result = strcmp (keya, keyb); else result = (order) ? -1 : 1; } else if (e2_fs_is_dir_fast (nameb)) result = (order) ? 1 : -1; else { if ((ea = strrchr (namea+1, '.')) == NULL) //always ascii '.', don't need g_utf8_strrchr() { // row a has NO extension.. check row b if ((strrchr (nameb+1, '.')) != NULL) result = -1; else result = strcmp (keya, keyb); } // row a HAS an extension.. check row b else if ((eb = strrchr (nameb+1, '.')) == NULL) result = 1; // both have extensions else { result = g_utf8_collate (ea, eb); //if they are the same, sort by name if (result == 0) result = strcmp (keya, keyb); } } g_free (namea); g_free (nameb); g_free (keya); g_free (keyb); return result; } /** @brief item-size sort-order comparison function Directories are placed before other things. @param model the data model to be interrogated @param a pointer to model iter for the first row to be compared @param b pointer to model iter for the second row to be compared @param direction pointer to the views's sort direction @return integer <0, 0 or >0 according to whether a belongs before, = or after b */ static gint _e2_fileview_size_sort (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction) { gchar *namea, *nameb; FileInfo *infoa, *infob; gboolean order = (*direction == GTK_SORT_ASCENDING); //TRUE for ascending, FALSE for descending gtk_tree_model_get (model, a, FILENAME, &namea, FINFO, &infoa, -1); if (g_str_equal (namea, "../")) { g_free (namea); return (order) ? -1 : 1; } gtk_tree_model_get (model, b, FILENAME, &nameb, FINFO, &infob, -1); if (g_str_equal (nameb, "../")) { g_free (namea); g_free (nameb); return (order) ? 1 : -1; } gboolean bf = e2_fs_is_dir_fast (nameb); g_free (nameb); if (e2_fs_is_dir_fast (namea)) { g_free (namea); if (bf) return (infoa->statbuf.st_size - infob->statbuf.st_size); else return (order) ? -1 : 1; } g_free (namea); if (bf) return (order) ? 1 : -1; return (infoa->statbuf.st_size - infob->statbuf.st_size); } /** @brief item-permission sort-order comparison function Directories are placed before other things. @param model the data model to be interrogated @param a pointer to model iter for the first row to be compared @param b pointer to model iter for the second row to be compared @param direction pointer to the views's sort direction @return integer <0, 0 or >0 according to whether a belongs before, = or after b */ static gint _e2_fileview_perm_sort (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction) { gchar *namea, *nameb; FileInfo *infoa, *infob; gboolean order = (*direction == GTK_SORT_ASCENDING); //TRUE for ascending, FALSE for descending gtk_tree_model_get (model, a, FILENAME, &namea, FINFO, &infoa, -1); if (g_str_equal (namea, "../")) { g_free (namea); return (order) ? -1 : 1; } gtk_tree_model_get (model, b, FILENAME, &nameb, FINFO, &infob, -1); if (g_str_equal (nameb, "../")) { g_free (namea); g_free (nameb); return (order) ? 1 : -1; } gboolean bf = e2_fs_is_dir_fast (nameb); g_free (nameb); if (e2_fs_is_dir_fast (namea)) { g_free (namea); if (bf) return (infoa->statbuf.st_mode - infob->statbuf.st_mode); else return (order) ? -1 : 1; } g_free (namea); if (bf) return (order) ? 1 : -1; return (infoa->statbuf.st_mode - infob->statbuf.st_mode); } /** @brief item-owner sort-order comparison function Directories are placed before other things. @param model the data model to be interrogated @param a pointer to model iter for the first row to be compared @param b pointer to model iter for the second row to be compared @param direction pointer to the views's sort direction @return integer <0, 0 or >0 according to whether a belongs before, = or after b */ static gint _e2_fileview_user_sort (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction) { gchar *namea, *nameb; FileInfo *infoa, *infob; gboolean order = (*direction == GTK_SORT_ASCENDING); //TRUE for ascending, FALSE for descending gtk_tree_model_get (model, a, FILENAME, &namea, FINFO, &infoa, -1); if (g_str_equal (namea, "../")) { g_free (namea); return (order) ? -1 : 1; } gtk_tree_model_get (model, b, FILENAME, &nameb, FINFO, &infob, -1); if (g_str_equal (nameb, "../")) { g_free (namea); g_free (nameb); return (order) ? 1 : -1; } gboolean bf = e2_fs_is_dir_fast (nameb); g_free (nameb); if (e2_fs_is_dir_fast (namea)) { g_free (namea); if (bf) return (infoa->statbuf.st_uid - infob->statbuf.st_uid); else return (order) ? -1 : 1; } g_free (namea); if (bf) return (order) ? 1 : -1; return (infoa->statbuf.st_uid - infob->statbuf.st_uid); } /** @brief item-group sort-order comparison function Directories are placed before other things. @param model the data model to be interrogated @param a pointer to model iter for the first row to be compared @param b pointer to model iter for the second row to be compared @param direction pointer to the views's sort direction @return integer <0, 0 or >0 according to whether a belongs before, = or after b */ static gint _e2_fileview_group_sort (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction) { gchar *namea, *nameb; FileInfo *infoa, *infob; gboolean order = (*direction == GTK_SORT_ASCENDING); //TRUE for ascending, FALSE for descending gtk_tree_model_get (model, a, FILENAME, &namea, FINFO, &infoa, -1); if (g_str_equal (namea, "../")) { g_free (namea); return (order) ? -1 : 1; } gtk_tree_model_get (model, b, FILENAME, &nameb, FINFO, &infob, -1); if (g_str_equal (nameb, "../")) { g_free (namea); g_free (nameb); return (order) ? 1 : -1; } gboolean bf = e2_fs_is_dir_fast (nameb); g_free (nameb); if (e2_fs_is_dir_fast (namea)) { g_free (namea); if (bf) return (infoa->statbuf.st_gid - infob->statbuf.st_gid); else return (order) ? -1 : 1; } g_free (namea); if (bf) return (order) ? 1 : -1; return (infoa->statbuf.st_gid - infob->statbuf.st_gid); } /** @brief item-acess-date sort-order comparison function Directories are placed before other things. @param model the data model to be interrogated @param a pointer to model iter for the first row to be compared @param b pointer to model iter for the second row to be compared @param direction pointer to the views's sort direction @return integer <0, 0 or >0 according to whether a belongs before, = or after b */ static gint _e2_fileview_adate_sort (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction) { gchar *namea, *nameb; FileInfo *infoa, *infob; gboolean order = (*direction == GTK_SORT_ASCENDING); //TRUE for ascending, FALSE for descending gtk_tree_model_get (model, a, FILENAME, &namea, FINFO, &infoa, -1); if (g_str_equal (namea, "../")) { g_free (namea); return (order) ? -1 : 1; } gtk_tree_model_get (model, b, FILENAME, &nameb, FINFO, &infob, -1); if (g_str_equal (nameb, "../")) { g_free (namea); g_free (nameb); return (order) ? 1 : -1; } gboolean bf = e2_fs_is_dir_fast (nameb); g_free (nameb); if (e2_fs_is_dir_fast (namea)) { g_free (namea); if (bf) return (infoa->statbuf.st_atime - infob->statbuf.st_atime); else return (order) ? -1 : 1; } g_free (namea); if (bf) return (order) ? 1 : -1; return (infoa->statbuf.st_atime - infob->statbuf.st_atime); } /** @brief item-modification-date sort-order comparison function Directories are placed before other things. @param model the data model to be interrogated @param a pointer to model iter for the first row to be compared @param b pointer to model iter for the second row to be compared @param direction pointer to the views's sort direction @return integer <0, 0 or >0 according to whether a belongs before, = or after b */ static gint _e2_fileview_mdate_sort (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction) { gchar *namea, *nameb; FileInfo *infoa, *infob; gboolean order = (*direction == GTK_SORT_ASCENDING); //TRUE for ascending, FALSE for descending gtk_tree_model_get (model, a, FILENAME, &namea, FINFO, &infoa, -1); if (g_str_equal (namea, "../")) { g_free (namea); return (order) ? -1 : 1; } gtk_tree_model_get (model, b, FILENAME, &nameb, FINFO, &infob, -1); if (g_str_equal (nameb, "../")) { g_free (namea); g_free (nameb); return (order) ? 1 : -1; } gboolean bf = e2_fs_is_dir_fast (nameb); g_free (nameb); if (e2_fs_is_dir_fast (namea)) { g_free (namea); if (bf) return (infoa->statbuf.st_mtime - infob->statbuf.st_mtime); else return (order) ? -1 : 1; } g_free (namea); if (bf) return (order) ? 1 : -1; return (infoa->statbuf.st_mtime - infob->statbuf.st_mtime); } /** @brief item-status-change-date sort-order comparison function Directories are placed before other things. @param model the data model to be interrogated @param a pointer to model iter for the first row to be compared @param b pointer to model iter for the second row to be compared @param direction pointer to the views's sort direction @return integer <0, 0 or >0 according to whether a belongs before, = or after b */ static gint _e2_fileview_cdate_sort (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction) { gchar *namea, *nameb; FileInfo *infoa, *infob; gboolean order = (*direction == GTK_SORT_ASCENDING); //TRUE for ascending, FALSE for descending gtk_tree_model_get (model, a, FILENAME, &namea, FINFO, &infoa, -1); if (g_str_equal (namea, "../")) { g_free (namea); return (order) ? -1 : 1; } gtk_tree_model_get (model, b, FILENAME, &nameb, FINFO, &infob, -1); if (g_str_equal (nameb, "../")) { g_free (namea); g_free (nameb); return (order) ? 1 : -1; } gboolean bf = e2_fs_is_dir_fast (nameb); g_free (nameb); if (e2_fs_is_dir_fast (namea)) { g_free (namea); if (bf) return (infoa->statbuf.st_ctime - infob->statbuf.st_ctime); else return (order) ? -1 : 1; } g_free (namea); if (bf) return (order) ? 1 : -1; return (infoa->statbuf.st_ctime - infob->statbuf.st_ctime); } /** @brief sort treeview for @a view by column @a colnum This supports sorting by keypress @param colnum column-enumerator, FILENAME...CHANGED @param view data structure for the pane @return */ gboolean e2_fileview_sort_column (gint colnum, ViewInfo *view) { if (colnum < FILENAME || colnum > CHANGED) return FALSE; GList *cols = gtk_tree_view_get_columns (GTK_TREE_VIEW (view->treeview)); GtkTreeViewColumn *col = g_list_nth_data (cols, colnum); g_list_free (cols); gboolean visible; g_object_get (G_OBJECT (col), "visible", &visible, NULL); if (!visible) return FALSE; gint old_sortcol; GtkSortType old_order; gboolean toggle = TRUE; GtkTreeSortable *sortable = GTK_TREE_SORTABLE (view->store); gtk_tree_sortable_get_sort_column_id (sortable, &old_sortcol, &old_order); if (colnum == FILENAME) { //only do normal name-sorting if (view->extsort) { //counter the effects of the normal toggle if (old_sortcol == colnum) toggle = FALSE; view->extsort = FALSE; gtk_tree_sortable_set_sort_column_id (sortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING); gtk_tree_sortable_set_sort_func (sortable, FILENAME, e2_all_columns[FILENAME].sort_func, &view->sort_order, NULL); } } if (old_sortcol == colnum) { //column is already sorted if (toggle) view->sort_order = (old_order == GTK_SORT_ASCENDING) ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING; } else { //this is a new sort column //replace sort indicator gtk_widget_hide (view->sort_arrows[old_sortcol]); gtk_widget_show (view->sort_arrows[colnum]); //1st click makes it ascending view->sort_order = GTK_SORT_ASCENDING; // order = TRUE; } //set appropriate arrow type GtkArrowType arrow = (view->sort_order == GTK_SORT_ASCENDING) ? GTK_ARROW_DOWN : GTK_ARROW_UP; gtk_arrow_set (GTK_ARROW (view->sort_arrows[colnum]), arrow, //different shadow types don't do any good, really ... // (view->extsort) ? GTK_SHADOW_ETCHED_IN : GTK_SHADOW_OUT); GTK_SHADOW_NONE); //remember the column, for cacheing view->sort_column = colnum; //do the sort gtk_tree_sortable_set_sort_column_id (sortable, colnum, view->sort_order); return TRUE; } /*****************/ /*** callbacks ***/ /*****************/ /** @brief cursor change detection This records the row to which the cursor is moved @param treeview the widget where the cursor moved @param view data structure for the pane @return FALSE, so related signals are not blocked */ static gboolean _e2_fileview_cursor_change_cb (GtkTreeView *treeview, ViewInfo *view) { printd (DEBUG, "callback: cursor changed"); GtkTreePath *path; gtk_tree_view_get_cursor (treeview, &path, NULL); if (path != NULL) //path can be invalid when last row of a filelist is deleted { view->row = *gtk_tree_path_get_indices (path); gtk_tree_path_free (path); } printd (DEBUG, "focus row is %d", view->row); return FALSE; } /** @brief column-header button-press detection This fn records which button was clicked, so that when a "release" callback occurs (which may be when a drag starts, not an actual release) we can check whether it is still pressed This is a workaround for gtk's (2.4 at least) behaviour It relies on detecting clicks on the undocumented column->button widget @param header UNUSED the col button header widget which was clicked @param event pointer to event data struct @param view UNUSED data structure for the view to which the file list belongs @return FALSE, so related signals are not blocked */ static gboolean _e2_fileview_column_header_buttonpress_cb (GtkWidget *header, GdkEventButton *event, ViewInfo *view) { printd (DEBUG, "callback: column header button press"); header_button = event->button; return FALSE; } /** @brief column-header button-release detection This checks whether a release event is real. In gtk 2.4 at least, such an event may be when a column-drag starts, not an actual release. It relies on detecting clicks on the undocumented column->button widget @param UNUSED header the col button widget which was clicked @param event pointed to event data struct @param view data structure for the view to which the file list belongs @return TRUE, if this is a 'bogus' release, else FALSE */ static gboolean _e2_fileview_column_header_buttonrel_cb (GtkWidget *header, GdkEventButton *event, ViewInfo *view) { printd (DEBUG, "callback: column header button release"); if (view != curr_view) e2_pane_activate_other (); //check for whether button is really still pressed //gtk 2.4 emits a bogus extra release signal when column-drag occurs gboolean pressed = FALSE; if (!block && event->send_event) { pressed = TRUE; block = TRUE; } else if (block) block = FALSE; //on the second pass, just unblock // else // pressed = FALSE; if (!pressed) header_button = -1; //signal to "clicked" callback not to do anything return pressed; //stop further action on this button if not released yet } /** @brief column-click callback This fn updates the view sorting, by applying an ascending sort to a column that is not sorted, or by toggling the sort direction of a column that is already sorted. Sort indicators are updated. This is called when a column header is clicked, before any associated "column-change" signal. @param col the view column which was clicked @param rt data structure for the pane to which the file list belongs @return */ //FIXME -click is not always passed through to here static void _e2_fileview_column_header_clicked_cb (GtkTreeViewColumn *col, ViewInfo *view) { if (header_button != -1) return; //the button is still down, this is drag action printd (DEBUG, "callback: column header click"); //get which column was cliicked GList *cols = gtk_tree_view_get_columns (GTK_TREE_VIEW (view->treeview)); gint clicked_col = g_list_index (cols, (gpointer) col); g_list_free (cols); //get colum-order array for the pane gint *order_array = (view == app.pane1.view) ? displayed_col_order[0] : displayed_col_order[1]; //get clicked-model-column number gint i = order_array[clicked_col]; /* if (i == 0) { //filename column DO WE WANT TO MAKE THIS DYNAMIC BUT SLOWER ?? //get a local copy of the case-sensitive flag E2_OptionSet *set = e2_option_get_simple ("namesort-case-sensitive"); case_sensitive = _e2_option_bool_get (set); } */ //get current-sorted-column details gint m; GtkSortType old_order; GtkTreeSortable *sortable = GTK_TREE_SORTABLE (view->store); gtk_tree_sortable_get_sort_column_id (sortable, &m, &old_order); gboolean toggle = TRUE; gboolean extsort = FALSE; if (i == FILENAME) { GdkModifierType mask = e2_utils_get_modifiers (); if (mask & GDK_CONTROL_MASK) { //do extension-sorting extsort = TRUE; if (!view->extsort) { //counter the effects of the normal toggle if (m == i) toggle = FALSE; view->extsort = TRUE; gtk_tree_sortable_set_sort_column_id (sortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING); gtk_tree_sortable_set_sort_func (sortable, FILENAME, (GtkTreeIterCompareFunc) e2_fileview_ext_sort, &view->sort_order, NULL); } } else { //do normal name-sorting if (view->extsort) { //counter the effects of the normal toggle if (m == i) toggle = FALSE; view->extsort = FALSE; gtk_tree_sortable_set_sort_column_id (sortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING); gtk_tree_sortable_set_sort_func (sortable, FILENAME, e2_all_columns[FILENAME].sort_func, &view->sort_order, NULL); } } } if (m == i) { //column is already sorted if (toggle) view->sort_order = (old_order == GTK_SORT_ASCENDING) ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING; } else { //this is a new sort column //replace sort indicator gtk_widget_hide (view->sort_arrows[m]); gtk_widget_show (view->sort_arrows[i]); //1st click makes it ascending view->sort_order = GTK_SORT_ASCENDING; // order = TRUE; } //set appropriate arrow type GtkArrowType arrow; if (extsort) //FIXME do a more-intuitive arrow arrow = (view->sort_order == GTK_SORT_ASCENDING) ? GTK_ARROW_RIGHT : GTK_ARROW_LEFT; else arrow = (view->sort_order == GTK_SORT_ASCENDING) ? GTK_ARROW_DOWN : GTK_ARROW_UP; gtk_arrow_set (GTK_ARROW (view->sort_arrows[i]), arrow, //different shadow types don't do any good, really ... // (view->extsort) ? GTK_SHADOW_ETCHED_IN : GTK_SHADOW_OUT); GTK_SHADOW_NONE); /* no use, gtk always returns gchar data to heap space //setup stack variable space for speedier name sorts if (i == FILENAME) { gchar namespace [2][E2_UTFNAME_MAX]; gtk_tree_sortable_set_sort_func (sortable, FILENAME, e2_all_columns[FILENAME].sort_func, (gpointer *) namespace, NULL); } */ //remember the column, for cacheing view->sort_column = i; //do the sort gtk_tree_sortable_set_sort_column_id (sortable, i, view->sort_order); gtk_widget_grab_focus (view->treeview); return; } /** @brief column-change callback This fn refreshes the columns-order array for the pane, by interating over pane columns glist, grabbing each title like "4", converting it to corresponding integer. It is called when a column is dropped to a different position and when the window is closed (also, multiple times when the window is re-created - CHECKME why) @param treeview the widget where the action occurred @param view data structure for the view @return */ static void _e2_fileview_col_change_cb (GtkTreeView *treeview, ViewInfo *view) { printd (DEBUG, "callback: column change"); gint *order_array = (view == &app.pane1_view) ? displayed_col_order[0] : displayed_col_order[1]; GList *cols = gtk_tree_view_get_columns (treeview); GList *tmp; const gchar *title; gint colnum = 0; for (tmp = cols; tmp != NULL; tmp = tmp->next) { title = gtk_tree_view_column_get_title (tmp->data); order_array[colnum++] = atoi (title); } g_list_free (cols); //update order of "slave" pane if appropriate gint *slave_array; ViewInfo *slave_view; if (e2_option_bool_get ("pane2-uses-other") && view == &app.pane1_view) { slave_array = displayed_col_order[1]; slave_view = &app.pane2_view; } else if (e2_option_bool_get ("pane1-uses-other") && view == &app.pane2_view) { slave_array = displayed_col_order[0]; slave_view = &app.pane1_view; } else return; printd (DEBUG, "slave-view column change"); g_signal_handlers_block_by_func (G_OBJECT (slave_view->treeview), _e2_fileview_col_change_cb, slave_view); cols = gtk_tree_view_get_columns (GTK_TREE_VIEW (slave_view->treeview)); for (colnum = 0; colnum < MAX_COLUMNS; colnum++) { gint current_col = order_array[colnum]; if (slave_array[colnum] != current_col) { gint j; GtkTreeViewColumn *prior_col, *moved_col; if (colnum == 0) prior_col = NULL; else prior_col = g_list_nth_data (cols, colnum-1); for (j = colnum+1; j < MAX_COLUMNS; j++) { if (slave_array[j] == current_col) { moved_col = g_list_nth_data (cols, j); gtk_tree_view_move_column_after ( GTK_TREE_VIEW (slave_view->treeview), moved_col, prior_col); //this approach seems too complex, but several simpler tries failed !! g_list_free (cols); cols = gtk_tree_view_get_columns (GTK_TREE_VIEW (slave_view->treeview)); j = colnum; for (tmp = g_list_nth (cols, j); tmp != NULL; tmp=tmp->next) { const gchar *title = gtk_tree_view_column_get_title (tmp->data); slave_array[j] = atoi(title); j++; } break; } } } } g_list_free (cols); g_signal_handlers_unblock_by_func (G_OBJECT (slave_view->treeview), _e2_fileview_col_change_cb, slave_view); } /** @brief row-activated callback Activation is triggered when is pressed or when a double-click happens This "executes" the clicked row item view->row will have been set by the button-release callback @param treeview the widget where the button was pressed @param path model path to the clicked row @param col UNUSED clicked view column @param view UNUSED rt data for the view to be worked on @return */ static void _e2_fileview_row_activated_cb ( GtkTreeView *treeview, GtkTreePath *tpath, GtkTreeViewColumn *col, ViewInfo *view) { printd (DEBUG, "callback: _e2_fileview_row_activated"); GtkTreeIter iter; GtkTreeModel *model = gtk_tree_view_get_model (treeview); if (gtk_tree_model_get_iter (model, &iter, tpath)) { gchar *localpath; FileInfo *info; gtk_tree_model_get (model, &iter, FINFO, &info, -1); //this may append "..", which will be parsed in the cd process localpath = e2_utils_dircat (view, info->filename, TRUE); #ifdef E2_VFS VPATH sdata = { localpath, view->spacedata }; e2_task_backend_open (&sdata, TRUE); #else e2_task_backend_open (localpath, TRUE); #endif g_free (localpath); } } /** @brief mouse button press callback This makes the clicked view active, if it wasn't already. A button-1 press when there is nothing else selected logs the clicked model row, if any, in case it's a drag start. (For anything other than a drag, the row is logged whem the cursor moves.) The gtk handler will select the row. Double-clicks are detected (based on cb sequence) here, to work around gtk's behaviour when its handler is aborted after a single click A button-2 press does an updir, if that option is in force, or else logs the clicked model row, if any. A button-3 press sets up the relevant context menu, and may select the line or unselect everything, depending on options in force @param treeview the widget where the button was pressed @param event gdk event data @param view rt data for the view to be worked on @return TRUE (stop other handlers) for btn 2 (updir) or btn 3 press, else FALSE (allow other handlers) */ static gboolean _e2_fileview_button_press_cb (GtkWidget *treeview, GdkEventButton *event, ViewInfo *view) { printd (DEBUG, "callback: mouse button %d press event type %d", event->button, event->type); //check that we're in the actual treeview (not its header) by making //sure that the window is correct GdkWindow *window = gtk_tree_view_get_bin_window (GTK_TREE_VIEW (treeview)); if (window != event->window) return FALSE; //make the clicked pane active, if it wasn't already if (view != curr_view) // && !panechange_started) e2_pane_activate_other (); GtkTreePath *path; GtkTreeSelection *sel; #ifdef E2_ALTLEFTMOUSE // gboolean oldleft = left_pressed; //set flag to assist processing any drag left_pressed = (event->button == 1); //&& event->type == GDK_BUTTON_PRESS); if (left_pressed //we don't handle any 'modified' button-press (so that range-selection works as normal) //or a click within the specified double-click interval from the prior click && !(event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK)) ) { //event-time for detecting double-clicks /* because we block returns for selected items, double-clicks have to be detected and processed here, too instead of click-interval, test now relies on the normal cb sequence for doubles (pr, rl, pr, pr, rl) i.e. a press without an intervening release is a double (or triple!) static guint32 last_event_time = 0; guint32 interval = event->time - last_event_time; last_event_time = event->time; if (interval >= click_interval) //time-based check for double-click (the 3rd pr will pass this test too) { */ /*if we've passed all the tests, and a selected item is clicked, we block gtk's deselection of other items until the button is released. Then, we'll know how to treat it*/ if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (treeview), event->x, event->y, &path, NULL, NULL, NULL)) { // sel = view->selection; if (gtk_tree_selection_path_is_selected (view->selection, path)) { //sometimes the focus doesn't get transferred properly by gtk, so ... gtk_widget_grab_focus (view->treeview); // if (oldleft) //this is a fake press, generated as part of a double-click if (event->type == GDK_2BUTTON_PRESS) { printd (DEBUG, "intercepted mouse double-click"); //process it as a double-click _e2_fileview_row_activated_cb (GTK_TREE_VIEW (treeview), path, NULL, view); } gtk_tree_path_free (path); //note that returning TRUE blocks some normal treeview behaviour //like refocussing a selected item and repeated double-clicks //and allowing in-place editing /*annoying, interferes with double-clicks #ifdef EDIT_INPLACE return (event->type != GDK_BUTTON_PRESS && gtk_tree_selection_count_selected_rows (view->selection) == 1); #else */ return TRUE; //wait until release, to see what to do with the selected row //#endif } gtk_tree_path_free (path); } // } } else if (left_pressed && event->type != GDK_BUTTON_PRESS) return TRUE; //kill left multi-clicks with mod key(s) else #endif /* if (event->button == 1 && (event->type == GDK_2BUTTON_PRESS || event->type == GDK_3BUTTON_PRESS)) ) { //double or triple left-click } else */ if (event->button == 2) return TRUE; //wait until release, to see whether to select the row else if (event->button == 3) { if (e2_option_bool_get ("windows-right-click") // && (view->tagged == NULL) ) { //if windows right click is enabled, we want to also select the item, //if not already done, and clear any other selected item(s) unless this //one is selected already sel = view->selection; if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (treeview), event->x, event->y, &path, NULL, NULL, NULL)) { if (!gtk_tree_selection_path_is_selected (sel, path)) gtk_tree_selection_unselect_all (sel); gtk_tree_selection_select_path (sel, path); //do this now because menu created before cursor-move cb view->row = *gtk_tree_path_get_indices (path); gtk_tree_path_free (path); } else if (! e2_option_bool_get ("windows-right-click-extra")) gtk_tree_selection_unselect_all (sel); } //context menu gint type = 0; guint modifiers = gtk_accelerator_get_default_mod_mask (); modifiers &= event->state; if (modifiers == GDK_SHIFT_MASK) type = 1; else if (modifiers == GDK_CONTROL_MASK) type = 2; else if (modifiers == (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) type = 3; e2_context_menu_show (event->button, event->time, type); return TRUE; } return FALSE; } /** @brief mouse button release callback This checks if the click was a left click. If so, and it's not the end of a drag, or an 'un-select' click, the clicked row is logged. Detection of the end of a drag relies on an undocumented check whether the release happened in the same pane as was clicked. @param treeview UNUSED where the button was _pressed_ @param event @param view rt data for the view where the button was _pressed_ @return TRUE (stop other handlers) if updir initiated, else FALSE (allow other handlers) */ static gboolean _e2_fileview_button_release_cb (GObject *treeview, GdkEventButton *event, ViewInfo *view) { printd (DEBUG, "callback: button release"); //check that we're in the actual treeview (not its header) by making //sure that the window is correct //(this test will also fail at the end of a drag to the other pane) GdkWindow *window = gtk_tree_view_get_bin_window (GTK_TREE_VIEW (treeview)); if (window != event->window) return FALSE; //for releases, there is no distinction between 1- 2- or 3- buttons #ifdef E2_ALTLEFTMOUSE if (event->button == 1) { left_pressed = FALSE; //we ignore releases when a modifier is pressed, //and normal-drag ends, // i.e. event->send_event seems to be FALSE (ie the event was not sent explicitly) //and drag-select ends if (!(event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK)) && event->send_event == 0 && !drag_sel_now) { /*there was no drag-selection, or the drag did not cover anything but the clicked item we want an outcome same as from a normal button-press i.e. select just the clicked row and log it, if any */ GtkTreePath *path; if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (view->treeview), event->x, event->y, &path, NULL, NULL, NULL)) { //we're releasing over a treeview-item (which must have been selected) GtkTreeSelection *sel = view->selection; if (gtk_tree_selection_count_selected_rows (sel) > 1) { gtk_tree_selection_unselect_all (sel); gtk_tree_selection_select_path (sel, path); //record the row as the last-selected one // view->row = *gtk_tree_path_get_indices (path);NOW IN CURSOR MOVE CB } gtk_tree_path_free (path); } } } else #endif if (event->button == 2) { // printd (DEBUG, "callback: mouse button-2 release"); //correctly distinguish drag releases, and normal clicks //event->send_event seems to be FALSE (ie the event was not sent explicitly) //for releases in the same pane as clicked?? if (event->send_event == 0) { //not a drag release if (button2updir) { printd (DEBUG, "up"); E2_PaneRuntime *rt = (view == curr_view) ? curr_pane : other_pane; e2_pane_change_dir (rt, ".."); return TRUE; } //otherwise, maybe log the row //don't bother with modifers-check in this context // guint modifiers = gtk_accelerator_get_default_mod_mask (); // if ((event->state & modifiers) != GDK_CONTROL_MASK) if (!(event->state & GDK_CONTROL_MASK)) { //not an "unselect" click GtkTreePath *path; if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (view->treeview), event->x, event->y, &path, NULL, NULL, NULL)) { // row was NOT selected when the mouse was clicked // GtkTreeSelection *sel = view->selection; gtk_tree_selection_select_path (view->selection, path); //record the row as the last-selected one // view->row = *gtk_tree_path_get_indices (path);NOW IN CURSOR MOVE CB gtk_tree_path_free (path); } } } else //set signal for DnD handler btn2_released = TRUE; } else btn2_released = FALSE; return FALSE; } /** @brief set popup menu position This function is supplied when calling gtk_menu_popup(), to position the displayed menu. set @a push_in to TRUE for menu completely inside the screen, FALSE for menu clamped to screen size @param menu UNUSED the GtkMenu to be positioned @param x place to store gint representing the menu left @param y place to store gint representing the menu top @param push_in place to store pushin flag @param treeview in focus when the menu key was pressed @return */ void e2_fileview_set_menu_position (GtkMenu *menu, gint *x, gint *y, gboolean *push_in, GtkWidget *treeview) { gint left, top; gtk_window_get_position (GTK_WINDOW (app.main_window), &left, &top); GtkAllocation alloc = treeview->allocation; *x = left + alloc.x + alloc.width/2; *y = top + alloc.y +alloc.height/2 - 30; *push_in = FALSE; } /** @brief menu button callback (does not apply for 'modified' menu button) Sets up the relevant context menu @param treeview UNUSED the widget in focus when the action was initiated @param view UNUSED rt data for the view to be worked on @return TRUE always */ /*static gboolean _e2_fileview_popup_cb (GtkWidget *treeview, ViewInfo *view) { gint menu_type = 0; //default to 'normal' context menu //FIXME this check irrelevant, as cb never used for menu btn + Ctrl/Shift / * GdkModifierType state; if (gtk_get_current_event_state (&state)) { if ((state & GDK_SHIFT_MASK) && (state & GDK_CONTROL_MASK)) menu_type = 3; else if (state & GDK_SHIFT_MASK) menu_type = 1; else if (state & GDK_CONTROL_MASK) menu_type = 2; } * / gint event_time = gtk_get_current_event_time (); //button '0' signals this is not a mouse-button click e2_context_menu_show (0, event_time, menu_type); return TRUE; } */ /** @brief treeview key-press callback @param widget UNUSED the focused treeview widget when the key was pressed @param event pointer to event data struct @param view rt data for the view to be worked on @return TRUE when we handle a 'vi key' */ static gboolean _e2_fileview_key_press_cb (GtkWidget *widget, GdkEventKey *event, ViewInfo *view) { if (event->keyval == GDK_Menu) { gint menu_type; switch (event->state & gtk_accelerator_get_default_mod_mask ()) { default: menu_type = 0; //'normal' context menu break; case GDK_SHIFT_MASK: menu_type = 1; break; case GDK_CONTROL_MASK: menu_type = 2; break; // is not a valid modifier for mouse right-click, so we also ignore //that for the menu-button case (GDK_SHIFT_MASK | GDK_CONTROL_MASK): menu_type = 3; break; } //button 0 signals this is not a mouse-button click e2_context_menu_show (0, event->time, menu_type); return TRUE; } return FALSE; } #ifdef EDIT_INPLACE /** @brief disable filelist refreshing during a filename edit @param renderer the renderer for the cell @param editable the interface to @a renderer @param path_string string form of gtk tree path to the row to be amended @param view pointer to view data struct @return */ static void _e2_fileview_edit_start_cb (GtkCellRenderer *renderer, GtkCellEditable *editable, gchar *path_string, ViewInfo *view) { printd (DEBUG, "start cell edit cb"); e2_filelist_disable_one_refresh ((view == curr_view) ? PANEACTIVE : PANEINACTIVE); //disable keypressed that will interfere g_signal_handlers_block_by_func (G_OBJECT (view->treeview), _e2_fileview_key_press_cb, view); } /** @brief enable filelist refreshing after an aborted filename edit @param renderer the renderer for the cell @param view pointer to view data struct @return */ static void _e2_fileview_edit_cancel_cb (GtkCellRenderer *renderer, ViewInfo *view) { printd (DEBUG, "cancel cell edit cb"); g_signal_handlers_unblock_by_func (G_OBJECT (view->treeview), _e2_fileview_key_press_cb, view); e2_filelist_enable_one_refresh ((view == curr_view) ? PANEACTIVE : PANEINACTIVE); } /** @brief save edited text value in the underlying treestore @param renderer the renderer for the cell @param path_string string form of gtk tree path to the row to be amended @param new_text replacement text string for the cell @param view pointer to view data struct @return */ static void _e2_fileview_name_edited_cb (GtkCellRendererText *cell, gchar *path_string, gchar *new_text, ViewInfo *view) { if (new_text != NULL) //probably always TRUE { printd (DEBUG, "filename edited cb, new text is %s", new_text); GtkTreeIter iter; if (gtk_tree_model_get_iter_from_string (view->model, &iter, path_string)) { gchar *oldname; gtk_tree_model_get (view->model, &iter, FILENAME, &oldname, -1); if (!( g_str_equal (oldname, new_text) || g_str_equal (oldname, G_DIR_SEPARATOR_S) //some things can't be changed || g_str_equal (oldname, "../"))) { gchar *old, *new, *utfold, *utfnew, *replacement; DialogButtons choice; gint len; gboolean isdir, success; len = strlen (oldname); isdir = (*(oldname + len - sizeof (gchar)) == G_DIR_SEPARATOR); //ascii check ok if (isdir) *(oldname + len - sizeof (gchar)) = '\0'; //strip trailer utfold = e2_utils_dircat (view, oldname, FALSE); old = F_FILENAME_TO_LOCALE (utfold); utfnew = e2_utils_dircat (view, new_text, FALSE); new = F_FILENAME_TO_LOCALE (utfnew); len = strlen (new); if (*(new + len - sizeof (gchar)) == G_DIR_SEPARATOR) //ascii check ok *(new + len - sizeof (gchar)) = '\0'; //strip trailer if (e2_option_bool_get ("confirm-overwrite") && !e2_fs_access2 (new E2_ERR_NONE())) { e2_filelist_enable_one_refresh ((view == curr_view) ? PANEACTIVE : PANEINACTIVE); choice = e2_dialog_ow_check (NULL, new, NONE); e2_filelist_disable_one_refresh ((view == curr_view) ? PANEACTIVE : PANEINACTIVE); } else choice = OK; if (choice == OK) { gdk_threads_leave (); //CHECKME what if a task-Q is is progress ? success = e2_task_backend_rename (old, new); gdk_threads_enter (); if (success) { if (isdir && !g_str_has_suffix (new_text, G_DIR_SEPARATOR_S)) replacement = g_strconcat (new_text, G_DIR_SEPARATOR_S, NULL); else if (!isdir && g_str_has_suffix (new_text, G_DIR_SEPARATOR_S)) { replacement = g_strdup (new_text); *(replacement + strlen (replacement) - sizeof (gchar)) = '\0'; } else replacement = (gchar *) new_text; GtkTreeIter child; gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (view->model), &child, &iter); gtk_list_store_set (view->store, &child, FILENAME, replacement, -1); if (replacement != new_text) g_free (replacement); } //editing works only on unselected rows and anyway, //doesn't stay selected after the next refresh // gtk_tree_selection_select_iter (view->selection, &iter); // if (success) // e2_filelist_request_refresh (view->dir, TRUE); CHECKME ok without E2_FAM ? } F_FREE (old); F_FREE (new); g_free (utfold); g_free (utfnew); } g_free (oldname); } } g_signal_handlers_unblock_by_func (G_OBJECT (view->treeview), _e2_fileview_key_press_cb, view); e2_filelist_enable_one_refresh ((view == curr_view) ? PANEACTIVE : PANEINACTIVE); } #endif //def EDIT_INPLACE /* * @brief timer callback function which checks whether ok to proceed with a cd @param busy flag to check @return FALSE when ready to proceed */ /*static gboolean _e2_fileview_check_completion (gboolean *busy) { if (*busy) return TRUE; gtk_main_quit (); app.timers[REFRESHWAIT_T] = 0; return FALSE; } */ // some filter menu callbacks (others are in filter-dialog files) void e2_fileview_filter_dirs_cb (GtkWidget *widget, ViewInfo *view) { //toggle cached flag to match the menu check button // view->filter_directories = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget)); view->filter_directories = !view->filter_directories; e2_fileview_refilter_list (view); } void e2_fileview_remove_filters_cb (GtkWidget *widget, ViewInfo *view) { // turn off local flags view->name_filter.active = FALSE; view->size_filter.active = FALSE; view->date_filter.active = FALSE; view->filter_directories = FALSE; //show the results e2_toolbar_toggle_filter_button (view); e2_fileview_refilter_list (view); } /** @brief initialise file filter flags to show everything @param view data structure for view being processed @return */ /*void e2_fileview_initialize_filters (ViewInfo *view) { view->filter_directories = FALSE; view->name_filter.active = FALSE; g_strlcpy (view->name_filter.pattern, "*", sizeof(view->name_filter.pattern)); view->name_filter.case_sensitive = TRUE; view->size_filter.active = FALSE; view->size_filter.size = 0; view->size_filter.op = GT; view->date_filter.active = FALSE; view->date_filter.time = time(NULL); view->date_filter.time_type = ATIME; view->date_filter.op = GT; } */ /** @brief clear filter patterns for view associated with @a view @param view rt data for the view, assigned when function was initiated @return */ static void _e2_fileview_clear_filter_patterns (ViewInfo *view) { E2_SelectPattern *patterninfo; GSList *member; if (view->name_filter.compiled_patterns == NULL) return; for (member = view->name_filter.compiled_patterns; member != NULL; member = member->next) { patterninfo = (E2_SelectPattern *)member->data; g_pattern_spec_free (patterninfo->pspec); DEMALLOCATE (E2_SelectPattern, patterninfo); } g_slist_free (view->name_filter.compiled_patterns); view->name_filter.compiled_patterns = NULL; } /** @brief compile filter patterns for view associated with @a view @param view rt data for the view, assigned when function was initiated @return TRUE if the process was completed successfully */ static gboolean _e2_fileview_compile_filter_patterns (ViewInfo *view) { if (view->name_filter.compiled_patterns != NULL) _e2_fileview_clear_filter_patterns (view); E2_SelectPattern *patterninfo; GSList *members = NULL; gchar *s, *p, *freeme; gchar save; p = view->name_filter.patternptr; //maybe several patterns, separated by commas while ((s = strchr (p, ',')) != NULL) //if always ascii ',', don't need g_utf8_strchr() { //check each pattern that is followed by a ',' while (p[0] == ' ') //no \t check p++; if (p == s) { //empty pattern p++; continue; } patterninfo = MALLOCATE (E2_SelectPattern); //too small for slice CHECKALLOCATEDWARN (patterninfo, ) if (patterninfo == NULL) { g_slist_free (members); //improper cleanup, too bad return FALSE; } members = g_slist_append (members, patterninfo); if (p[0] == '!') { patterninfo->negated = !view->name_filter.invert_mask; p++; } else { patterninfo->negated = view->name_filter.invert_mask; if (p[0] == '\\' && p[1] == '!') p++; } save = *s; *s = '\0'; if (!view->name_filter.case_sensitive) { freeme = g_utf8_strdown (p, -1); patterninfo->pspec = g_pattern_spec_new (freeme); g_free (freeme); } else patterninfo->pspec = g_pattern_spec_new (p); *s = save; NCHR(s); p = s; } //now the last (or only) pattern while (p[0] == ' ') p++; if (p[0] == '\0') { view->name_filter.compiled_patterns = members; return (members != NULL); } patterninfo = MALLOCATE (E2_SelectPattern); //too small for slice CHECKALLOCATEDWARN (patterninfo, ) if (patterninfo == NULL) { g_slist_free (members); //improper cleanup, too bad return FALSE; } members = g_slist_append (members, patterninfo); if (p[0] == '!') { patterninfo->negated = !view->name_filter.invert_mask; p++; } else { patterninfo->negated = view->name_filter.invert_mask; if (p[0] == '\\' && p[1] == '!') p++; } if (!view->name_filter.case_sensitive) { freeme = g_utf8_strdown (p, -1); patterninfo->pspec = g_pattern_spec_new (freeme); g_free (freeme); } else patterninfo->pspec = g_pattern_spec_new (p); view->name_filter.compiled_patterns = members; return TRUE; } static gchar *name_pattern = NULL; static gboolean hide_updir = FALSE; //for speed, set in refilter-list func /** @brief treeview row visibility function @param model the child model of the GtkTreeModelFilter @param iter pointer to a GtkTreeIter for the row in @a model whose visibility is to be determined @param view rt data for the view, assigned when function was initiated @return TRUE if the row is visible */ static gboolean _e2_fileview_filter_check (GtkTreeModel *model, GtkTreeIter *iter, ViewInfo *view) { FileInfo *info; gtk_tree_model_get (model, iter, FINFO, &info, -1); gchar *name = info->filename; if (ITEM_ISHIDDEN (name) && ( //name[1] == '\0' || filtered out when dir read (!view->show_hidden && name[1] != '.') // || (name[1] == '.' && name[2] == '\0' && (!e2_option_bool_get ("show-updir-entry") //FIXME faster || (name[1] == '.' && name[2] == '\0' && (hide_updir || (view->dir[0] == G_DIR_SEPARATOR && view->dir[1] == '\0')) //never show updir in root directory ) ) ) #ifdef E2_VFSTMP //FIXME show updir in root of archive etc v-dirs #endif return FALSE; if (!view->filter_directories && e2_fs_is_dir (info, view)) return TRUE; if (view->name_filter.active) { #if 0 gchar *s, *p, *utf, *freeme; gchar save; gboolean negated, matched, positive_check = FALSE, result = FALSE; p = name_pattern; utf = F_FILENAME_FROM_LOCALE (info->filename); //not DISPLAYNAME //maybe several patterns, separated by commas while ((s = strchr (p, ',')) != NULL) //if always ascii ',', don't need g_utf8_strchr() { //check each pattern that is followed by a ',' save = *s; *s = '\0'; while (p[0] == ' ') p++; if (p[0] == '!') { negated = !view->name_filter.invert_mask; p++; } else { negated = view->name_filter.invert_mask; if (p[0] == '\\' && p[1] == '!') p++; } if (!positive_check) positive_check = !negated; if (!view->name_filter.case_sensitive) { freeme = g_utf8_strdown (utf, -1); matched = g_pattern_match_simple (p, freeme); g_free (freeme); } else matched = g_pattern_match_simple (p, utf); *s = save; if (matched && negated) { F_FREE (utf); return FALSE; } if (matched && !negated) result = TRUE; //but keep looking for any later exclude //if neither negated nor matched, we don't change result NCHR(s); //in case the matched char is bigger than 1 byte p = s; } //check the last (or only) pattern while (p[0] == ' ') p++; if (p[0] != '\0') { if (p[0] == '!') { negated = !view->name_filter.invert_mask; p++; } else { negated = view->name_filter.invert_mask; if (p[0] == '\\' && p[1] == '!') p++; } if (!positive_check) positive_check = !negated; if (!view->name_filter.case_sensitive) { freeme = g_utf8_strdown (utf, -1); matched = g_pattern_match_simple (p, freeme); g_free (freeme); } else matched = g_pattern_match_simple (p, utf); if (matched) result = !negated; //extra check for unmatched final check else if (negated && !positive_check) result = TRUE; } F_FREE (utf); if (!result) return FALSE; #else //ndef 0 //hidden updir's are already refused, above if (name[0] != '.' || name[1] != '.' || name[2] != '\0') { GSList *member; E2_SelectPattern *patterninfo; gboolean negated = FALSE, matched = FALSE, positive_check = FALSE, result = FALSE; gchar *freeme, *utf = F_FILENAME_FROM_LOCALE (info->filename); //not DISPLAYNAME for (member = view->name_filter.compiled_patterns; member != NULL; member= member->next) { patterninfo = (E2_SelectPattern *)member->data; negated = patterninfo->negated; if (!positive_check) positive_check = !negated; if (!view->name_filter.case_sensitive) { freeme = g_utf8_strdown (utf, -1); matched = g_pattern_match_string (patterninfo->pspec, freeme); g_free (freeme); } else matched = g_pattern_match_string (patterninfo->pspec, utf); if (matched && negated) { F_FREE (utf); return FALSE; } if (matched && !negated) result = TRUE; //but keep looking for any later exclude //if neither negated nor matched, we don't change result } F_FREE (utf); //extra check for unmatched final check if (!matched && negated && !positive_check) result = TRUE; if (!result) return FALSE; } #endif //def 0 } if (view->size_filter.active) { switch (view->size_filter.op) { case LT: if (info->statbuf.st_size >= view->size_filter.size) return FALSE; break; case EQ: if (info->statbuf.st_size != view->size_filter.size) return FALSE; break; default: // case GT: if (info->statbuf.st_size <= view->size_filter.size) return FALSE; break; } } if (view->date_filter.active) { switch (view->date_filter.time_type) { case ATIME: if (view->date_filter.op == GT) { if (difftime (view->date_filter.time, info->statbuf.st_atime) > 0) return FALSE; } else if (difftime (view->date_filter.time, info->statbuf.st_atime) <= 0) return FALSE; break; case CTIME: if (view->date_filter.op == GT) { if (difftime (view->date_filter.time, info->statbuf.st_ctime) > 0) return FALSE; } else if (difftime (view->date_filter.time, info->statbuf.st_ctime) <= 0) return FALSE; break; default: // case MTIME: if (view->date_filter.op == GT) { if (difftime (view->date_filter.time, info->statbuf.st_mtime) > 0) return FALSE; } else if (difftime (view->date_filter.time, info->statbuf.st_mtime) <= 0) return FALSE; break; } } return TRUE; } /** @brief treeview row visibility function for unfiltered view Assumes "." entry is filtered out when dir data is read @param model the child model of the GtkTreeModelFilter @param iter pointer to a GtkTreeIter for the row in @a model whose visibility is to be determined @param viewdir ptr to view->dir @return TRUE unless the row is ".." in the root directory */ static gboolean _e2_fileview_filter_none (GtkTreeModel *model, GtkTreeIter *iter, gchar *viewdir) { #ifdef E2_VFSTMP //FIXME show updir from root of archive etc v-dirs #endif //do fastest checks in order, to filter updir item from root dir if (viewdir[1] == '\0' && viewdir[0] == G_DIR_SEPARATOR) //ascii ok { FileInfo *info; gtk_tree_model_get (model, iter, FINFO, &info, -1); if (info->filename[2] == '\0' && info->filename[1] == '.' && ITEM_ISHIDDEN(info->filename)) return FALSE; } return TRUE; } /** @brief update filters for treeview This changes model, and attaches view to new model Assumes view attached to a model (store ?) When new store is created, view->filtered_before will be false @param view rt data for the view @return */ void e2_fileview_refilter_list (ViewInfo *view) { hide_updir = !e2_option_bool_get ("show-updir-entry"); //save, for speedier filtering gboolean filtered_now = ( view->name_filter.active || view->size_filter.active || view->date_filter.active || !view->show_hidden || hide_updir ); if (filtered_now) { //setup for filter func if (view->name_filter.active) { _e2_fileview_compile_filter_patterns (view); if (!view->name_filter.case_sensitive) name_pattern = g_utf8_strdown (view->name_filter.patternptr, -1); else name_pattern = g_strdup (view->name_filter.patternptr); } if (!view->filtered_before) { //gtk (2.6 at least) won't allow a changed visibility function, //so have to replace the filtermodel to make such change //this combination of changes has been confirmed to 0-ref old //filtermodel, and leave all other relevant refcounts = 1 view->model = gtk_tree_model_filter_new (GTK_TREE_MODEL (view->store), NULL); gtk_tree_view_set_model (GTK_TREE_VIEW (view->treeview), view->model); g_object_unref (G_OBJECT (view->model)); gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (view->model), (GtkTreeModelFilterVisibleFunc) _e2_fileview_filter_check, view, NULL); } gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (view->model)); if (view->name_filter.active) { _e2_fileview_clear_filter_patterns (view); g_free (name_pattern); } } else //if (view->filtered_before) { //de-filter or apply unfiltered visibility func view->model = gtk_tree_model_filter_new (GTK_TREE_MODEL (view->store), NULL); gtk_tree_view_set_model (GTK_TREE_VIEW (view->treeview), view->model); g_object_unref (G_OBJECT (view->model)); #ifdef E2_VFSTMP //FIXME for v-dir, display ".." in root of archive etc, needs view ptr #else gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (view->model), (GtkTreeModelFilterVisibleFunc) _e2_fileview_filter_none, view->dir, NULL); #endif gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (view->model)); } if (view->row > 0) { guint visible_rows = gtk_tree_model_iter_n_children (view->model, NULL); if (view->row > visible_rows - 1) view->row = visible_rows - 1; //if there was no selection, or the former selection is gone (after filter or deletion) //we want relative cursor-moves to stay in the same vicinity if (gtk_tree_selection_count_selected_rows (view->selection) == 0) { GtkTreePath *tpath = gtk_tree_path_new_from_indices (view->row, -1); //this "marks" the item, but mere selection is not enough for this to work gtk_tree_view_set_cursor (GTK_TREE_VIEW (view->treeview), tpath, NULL, FALSE); gtk_tree_selection_unselect_path (view->selection, tpath); gtk_tree_path_free (tpath); } } view->filtered_before = filtered_now; } /** @brief decide whether type-ahead search matches a row @param model the treemodel being searched @param column UNUSED the search column (= FILENAME) @param key the aggregate string of keys sofar pressed @param iter an iter pointing the row of @a model that should be compared with @a key @param view pointer to data struct for the view @return FALSE when a match is found */ static gboolean _e2_fileview_match_filename (GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter, ViewInfo *view) { gchar *itemname; gtk_tree_model_get (model, iter, FILENAME, &itemname, -1); gboolean match = g_str_has_prefix (itemname, key); if (match) { //we have a match GtkTreePath *path = gtk_tree_model_get_path (model, iter); view->row = *gtk_tree_path_get_indices (path); if (view->row > 0) view->row++; //CHECKME why is this needed .. //these selection-flags probably ineffective e2_fileview_focus_row (view, view->row, FALSE, FALSE, FALSE, TRUE); gtk_tree_path_free (path); } g_free (itemname); return !match; } /* * @brief UNUSED find filename begining with specified char, in current view @param ch character at the start of the name @return the row number where the matching name was found, or -1 if not found */ /*static gint e2_fileview_find_filename_begining_with (gchar ch) { gint i; //number of rows in the model gint n = gtk_tree_model_iter_n_children (curr_view->model, NULL); GtkTreeIter iter; GtkTreeModel *model = curr_view->model; gchar *name; // search from after the current row to the end of the store for (i = curr_view->row+1; i < n; i++) { if (! gtk_tree_model_iter_nth_child (model, &iter, NULL, i)) continue; //the specified row doesn't exist any more gtk_tree_model_get (model, &iter, FILENAME, &name, -1); if (name[0] == ch) { g_free (name); return i; } else g_free (name); } // search from the first row to the current row for (i = 0; i < curr_view->row; i++) { if (! gtk_tree_model_iter_nth_child (model, &iter, NULL, i)) continue; //the specified row doesn't exist any more gtk_tree_model_get (model, &iter, FILENAME, &name, -1); if (name[0] == ch) { g_free (name); return i; } else g_free (name); } return -1; } */ // Public functions /** @brief display and optionally select a numbered row The treeview is scrolled, and the cursor is moved, whether or not the focused row is selected Of course, the focused row may be selected by gtk, independently of @a select_row, and current selection will probably be cleared by gtk, independently of @a clear_selection This assumes that BGL is closed @param view rt data for ther view to be worked on @param row treeview index of the row that is to be focused @param select_row TRUE if the focused row needs to be selected @param clear_selection TRUE if whole selection for the view is to be un-selected first @param center TRUE to place the focused row near the center of the view window @param grab_focus TRUE if the treeview is to be given the focus @return */ void e2_fileview_focus_row (ViewInfo *view, gint row, gboolean select_row, gboolean clear_selection, gboolean center, gboolean grab_focus) { if (view != NULL) { GtkTreeIter iter; GtkTreeModel *model = view->model; //gtk_tree_view_get_model (GTK_TREE_VIEW (view->treeview)); if (! gtk_tree_model_iter_nth_child (model, &iter, NULL, row)) return; //the specified row doesn't exist GtkTreeView *tvw = GTK_TREE_VIEW (view->treeview); GtkTreePath *path = gtk_tree_model_get_path (model, &iter); gtk_tree_view_scroll_to_cell (tvw, path, NULL, center, 0.382, 0.0); //remember existing selection, if any, because moving the cursor kills //the selection (if that hasn't been done by gtk already) GList *selitems = (clear_selection) ? NULL : gtk_tree_selection_get_selected_rows (view->selection, NULL); gtk_tree_view_set_cursor (tvw, path, NULL, FALSE); //selects just this row if (selitems != NULL) { GList *member; GtkTreePath *tp; for (member = selitems; member != NULL; member = member->next) { tp = (GtkTreePath *) member->data; gtk_tree_selection_select_path (view->selection, tp); gtk_tree_path_free (tp); } g_list_free (selitems); } if (!select_row) gtk_tree_selection_unselect_path (view->selection, path); if (grab_focus) gtk_widget_grab_focus (view->treeview); gtk_tree_path_free (path); } } /* * @brief UNUSED select 1st row, in specified view, where the filename matches a specified name model content update is suspended while this is performed @param view rt data for ther view to be worked on @param filename string with name of file to be used for selection @return */ /*void e2_fileview_select_row_by_filename (ViewInfo *view, gchar *filename) { gint i; //number of rows in the model gint n = gtk_tree_model_iter_n_children (curr_view->model, NULL); GtkTreeIter iter; GtkTreeModel *model = curr_view->model; gchar *thisname; e2_filelist_disable_refresh(); // search from after the current row to the end of the store for (i = 0; i < n; i++) { if (! gtk_tree_model_iter_nth_child (model, &iter, NULL, i)) continue; //the specified row doesn't exist any more gtk_tree_model_get (model, &iter, FILENAME, &thisname, -1); if (g_str_equal (thisname, filename)) { e2_fileview_focus_row (view, i, TRUE, FALSE, TRUE, TRUE); g_free (thisname); break; } else g_free (thisname); } e2_filelist_enable_refresh(); } */ /** @brief get file info for 1st selected row This includes a special-case mechanism for getting "..", which may be opened or activated when updir entries are shown in filelists @param view data stuct for the view to be worked on @param updir TRUE to include "../" in the candidates @return FileInfo data for the 1st selected row, or if none, then NULL */ FileInfo *e2_fileview_get_selected_first_local (ViewInfo *view, gboolean updir) { FileInfo *info = NULL; // GtkTreeSelection *sel = view->selection; GtkTreeModel *model; GList *base = gtk_tree_selection_get_selected_rows (view->selection, &model); GList *rowpaths; GtkTreeIter iter; for (rowpaths = base; rowpaths != NULL; rowpaths = rowpaths->next) { GtkTreePath *first_sel = (GtkTreePath *) rowpaths->data; if (gtk_tree_model_get_iter (model, &iter, first_sel)) { gtk_tree_model_get (model, &iter, FINFO, &info, -1); if (updir || !g_str_equal (info->filename, "..")) break; //ignore selection of "../" info = NULL; //in case there is nothing else selected } } g_list_foreach (base, (GFunc) gtk_tree_path_free, NULL); g_list_free (base); return info; } /** @brief get list of selected rows' info items This is usable only when the data is used before any chance of filelist refresh, which would invalidate the listed data pointers @param view rt data for the view to be worked on @return list of selected rows' info items, excluding ".." */ GList *e2_fileview_get_selected_local (ViewInfo *view) { // g_signal_emit_by_name (G_OBJECT (view->treeview), "end-selection"); // if (view->tagged != NULL) // return g_list_copy (view->tagged); GList *selectedrow_data = NULL; FileInfo *info; GtkTreePath *path; GtkTreeIter iter; // GtkTreeSelection *sel = view->selection; GtkTreeModel *model; GList *base = gtk_tree_selection_get_selected_rows (view->selection, &model); GList *rowpaths; for (rowpaths=base; rowpaths!=NULL; rowpaths=rowpaths->next) { path = (GtkTreePath *) rowpaths->data; if (gtk_tree_model_get_iter (model, &iter, path)) { gtk_tree_model_get (model, &iter, FINFO, &info, -1); if (!g_str_equal (info->filename, "..")) //CHECKME ok, even with localised string? selectedrow_data = g_list_append (selectedrow_data, info); } } g_list_foreach (base, (GFunc) gtk_tree_path_free, NULL); g_list_free (base); return selectedrow_data; } /** @brief toggle selection of all items in a pane This function selects or unselects all** items in a pane, depending on the current state of the corresponding flag, pane1_all or pane2_all ** except '..' entries, (if displayed) The pane will be activated, if it wasn't already. The function handles clicks of the select-all toggle buttons @param blah unused widget ptr (in case this is a callback) @param view fileview for pane that is to be (un)selected @return */ void e2_fileview_select_all (GtkWidget *blah, ViewInfo *view) { if (curr_view != view) e2_pane_activate_other (); GtkTreeSelection *sel = curr_view->selection; gboolean *allnow = (view == app.pane1.view) ? &pane1_all : &pane2_all ; if (*allnow) { gtk_tree_selection_unselect_all (sel); *allnow = FALSE; } else //select { gtk_tree_selection_select_all (sel); *allnow = TRUE; } } /** @brief cleanup content of selected file info struct foreach function for e2_fileview_clean_selected() @param seldata ptr to the struct to be processed @param user_data user data passed to g_ptr_array_foreach() UNUSED @return */ void e2_fileview_selected1_clean (E2_SelectedItemInfo *seldata, gpointer user_data) { /* g_free (seldata->filename); #ifdef E2_INCLIST gtk_tree_row_reference_free (seldata->ref); #else gtk_tree_path_free (seldata->path); #endif */ DEALLOCATE (E2_SelectedItemInfo, seldata); } /** @brief cleanup pointer array of selected file info structs @param selected ptr to the array to be freed @return */ void e2_fileview_clean_selected (GPtrArray *selected) { g_ptr_array_foreach (selected, (GFunc) e2_fileview_selected1_clean, NULL); g_ptr_array_free (selected, TRUE); } /** @brief create pointer array of selected file info structs This fn creates a pointer array, with pointers to structs containing selected items' names and other info. If found, ".." is filtered out. If found, any trailing / is removed. Item name strings are localised Data is duplicated, so the selection is refresh-proof. Treerow refs support incremental store changes as the selection is processed The returned array needs to be freed with its data. @param view is data structure for the view to be interrogated @return pointer to GPtrArray structure, or NULL if nothing selected */ GPtrArray *e2_fileview_get_selected (ViewInfo *view) { // if (view == curr_view) // g_signal_emit_by_name (G_OBJECT(view->treeview), "end-selection"); GtkTreeSelection *sel = view->selection; gint count = gtk_tree_selection_count_selected_rows (sel); if (count > 0) { GPtrArray *array = g_ptr_array_sized_new (count); GtkTreeModel *model; GList *base = gtk_tree_selection_get_selected_rows (sel, &model); GList *paths; GtkTreeIter iter; // gchar *filename, *localfilename; FileInfo *info; E2_SelectedItemInfo *seldata; for (paths = base; paths != NULL; paths = paths->next) { if (!gtk_tree_model_get_iter (model, &iter, paths->data)) continue; //the row has been altered since it was selected gtk_tree_model_get (model, &iter, FINFO, &info, -1); // gtk_tree_model_get (model, &iter, FILENAME, &filename, -1); if (!g_str_equal (info->filename, "..")) { seldata = ALLOCATE (E2_SelectedItemInfo); CHECKALLOCATEDWARN (seldata, continue); memcpy (&seldata->filename, &info->filename, sizeof (seldata->filename)); /* //always replicate filename // localfilename = g_strdup (filename); //strip any trailer (can't have it on links, at least) // gchar *s = localfilename + strlen (localfilename) - 1; // if (*s == G_DIR_SEPARATOR) // *s = '\0'; // seldata->filename = localfilename; //this is utf8 // seldata->statbuf = info->statbuf; NO LONGER USED // seldata->filename = g_strdup (info->filename); #ifdef E2_INCLIST //get a reference in case the model changes during the operation on the selection seldata->ref = gtk_tree_row_reference_new (model, paths->data); #else //this is only relevant if store data not changed on the fly seldata->path = gtk_tree_path_copy (paths->data); #endif */ g_ptr_array_add (array, seldata); } // always free it now else // g_free (filename); } g_list_foreach (base, (GFunc) gtk_tree_path_free, NULL); g_list_free (base); if (array->len == 0) //there was just a ".." selected { g_ptr_array_free (array, TRUE); array = NULL; } return array; } return NULL; } /** @brief cleanup one history item foreach function for e2_fileview_clean_history() @param seldata ptr to the struct to be processed @param user_data user data passed to g_ptr_array_foreach() UNUSED @return */ static void _e2_fileview_clean1_history (E2_DirHistoryEntry *data, gpointer user_data) { DEALLOCATE (E2_DirHistoryEntry, data); } /** @brief cleanup history data @param store for pointer to the list of history items to clean @return */ void e2_fileview_clean_history (GList **history) { if (*history != NULL) { g_list_foreach (*history, (GFunc) _e2_fileview_clean1_history, NULL); g_list_free (*history); *history = NULL; } } /** @brief get itemname for a specified view and row @param view is data structure for the view to be interrogated @param row number of the row to be interrogated REAL or FILTERED ? @return pointer to duplicated string, or NULL if there's nothing found */ gchar *e2_fileview_get_row_name (ViewInfo *view, gint row) { GtkTreeIter iter; // GtkTreeModel *model = GTK_TREE_MODEL (view->store); maybe filter GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (view->treeview)); if (! gtk_tree_model_iter_nth_child (model, &iter, NULL, row)) return NULL; gchar *name; gtk_tree_model_get (model, &iter, FILENAME, &name, -1); return name; } /** @brief helper to set file-list font @param view pointer to data struct for the view to be changed @param fontstr name of font to apply to the filelist associated with @a view @return */ static void _e2_fileview_set_font (ViewInfo *view, gchar *fontstr) { GList* columns, *renderers, *base1, *base2; columns = base1 = gtk_tree_view_get_columns (GTK_TREE_VIEW (view->treeview)); for (; columns != NULL ; columns = columns->next) { GtkTreeViewColumn *column = columns->data; renderers = base2 = gtk_tree_view_column_get_cell_renderers (column); for (; renderers != NULL ; renderers = renderers->next) g_object_set (renderers->data, "font", fontstr, NULL); g_list_free (base2); } g_list_free (base1); } /** @brief set file-lists font This is needed to change font after a config dialog that does not rebuild the whole window @return */ void e2_fileview_set_font (void) { gchar *fontstr = (e2_option_bool_get ("custom-list-font")) ? e2_option_str_get ("list-font") : NULL; //NULL forces default _e2_fileview_set_font (curr_view, fontstr); _e2_fileview_set_font (other_view, fontstr); } /** @brief set background color of treeview line @param view data struct for the view to process @param iter pointer to iter for the row to be changed @param color pointer to struct with data for the new color @return */ void e2_fileview_set_row_background (ViewInfo *view, GtkTreeIter *iter, GdkColor *color) { //set the attribute column for this row gtk_list_store_set (GTK_LIST_STORE (view->store), iter, BACKCOLOR, color, -1); view->lit = TRUE; } /** @brief revert background color of treeview line @param view data struct for the view to process @param iter pointer to iter for the row to be changed @return */ void e2_fileview_clear_row_background (ViewInfo *view, GtkTreeIter *iter) { //restore the background-attribute column for this row #ifdef E2_ASSISTED GdkColor *bc; if (e2_option_bool_get ("color-background-set")) bc = e2_option_color_get ("color-background"); else bc = NULL; gtk_list_store_set (GTK_LIST_STORE (view->store), iter, BACKCOLOR, bc, -1); #else gtk_list_store_set (GTK_LIST_STORE (view->store), iter, BACKCOLOR, NULL, -1); #endif view->lit = FALSE; } /** @brief translate array into another form sorted in index order This is needed to convert the 'fixed' column order used in data models and config/cache data, to variable order in accord with the displayed view columns and vice versa @param src_array pointer to array of ints that is to be translated @param dest_array pointer to array of ints that stores the translated order @param size the number of elements to be translated @return */ void e2_fileview_translate_cols_array (gint *src_array, gint *dest_array, gint size) { gint i; for (i = 0; i < size; i++) dest_array[src_array[i]] = i; } /** @brief update columns data, that is not automatically updated, for caching Columns order and widths are udated and stored in a list List format is: pane1 {order, width, ...}, pane2 {order, width, ...} @return */ void e2_fileview_update_col_cachedata (void) { //get current column widths GtkTreeViewColumn *col, *last; G_CONST_RETURN gchar *title; gint i, lc; GList *columns = gtk_tree_view_get_columns (GTK_TREE_VIEW (app.pane1.view->treeview)); GList *base = columns; lc = 0; last = (GtkTreeViewColumn *)columns->data; for (; columns != NULL; columns=columns->next) { col = columns->data; title = gtk_tree_view_column_get_title (col); //title is "0" ... "7", for cols 0 ... 7 i = atoi (title); col_width_store [0][i] = gtk_tree_view_column_get_width (col); //remember this one if it's a candidate for last-displayed column if (col_width_store [0][i] > 0) { lc = i; last = col; } } //for last-displayed column, cache the user-specified width, not the //expanded-to-fill width col_width_store [0][lc] = gtk_tree_view_column_get_fixed_width (last); g_list_free (base); columns = gtk_tree_view_get_columns (GTK_TREE_VIEW (app.pane2.view->treeview)); base = columns; lc = 0; last = (GtkTreeViewColumn *)columns->data; for (; columns != NULL; columns=columns->next) { col = columns->data; title = gtk_tree_view_column_get_title (col); i = atoi (title); col_width_store [1][i] = gtk_tree_view_column_get_width (col); //remember this one if it's a candidate for last-displayed column if (col_width_store [1][i] > 0) { lc = i; last = col; } } col_width_store [1][lc] = gtk_tree_view_column_get_fixed_width (last); g_list_free (base); //get columns displayed order e2_fileview_translate_cols_array (displayed_col_order[0], stored_col_order[0], MAX_COLUMNS); e2_fileview_translate_cols_array (displayed_col_order[1], stored_col_order[1], MAX_COLUMNS); /*SUPERSEDED BY ARRAY CACHEING //setup for list-cache writing //start fresh list e2_list_free_with_data (&cols_data); gchar *buffer; gint j; for (j = 0; j < 2; j++) { for (i = 0; i < MAX_COLUMNS; i++) { //data need to be strings, for the cache writer buffer = g_strdup_printf ("%d", stored_col_order[j][i]); cols_data = g_list_append (cols_data, buffer); buffer = g_strdup_printf ("%d", col_width_store[j][i]); cols_data = g_list_append (cols_data, buffer); }} */ } /* * @brief UNUSED find which view column shows filenames Columns may be in any order Scans column titles, looking for "0" @param view data structure for the view to which the file list belongs @return integer column number, 0 to MAX_COLUMNS-1 */ /* gint e2_fileview_find_name_col (ViewInfo *view) { GList *cols = gtk_tree_view_get_columns (GTK_TREE_VIEW (view->treeview)); GList *base = cols; G_CONST_RETURN gchar *title; gint result = 0; for ( ; cols != NULL; cols = cols->next ) { title = gtk_tree_view_column_get_title (cols->data); if (g_str_equal (title, "0")) break; result++; } g_list_free (base); // return (result < MAX_COLUMNS) ? result : -1; return result; } */ /** @brief exchange active and inactive panes This function is called from e2_pane_activate_other (), i.e. whenever we need to exchange curr_view and other_view. It toggles pointers and changes colours The latter is done by interrogating the selections in both file views, and so this fn should be blocked if either view is still being populated at the time this is called ?? CHECKME @return */ void e2_fileview_switch_views (void) { ViewInfo *temp; #ifdef E2_VFSTMP //FIXME what dir to open other_view->dir may be virtual, as indicated by view->spacedata //add some func like e2_fs_chdir (view); if (other_view->spacedata != NULL) { if (e2_fs_chdir_local (other_view->spacedata->workplace E2_ERR_NONE())) return; } else #else if (!e2_fs_chdir (other_view->dir E2_ERR_NONE())) #endif return; temp = other_view; other_view = curr_view; curr_view = temp; e2_pane_flag_active (); //change the status-indicator for the panes } /* * @brief UNUSED swap view pointers Exchange curr_view and other_view, without doing anything else @return */ /*void e2_fileview_switch_views_simple (void) { ViewInfo *temp = other_view; other_view = curr_view; curr_view = temp; }*/ /** @brief get indices of top line and left column shown in a filelist treeview This gets scroll info for treeview rebuilds @param view data structure for the view to be changed @param leftcol pointer to store for the left column index @param toprow pointer to store for the top row index @return */ void e2_fileview_get_scroll_data (ViewInfo *view, gint *leftcol, gint *toprow) { GtkTreePath *startpath; GtkTreeViewColumn *startcol; // gint cell_x, cell_y; if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (view->treeview), 0, 0, &startpath, &startcol, NULL, NULL)) //&cell_x, &cell_y)) { *toprow = *gtk_tree_path_get_indices (startpath); printd (DEBUG, "path found, row is %d", *toprow); gtk_tree_path_free (startpath); } else //liststore is empty *toprow = 0; //gtk bug, startcol always is the one at index 0 (maybe fixed after 2.8.6) *leftcol = 0; } /** @brief scroll a filelist treeview to top line @a toprow and left column @a leftcol This is used in treeview rebuilds It expects that the treeview exists - i.e. be careful when threaded Assumes BGL is closed @param view data structure for the view to be changed @param leftcol left column index @param toprow top row index @return */ void e2_fileview_scroll_to_position (ViewInfo *view, gint leftcol, gint toprow) { if (toprow > 0 || leftcol > 0) { GtkTreeView *tvw = GTK_TREE_VIEW (view->treeview); GtkTreeModel *model = gtk_tree_view_get_model (tvw); GtkTreeViewColumn* col = NULL; GtkTreePath* path = NULL; if (toprow > 0) { GtkTreeIter iter; if (gtk_tree_model_iter_nth_child (model, &iter, NULL, toprow)) path = gtk_tree_model_get_path (model, &iter); } else path = gtk_tree_path_new_first (); // WAIT_FOR_EVENTS gtk_tree_view_scroll_to_cell (tvw, path, col, TRUE, 0, 0); gtk_tree_path_free (path); } } /** @brief helper function to clean up redundant tree hash tables when there's nothing better to do @param data pointer to table to be destroyed @return FALSE so that this is not called again */ gboolean e2_fileview_treehash_free (GHashTable *data) { g_hash_table_destroy (data); return FALSE; } /** @brief get hash of selected-item names for pane in @a view Logged names are in utf-8 encoding, dirs have trailing / It is used as part of the view refresh or recreate processes (even with FAM, needed for refresh action, (un)hide, (un)filter, window re-create) @param view data structure for view being processed @return hash table of names, NULL if nothing selected */ //CHECKME filter & child GHashTable *e2_fileview_log_selected_names (ViewInfo *view) { GtkTreeModel *model; GList *base = gtk_tree_selection_get_selected_rows (view->selection, &model); if (g_list_length (base) == 0) return NULL; //build names-hash GHashTable *treehash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); GtkTreeIter iter; gchar *itemname; GList *selected_paths; for (selected_paths=base; selected_paths!=NULL; selected_paths=selected_paths->next) { if (gtk_tree_model_get_iter (model, &iter, selected_paths->data)) { gtk_tree_model_get (model, &iter, FILENAME, &itemname, -1); g_hash_table_insert (treehash, itemname, NULL); } gtk_tree_path_free (selected_paths->data); } g_list_free (base); return treehash; } /** @brief re-select items in list store for specified view @a view ->selected_names is freed here @param view data structure for view being processed @return */ void e2_fileview_reselect_names (ViewInfo *view) { // GtkTreeModel *model = GTK_TREE_MODEL (view->store); //may be filter model GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (view->treeview)); GtkTreeIter iter; gboolean notempty = gtk_tree_model_get_iter_first (model, &iter); GHashTable *oldselected = view->selected_names; if (oldselected != NULL && notempty) { guint more_hits = g_hash_table_size (oldselected); GtkTreeSelection *sel = view->selection; gchar *itemname; gpointer key; gpointer value; do { gtk_tree_model_get (model, &iter, FILENAME, &itemname, -1); if (g_hash_table_lookup_extended (oldselected, itemname, &key, &value)) { gtk_tree_selection_select_iter (sel, &iter); more_hits--; } g_free (itemname); } while (more_hits > 0 && gtk_tree_model_iter_next (model, &iter)); } if (oldselected != NULL) { //setup to get rid of hash when there's more time g_idle_add_full (G_PRIORITY_LOW, (GSourceFunc) e2_fileview_treehash_free, oldselected, NULL); view->selected_names = NULL; } } /** @brief cleanup helper function @param info data item to clean @param data UNUSED @return */ static void _e2_fileview_cleaninfo (FileInfo *info, gpointer data) { DEALLOCATE (FileInfo, info); } /** @brief convert @a entries from a list of localised heaped strings to a list of FileInfo's Upon failure, the suppiied list is cleared and its ptr set to relevant error code @param parentpath absolute path of dir containing items in @a list, localised string @param list store for ptr to Glist of heaped item names @return TRUE if the list was converted successfully */ static gboolean _e2_fileview_make_all_infos (gchar *parentpath, GList **list) { gint len1, len2, trailer; gchar *item; FileInfo *info; GList *member; guint errval = E2DREAD_ENOENT; //assign to prevent compiler error #ifdef E2_VFS VPATH data; data.spacedata = NULL; //FIXME #endif #ifdef E2_VFSTMP get proper spacedata #endif len1 = strlen (parentpath); trailer = (*(parentpath + len1 - sizeof (gchar)) == G_DIR_SEPARATOR) ? 0 : sizeof (gchar); //ascii ok len2 = (len1 + trailer + NAME_MAX)/8*8 + 8; gchar localpath[len2]; g_strlcpy (localpath, parentpath, sizeof (localpath)); if (trailer) { *(parentpath + len1 - sizeof (gchar)) = G_DIR_SEPARATOR; len1 += sizeof (gchar); } len2 -= len1; //or NAME_MAX ? item = localpath + len1; //retval = TRUE; for (member = *list; member != NULL; member = member->next) { #ifdef USE_GLIB2_10 info = (FileInfo *) g_slice_alloc (sizeof (FileInfo)); // info = ALLOCATE (FileInfo); #else info = ALLOCATE (FileInfo); #endif CHECKALLOCATEDWARN (info, ) if (info == NULL) { errval = E2DREAD_ENOMEM; goto errexit; } nextmember: g_strlcpy (item, (gchar *)member->data, len2); #ifdef E2_VFS data.localpath = localpath; if (e2_fs_lstat (&data, &info->statbuf E2_ERR_NONE())) #else if (e2_fs_lstat (localpath, &info->statbuf E2_ERR_NONE())) #endif { //FIXME use error code from prior lstat to do this parent-check better *item = '\0'; if (e2_fs_lstat (localpath, &info->statbuf E2_ERR_NONE())) { errval = E2DREAD_NS; goto errexit; } else { //keep going if just this one item is N/A GList *tmp = member; member = member->next; *list = g_list_delete_link (*list, tmp); if (member != NULL) goto nextmember; DEALLOCATE (FileInfo, info); break; } } //any truncation here (>NAME_MAX+1) means it will never be able to be stat'd again g_strlcpy (info->filename, (gchar *)member->data, sizeof (info->filename)); g_free (member->data); member->data = info; } return TRUE; errexit: //FIXME warning e2_list_free_with_data (&member); //get rid of remaining string data g_list_foreach (*list, (GFunc) _e2_fileview_cleaninfo, NULL); g_list_free (*list); *list = GINT_TO_POINTER (errval); return FALSE; } /** @brief find matching path entry in history list @param list Glist of past path strings @param path path string to be matched @return list entry whose data matches path, or else NULL */ static GList *_e2_fileview_find_history (GList *list, gchar *path) { GList *tmp; for (tmp = list; tmp != NULL; tmp = tmp->next) { if (g_str_equal (((E2_DirHistoryEntry *) tmp->data)->path, path)) return tmp; } return NULL; } /** @brief update all items' info in the treeview associated with @a view After updating the underlying liststore contents, item(s) are (re) selected as appropriate, and the view is scrolled to the relevant position This is used for refreshes and cd's It can be a thread func - though not yet thread-safe Directory whose content we want is named in view->dir (utf-8) Current store content is cleared after successful preliminary things, but before new data is added, of course Active filter(s) are applied, but no sorting. If relevant, the text-color for selected items is updated here, rather than in the selection function (too many un-necessary repeats, there) This function is not necessarily thread-safe Assumes BGL is closed upon arrival here NOTE sets view->total_items, and view->row if needed @param view data structure for the view to which the file list belongs @return TRUE if all was completed */ gboolean e2_fileview_prepare_list (ViewInfo *view) { // printd (DEBUG, "e2_fileview_prepare_list"); #ifdef E2_REFRESH_DEBUG printd (DEBUG, "disable refresh, prepare list"); #endif e2_filelist_disable_refresh (); e2_window_set_cursor (GDK_WATCH); #ifdef E2_VFS VPATH data; data.spacedata = view->spacedata; #endif GtkTreeModel *mdl; GtkTreeSortable *sortable; GList *entries; //list of FileInfo's created when reading dir // GObject *debug = G_OBJECT (view->model); //#ifdef E2_NEWREFRESH if (view->listcontrols.refreshtype == E2_CHANGE) //#else // if (view->listcontrols.refreshtype == E2_REFRESH || view->listcontrols.refreshtype == E2_CHANGE) //#endif { //========== EXPERIMENTAL STORE-COPY PROCESS #ifdef STORECOPY //CHECKME rationalise code; confirm that filtering; refreshing are ok if (g_str_equal (curr_view->dir, other_view->dir) && curr_view->spacedata == other_view->spacedata) { ViewInfo *otherview = (view == curr_view) ? other_view:curr_view; //match only older refreshes // if (view->refreshtype == E2_CHANGE || otherview->dir_mtime < time (NULL)) // { printd (DEBUG, "copy other store"); GtkListStore *newstore = e2_filelist_copy_store (otherview->store); g_object_ref (G_OBJECT (view->store)); //this has same effect as unref existing filtermodel gtk_tree_view_set_model (GTK_TREE_VIEW (view->treeview), NULL); //arrange to cleanup the old store at an idle time gboolean newtimer = (app.used_stores == NULL); app.used_stores = g_slist_append (app.used_stores, view->store); if (newtimer) { printd (DEBUG, "setup to clear stores later"); g_idle_add (e2_filelist_clear_old_stores, NULL); } view->store = newstore; view->model = gtk_tree_model_filter_new (GTK_TREE_MODEL (newstore), NULL); g_object_unref (G_OBJECT (newstore)); //apply current filtering view->filtered_before = FALSE; //trigger re-attachment of filter func e2_fileview_refilter_list (view); //continue the current sorting arrangements sortable = GTK_TREE_SORTABLE (newstore); //allow non-sorted display using GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID // gtk_tree_sortable_set_default_sort_func (sortable, NULL, NULL, NULL); gint array_row = (view == app.pane1.view) ? 0 : 1; gint *order_array = displayed_col_order[array_row]; gint i, m; for (i = 0; i < MAX_COLUMNS; i++) { m = order_array[i]; //get the model/'external' index if (m == FILENAME && view->extsort) gtk_tree_sortable_set_sort_func (sortable, FILENAME, (GtkTreeIterCompareFunc) e2_fileview_ext_sort, &view->sort_order, NULL); else gtk_tree_sortable_set_sort_func (sortable, m, e2_all_columns[m].sort_func, &view->sort_order, NULL); } //sort the store using the current sort column & direction gtk_tree_sortable_set_sort_column_id (sortable, view->sort_column, view->sort_order); //make it fully ours gtk_tree_view_set_model (GTK_TREE_VIEW (view->treeview), view->model); // g_object_unref (G_OBJECT (view->model)); BAD //setup for status-line counters view->total_items = otherview->total_items; view->case_sensitive_names = otherview->case_sensitive_names; view->dir_mtime = otherview->dir_mtime; view->dir_ctime = otherview->dir_ctime; e2_window_set_cursor (GDK_LEFT_PTR); e2_filelist_enable_refresh (); return TRUE; // } // printd (DEBUG, "time-test failed"); } // else //panes have different dirs, or some error in copying // { printd (DEBUG, "use fresh store"); #endif //def STORECOPY //============ gchar *local = F_FILENAME_TO_LOCALE (view->dir); //construct relevant items data for store-filler //FIXME if we can, just get relevant items for a refresh //get a fresh set of FileInfo data from source // printd (DEBUG, "grab current directory data"); #ifdef E2_VFS data.localpath = local; gdk_threads_leave (); entries = (GList *)e2_fs_dir_foreach (&data, #else gdk_threads_leave (); entries = (GList *)e2_fs_dir_foreach (local, #endif E2_DIRWATCH_CHECK, //this is irrelevant for non-local dirs NULL, NULL, NULL E2_ERR_NONE()); // printd (DEBUG, "Read dir function returned %x", entries); gdk_threads_enter (); if (E2DREAD_FAILED (entries) || !_e2_fileview_make_all_infos (local, &entries)) { //any cleanup of entries (possibly with mixed data types) done downstream F_FREE (local); e2_window_set_cursor (GDK_LEFT_PTR); #ifdef E2_VFSTMP //FIXME warn user #endif #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, read dir function error"); #endif e2_filelist_enable_refresh (); // LISTS_LOCK // view->listcontrols.listing = FALSE; // LISTS_UNLOCK return FALSE; } /* LISTS_LOCK gboolean abort = (view->listcontrols.newpath != NULL); //different dir wanted now LISTS_UNLOCK if (abort) { STOP NOW } */ printd (DEBUG, "current directory data loaded"); //even when FAM is working, some things (e.g. treedialog) must poll for //changes, so set times reflecting the completed poll struct stat sb; #ifdef E2_VFS data.localpath = local; if (e2_fs_stat (&data, &sb E2_ERR_NONE())) //through links #else if (e2_fs_stat (local, &sb E2_ERR_NONE())) //through links #endif { printd (WARN, "Unable to stat directory: %s", view->dir); F_FREE (local); e2_window_set_cursor (GDK_LEFT_PTR); e2_filelist_enable_refresh (); return FALSE; } view->case_sensitive_names = E2FSCASE_UNKNOWN; //after cd, can be replaced by cached value view->dir_mtime = sb.st_mtime; view->dir_ctime = sb.st_ctime; /*#ifndef E2_NEWREFRESH # ifdef E2_VFSTMP if (view->spacedata != NULL && view->spacedata->monitored) { //v-dir is monitored //FIXME } else if (view->spacedata != NULL) { # endif # ifdef E2_FAM_KERNEL if (view->refreshtype == E2_REFRESH) e2_fs_FAM_clean_reports (view->dir); / * prior reports about the dir are now gone. Henceforth any report about the dir or an item in it will trigger a new refresh, even if the change that caused the report is shown in this refresh. The alternative is to do this at the end, and lose any change after an item is processed in this refresh * / # endif //which fam # ifdef E2_VFSTMP } # endif #endif //ndef E2_NEWREFRESH */ // mdl = GTK_TREE_MODEL (view->store); sortable = GTK_TREE_SORTABLE (view->store); //remember the current sorting arrangements gtk_tree_sortable_get_sort_column_id (sortable, &view->sort_column, &view->sort_order); //turn off model sorting before any rows are added //CHECKME do we really benefit from turning off sorting when the //view is detached from the model? //GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID (-2) appeared in gtk 2.6 gtk_tree_sortable_set_sort_column_id (sortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING); //============ //#ifdef STORECOPY // } //end of block for experimental stuff //#endif //============ } else //doing a window recreation { //create new store with current items, in case content-format is changed entries = NULL; FileInfo *infoptr, *newinfo; GtkTreeIter iter; mdl = GTK_TREE_MODEL (view->store); if (gtk_tree_model_get_iter_first (mdl, &iter)) { do { gtk_tree_model_get (mdl, &iter, FINFO, &infoptr, -1); #ifdef USE_GLIB2_10 newinfo = (FileInfo *) g_slice_alloc (sizeof(FileInfo)); g_memmove (newinfo, infoptr, sizeof(FileInfo)); #else newinfo = (FileInfo *) g_memdup (infoptr, sizeof(FileInfo)); #endif entries = g_list_prepend (entries, newinfo); //faster } while (gtk_tree_model_iter_next (mdl, &iter)); entries = g_list_reverse (entries); } } GtkListStore *store = e2_filelist_fill_store (entries, view); // printd (DEBUG, "new liststore ready"); if (store != NULL) { //there was a valid store created //can't re-assign the filter model to new store, must replace model //make sure the store and base model stay alive after the filtermodel goes g_object_ref (G_OBJECT (view->store)); view->model = gtk_tree_model_filter_new (GTK_TREE_MODEL (store), NULL); gtk_tree_view_set_model (GTK_TREE_VIEW (view->treeview), view->model); g_object_unref (G_OBJECT (store)); g_object_unref (G_OBJECT (view->model)); //arrange to cleanup the old store at an idle time gboolean newtimer = (app.used_stores == NULL); app.used_stores = g_slist_append (app.used_stores, view->store); if (newtimer) { printd (DEBUG, "setup to clear stores later"); g_idle_add (e2_filelist_clear_old_stores, NULL); } view->store = store; sortable = GTK_TREE_SORTABLE (store); //allow non-sorted display using GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID // gtk_tree_sortable_set_default_sort_func (sortable, NULL, NULL, NULL); gint array_row = (view == app.pane1.view) ? 0 : 1; gint *order_array = displayed_col_order[array_row]; gint i, m; for (i = 0; i < MAX_COLUMNS; i++) { m = order_array[i]; //get the model/'external' index if (m == FILENAME && view->extsort) gtk_tree_sortable_set_sort_func (sortable, FILENAME, (GtkTreeIterCompareFunc) e2_fileview_ext_sort, &view->sort_order, NULL); else gtk_tree_sortable_set_sort_func (sortable, m, e2_all_columns[m].sort_func, &view->sort_order, NULL); } // printd (DEBUG, "resort new data"); //sort the view using the current sort column & direction gtk_tree_sortable_set_sort_column_id (sortable, view->sort_column, view->sort_order); // printd (DEBUG, "resort finished"); //setup for status-line counters //FIXME if entries list can be just the changed items //entries count adjusted for ".." ("." filtered from list) view->total_items = g_list_length (entries) - 1; //may be -1 //fuse-created dirs may not have any ".." entry, but we need one gboolean addup = FALSE; FileInfo *infoptr; GtkTreeIter iter; //sorting will have placed any existing ".." at start of store //FIXME neater to do this is read-dir function if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter)) { gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, FINFO, &infoptr, -1); if (!g_str_equal (infoptr->filename, "..")) addup = TRUE; } else addup = TRUE; if (addup) { #ifdef USE_GLIB2_10 infoptr = (FileInfo *) g_slice_alloc (sizeof (FileInfo)); // infoptr = ALLOCATE (FileInfo); #else infoptr = ALLOCATE (FileInfo); #endif CHECKALLOCATEDFATAL (infoptr); g_strlcpy (infoptr->filename, "..", sizeof (infoptr->filename)); #ifdef E2_VFSTMP //FIXME properly handle root of v-dir #endif #ifdef E2_VFS VPATH fake = { G_DIR_SEPARATOR_S, view->spacedata }; e2_fs_stat (&fake, &infoptr->statbuf E2_ERR_NONE()); #else e2_fs_stat (G_DIR_SEPARATOR_S, &infoptr->statbuf E2_ERR_NONE()); #endif gtk_list_store_insert_with_values (store, &iter, 0, FILENAME, "../", SIZE, "0", PERM, "drwxrwxrwx", OWNER, "", GROUP, "", MODIFIED, "", ACCESSED, "", CHANGED, "", NAMEKEY, g_utf8_collate_key ("..", -1), FINFO, infoptr, FORECOLOR, e2_option_color_get ("color-ft-dir"), // VISIBLE, TRUE, -1); view->total_items++; } view->filtered_before = FALSE; //trigger re-attachment of filter func e2_fileview_refilter_list (view); } // else //liststore creation error // FIXME clear entries data, warn user if (entries != NULL) g_list_free (entries); //data in the list is in liststore pointers, freed later e2_window_set_cursor (GDK_LEFT_PTR); #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, prepare list"); #endif e2_filelist_enable_refresh (); // printd (DEBUG, "end prepare list for %s", view->dir); return TRUE; } /* * @brief change-directory completion-watch timer-function @param data pointer to data relating to the cd being monitored @return FALSE when ready to shutdown timer */ /* gboolean e2_fileview_cd_watch (E2_CDwatch *data) { if (app.timers[CDWAIT_T] > 0) return TRUE; //the cd is still blocked //can't use E2_PaneRuntime* directly, due to include circularity problem E2_PaneRuntime *rt = (data->view == curr_view) ? curr_pane : other_pane; if (!cd_blocked && g_str_has_prefix (rt->path, data->newpath)) { *data->completed_flag = CD_SUCCESS; g_free (data->newpath); DEALLOCATE (E2_CDwatch, data); return FALSE; } //stop after 100 tries = 10 seconds //FIXME use a timeout mechanism consistent with the one for non-local dir reading if (++data->repeats == 100) { *data->completed_flag = CD_TIMEOUT; g_free (data->newpath); DEALLOCATE (E2_CDwatch, data); return FALSE; } return TRUE; } */ /** @brief change-directory timer-function This can be called for both dirs, at session start at least It handles any requested cd for either pane @param data pointer to cd data for the change @return TRUE if prior cd is not complete, or FALSE to shutdown timer */ gboolean e2_fileview_cd_manage (E2_Listman *data) { E2_Listman *workdata; pthread_t thisID; pthread_attr_t attr; gboolean one_active, flag; //, flag2; guint fails = 0; LISTS_LOCK flag = app.pane1_view.listcontrols.cd_requested || app.pane2_view.listcontrols.cd_requested; data->cd_requested = TRUE; //mark this one to block any repeats printd (DEBUG, "managing cd to %s", data->newpath); LISTS_UNLOCK if (flag) { //this is a timer callback which escaped the termination process below //or (unlikely) is for the former-other pane and happened to arrive here //at a useless moment printd (NOTICE, "aborted cd timer, another one has precedence"); //, data->timer); return FALSE; } //immediately stop the timer which called here, to prevent _any_ chance of //parallel instances while (g_source_remove_by_user_data (data)) { printd (DEBUG, "shut down a cd-timer for this pane"); } //#ifndef E2_STATUS_DEMAND // e2_window_disable_status_update (); //prevents bad behaviour ?? //#endif //in case the active pane changes while we're here, get a snapshot one_active = (curr_view == &app.pane1_view); do { //try active pane first workdata = (one_active) ? &app.pane1_view.listcontrols : &app.pane2_view.listcontrols; LISTS_LOCK if (workdata->cd_requested) { if (!(workdata->cd_working || workdata->refresh_working #ifdef E2_STATUS_BLOCK || app.status_working #endif )) { LISTS_UNLOCK printd (NOTICE, "Changing active pane to %s", workdata->newpath); pthread_attr_init (&attr); pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); if (pthread_create (&thisID, &attr, (gpointer) _e2_fileview_change_dir, workdata) == 0) { printd (DEBUG,"change-dir-thread (ID=%lu) started", thisID); //do this here too in case the thread gets underway later LISTS_LOCK workdata->cd_requested = FALSE; LISTS_UNLOCK } else fails++; } else //busy if ( (one_active && app.pane2_view.listcontrols.cd_working) ||(!one_active && app.pane1_view.listcontrols.cd_working) ) { // if ( (one_active && app.pane2_view.listcontrols.timer == 0) // ||(!one_active && app.pane1_view.listcontrols.timer == 0) ) // { //there is no active timer for the other pane (should never happen) printd (DEBUG,"active pane blocakge, restarting timer"); //data->timer = g_timeout_add_full (G_PRIORITY_HIGH, 90, (GSourceFunc) e2_fileview_cd_manage, data, NULL); // } LISTS_UNLOCK return FALSE; //wait for a timer to call back here } else { LISTS_UNLOCK } } else { LISTS_UNLOCK } //then try inactive pane workdata = (one_active) ? &app.pane2_view.listcontrols : &app.pane1_view.listcontrols; LISTS_LOCK if (workdata->cd_requested) { if (!(workdata->cd_working || workdata->refresh_working #ifdef E2_STATUS_BLOCK || app.status_working #endif )) { LISTS_UNLOCK printd (NOTICE, "Changing INactive pane to %s", workdata->newpath); pthread_attr_init (&attr); pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); if (pthread_create (&thisID, &attr, (gpointer) _e2_fileview_change_dir, workdata) == 0) { printd (DEBUG,"change-dir-thread (ID=%lu) started", thisID); LISTS_LOCK workdata->cd_requested = FALSE; LISTS_UNLOCK } else fails++; } else //busy if ( (one_active && app.pane1_view.listcontrols.cd_working) ||(!one_active && app.pane2_view.listcontrols.cd_working) ) { // if ( (one_active && app.pane1_view.listcontrols.timer == 0) // ||(!one_active && app.pane2_view.listcontrols.timer == 0) ) // { //there is no active timer for the other pane (should never happen) printd (DEBUG,"INactive pane blocakge, restarting timer"); //data->timer = g_timeout_add_full (G_PRIORITY_HIGH, 90, (GSourceFunc) e2_fileview_cd_manage, data, NULL); // } LISTS_UNLOCK return FALSE; //wait for a timer to call back here } else { LISTS_UNLOCK } } else { LISTS_UNLOCK } LISTS_LOCK flag = app.pane1_view.listcontrols.cd_requested || app.pane2_view.listcontrols.cd_requested; LISTS_UNLOCK } while (flag && fails < 2); if (flag && fails == 2) { // gdk_threads_enter (); // gchar *msg = ; // e2_output_print_error (msg, FALSE); // gdk_threads_leave (); printd (WARN, "CD-thread creation error"); } //#ifndef E2_STATUS_DEMAND // printd (DEBUG,"resume status checking"); // e2_window_enable_status_update (-1); //#endif // LISTS_LOCK // data->timer = 0; //now it's ok to allow other timers // LISTS_UNLOCK return FALSE; } /** @brief get new directory contents for a view This is a thread-function. Assumes that the "big gtk lock" (gdk mutex) is off/open @param cddata data structure with parameters for the view to be changed @return NULL always */ static gpointer _e2_fileview_change_dir (E2_Listman *cddata) { E2_DirHistoryEntry *entry; #ifdef E2_REFRESH_DEBUG printd (DEBUG, "disable refresh, _e2_fileview_change_dir"); #endif e2_filelist_disable_one_refresh ((cddata == &app.pane1_view.listcontrols) ? PANE1:PANE2); //suspend overlapping refreshes ASAP e2_utils_block_thread_signals (); //block all allowed signals to this thread LISTS_LOCK gchar *newpath = cddata->newpath; //duplicate in case user cd's again, free it if cd fails cddata->newpath = NULL; //from now, non-NULL signals a new cd request //CHECKME ok to overlap calles, hence defer setting these flags ? cddata->cd_working = TRUE; //prevent re-entrant cd's cddata->cd_requested = FALSE; //this may preceed the change in e2_fileview_cd_manage() ViewInfo *view = cddata->view; gboolean history = cddata->history; gboolean hook = cddata->hook; LISTS_UNLOCK printd (DEBUG, "_e2_fileview_change_dir: %s", newpath); /* #ifdef E2_VFSTMP //FIXME better check for usable dir //also, handle vfs flags and local dirs that only use async IO //some OS have MNT_SYNCHRONOUS and MNT_ASYNC in a statfs.f_flags if (view->spacedata == NULL) { //not a v-dir #endif if (e2_fs_dir_is_native (newpath)) view->dirtype = FS_LOCAL; else view->dirtype = FS_FUSE; #ifdef E2_VFSTMP } #endif */ #ifdef E2_VFSTMP //FIXME check that e2_fs_cd_isok works for v-dirs #endif gdk_threads_enter (); //e2_fs_cd_isok () may display message if (! e2_fs_cd_isok (newpath E2_ERR_NONE())) //can't go there CHECKME no delay when non-mounted ? { gdk_threads_leave (); g_free (newpath); LISTS_LOCK cddata->cd_working = FALSE; LISTS_UNLOCK e2_filelist_enable_one_refresh ((view == &app.pane1_view) ? PANE1:PANE2); return NULL; } gdk_threads_leave (); //update row history for current dir //at session-start, view->dir = "", hence no match found in list //this update will be redundant if the cd fails or is aborted, but that's ok //we do it now anyway, to take up some time in case a competing activity is underway #ifdef E2_VFSTMP //CHECKME confirm v-dir history list is still in place #endif GList *outdir = _e2_fileview_find_history (view->dir_history, view->dir); if (outdir != NULL) { entry = outdir->data; /*even though the sort-order and/or dir contents might change before we re-visit here, if there is a selection now, remember its start, or else remember the start of the current display*/ /*this func may be called during a window-rebuild, in which case the old dir will the same as the new one, and the selection and visible rect data will be meaningless*/ GtkTreePath *startpath; if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (view->treeview), 0, 0, &startpath, NULL, NULL, NULL)) { entry->toprow = *gtk_tree_path_get_indices (startpath); gtk_tree_path_free (startpath); } else //dir is empty entry->toprow = 0; GtkTreeSelection *sel = view->selection; if (gtk_tree_selection_count_selected_rows (sel) > 0) { GtkTreeModel *model; GList *selpaths = gtk_tree_selection_get_selected_rows (sel, &model); //get the 1st selected item's row entry->selrow = *gtk_tree_path_get_indices (selpaths->data); g_list_foreach (selpaths, (GFunc) gtk_tree_path_free, NULL); g_list_free (selpaths); } else entry->selrow = -1; if (view->case_sensitive_names & E2FSCASE_NOCACHE) entry->case_sensitive_names = E2FSCASE_UNKNOWN; else entry->case_sensitive_names = view->case_sensitive_names; } /*checks for overlapping cd/refresh/recreate now done externally //pause until any in-progess e2_fileview_prepare_list() is completed //CHECKME should this involve some test for actual-treeview-completion ? LISTS_LOCK if (cddata->listing) { LISTS_UNLOCK printd (DEBUG, "waiting for completion of another treeview update before changing dir"); app.timers[REFRESHWAIT_T] = g_timeout_add (100, (GSourceFunc) _e2_fileview_check_completion, &cddata->listing); gtk_main (); } LISTS_UNLOCK */ GHashTable *selnames = view->selected_names; //remember in case cd fails view->selected_names = NULL; //signal no need to reselect anything //now update the local path store, with backup in case of error gchar *oldpath = g_strdup (view->dir); //E2_VFSTMPOK LISTS_LOCK g_strlcpy (view->dir, newpath, sizeof (view->dir)); cddata->refreshtype = E2_CHANGE; //signal for list creator LISTS_UNLOCK gdk_threads_enter (); //get the replacement filelist if (!e2_fileview_prepare_list (view)) { gdk_threads_leave (); printd (WARN, "prepare list failed"); view->selected_names = selnames; g_strlcpy (view->dir, oldpath, sizeof (view->dir)); g_free (oldpath); g_free (newpath); } else { // printd (DEBUG, "prepare list succeeded"); g_free (oldpath); E2_PaneRuntime *rt = (view == curr_view) ? curr_pane : other_pane; if (rt->toolbar.has_command_line) //update dirline to new path e2_command_line_change_dir (newpath, rt); //scroll to approximately the position from last time view->row = 0; //default position GtkTreeModel *mdl = view->model; GtkTreeIter iter; if (gtk_tree_model_get_iter_first (mdl, &iter)) { #ifdef E2_VFSTMP //CHECKME confirm v-dir history list is still in place #endif GList *indir = _e2_fileview_find_history (view->dir_history, view->dir); if (indir != NULL) { //new dir is in history list GtkTreePath *path; entry = indir->data; view->case_sensitive_names = entry->case_sensitive_names; if (entry->selrow > -1) { //during unpacking of an archive etc all data from last time may not be there //don't select or scroll if not enough data now (CHECKME items - 1 instead ? gint items = //if ( gtk_tree_model_iter_n_children (mdl, NULL); if (items //0,1,... 1-based <= entry->selrow //0,1,... 0-based ) { // entry->selrow = -1; if (items > 0) { e2_fileview_focus_row (view, items-1, FALSE, FALSE, FALSE, FALSE); } } else if (entry->selrow > -1) { //select the 1st row we selected last time //(tuff luck if there's been content change or sort-order change) //can't use e2_fileview_focus_row () as that is bad near end of list //move back past any filtered-out iter while (!gtk_tree_model_iter_nth_child (mdl, &iter, NULL, entry->selrow) && entry->selrow > -1) entry->selrow --; if (entry->selrow > -1) { view->row = entry->selrow; path = gtk_tree_model_get_path (mdl, &iter); if (view->row > 0) { //to avoid bad scroll-behaviour near the end of the list, need //to scroll in 2 steps ... gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view->treeview), path, NULL, TRUE, 1.0, 0.0); WAIT_FOR_EVENTS /*FIXME does nothing cuz value is 0 GtkAdjustment *vadj = gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (view->treeview)); gdouble value = gtk_adjustment_get_value (vadj); gdouble newvalue = value - vadj->page_size * 0.382; gtk_adjustment_set_value (vadj, newvalue); */ } gtk_tree_view_set_cursor (GTK_TREE_VIEW (view->treeview), path, NULL, FALSE); gtk_tree_path_free (path); } else { view->row = 0; //default value } } } else if (entry->toprow > 0) { //goto (approximately) the top of the visible window as it was last time while (!gtk_tree_model_iter_nth_child (mdl, &iter, NULL, entry->toprow) && entry->toprow > 0) entry->toprow --; if (entry->toprow > 0) { path = gtk_tree_model_get_path (mdl, &iter); gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view->treeview), path, NULL, TRUE, 0, 0); gtk_tree_path_free (path); } e2_fileview_focus_row (view, entry->toprow, FALSE, FALSE, FALSE, FALSE); } } else //the new dir has never been opened in this session { gboolean flag = e2_option_bool_get ("select-first-item"); e2_fileview_focus_row (view, 0, flag, FALSE, FALSE, FALSE); } } gdk_threads_leave (); #ifdef E2_STATUS_REF extern gint statref_count; #endif if (view == curr_view #ifdef E2_STATUS_REF && statref_count == 0 #endif #ifdef E2_STATUS_BLOCK && !app.status_working #endif ) e2_window_update_status_bar (NULL); //expects BGL off //ancillary tasks if (selnames != NULL) { //setup to get rid of any old hash g_idle_add_full (G_PRIORITY_LOW, (GSourceFunc) e2_fileview_treehash_free, selnames, NULL); } //this is calculated instead of passed, as circularity prevents a //E2_PaneRuntime * in any declaration in e2_fileview.h // E2_PaneRuntime *rt = (view == curr_view) ? curr_pane : other_pane; //clear any pending refresh hanging around from the old dir //CHECKME kernel reports too ? LISTS_LOCK cddata->refresh_requested = FALSE; LISTS_UNLOCK //change FAM place ASAP #ifdef E2_VFSTMP //v-dir monitoring when supported if (view->spacedata != NULL && view->spacedata->monitored) { LISTS_LOCK oldpath = rt->path; rt->path = newpath; //DON'T FREE ME LISTS_UNLOCK //startup alteration-monitoring for the new dir //(or if need be, for its target if it's a link) //FIXME e2_vfs_?? (oldpath, rt); g_free (oldpath); } else if (view->spacedata == NULL) { //local dir #endif #ifdef E2_FAM oldpath = rt->path; LISTS_LOCK rt->path = newpath; //DON'T FREE ME LISTS_UNLOCK //startup alteration-monitoring for the new dir //(or if need be, for its target if it's a link) e2_fs_FAM_change (oldpath, rt); g_free (oldpath); #else //update the path store LISTS_LOCK g_free (rt->path); rt->path = newpath; LISTS_UNLOCK #endif //def E2_FAM #ifdef E2_VFSTMP } #endif //run refocus hooklist (BGL open) if (view == curr_view) { #ifdef E2_VFSTMP //CHECKME hook for v-dir #else e2_hook_list_run (&app.hook_pane_focus_changed, curr_pane); #endif } //run cd hooklist (BGL open) if (hook #ifdef E2_VFSTMP //CHECKME mkdir dialog for v-dir's && (view->spacedata != NULL || view->spacedata->dirtype == FS_LOCAL || view->spacedata->dirtype == FS_FUSE) #endif ) { #ifdef E2_VFSTMP //FIXME hook process for v-dir's #else e2_hook_list_run (&rt->hook_change_dir, newpath); //not rt->path if that can be altered in any other thread #endif } GList *tmp; #ifdef E2_VFSTMP //CHECKME relevant list for v-dir still in place #endif // printd (DEBUG, "_e2_fileview_change_dir update history"); //update goto-line-after-opening data if ((tmp = _e2_fileview_find_history (view->dir_history, view->dir)) == NULL) { //need a new entry for the history list entry = ALLOCATE0 (E2_DirHistoryEntry); CHECKALLOCATEDWARNT (entry, ); if (entry != NULL) { //just set the path, for now - other data set when dir is departed g_strlcpy (entry->path, view->dir, sizeof(entry->path)); view->dir_history = g_list_append (view->dir_history, entry); } } else entry = tmp->data; #ifdef E2_VFSTMP //CHECKME relevant list for v-dir still in place #endif if (history) { //update the pane visited-dirs history HISTORY_LOCK //when this list was cached, independent copies of paths were needed //e2_list_update_history (newpath, &rt->opendirs, NULL, 30, TRUE); //now ... //if new entry is already in history, just adjust the index if ((tmp = g_list_find_custom (rt->opendirs, entry, (GCompareFunc)e2_list_strcmp)) != NULL) rt->opendir_cur = g_list_position (rt->opendirs, tmp); else { if (rt->opendirs != NULL && rt->opendir_cur > 0) //split off items before (i.e. visited after) current, and free them e2_list_nth_break (&rt->opendirs, rt->opendir_cur, NULL, &rt->opendirs); rt->opendirs = g_list_prepend (rt->opendirs, entry); //treat as a string if (rt->opendir_cur == 0) { //list is one longer now if (g_list_length (rt->opendirs) > 30) { tmp = g_list_last (rt->opendirs); rt->opendirs = g_list_delete_link (rt->opendirs, tmp); } } else //at least one member of list was removed before adding this one rt->opendir_cur = 0; //and this is now current } HISTORY_UNLOCK } } LISTS_LOCK cddata->cd_working = FALSE; LISTS_UNLOCK #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, change-dir"); #endif e2_filelist_enable_one_refresh ((view == &app.pane1_view) ? PANE1:PANE2); printd (DEBUG, "_e2_fileview_change_dir ends"); return NULL; } //#ifdef E2_NEWREFRESH /* * @brief idle function to run refresh hooklist NOTE if any registered hook-function does anything to the filesystem that triggers another refresh, then an infinite loop will happen ! So this is probably not much use ... @param view pointer to data struct for the view whose filelist was refreshed @return FALSE, to stop the callbacks */ /*static gboolean _e2_fileview_run_hooks (ViewInfo *view) { WATCH OUT FOR REENTRANT CALLING THIS ? e2_hook_list_run (&view->hook_refresh, view); return FALSE; } */ /** @brief incrementally refresh list store for @a view This is a thread function If the directory associated with @a view is no longer accessible, a suitable alternative is found and displayed. Expects BGL to be open, on arrival here @param view data structure for view being processed @return NULL (FALSE) if the refresh fails (or cd to elsewhere), pointerised 1 if it succeeds */ gpointer e2_fileview_refresh_list (ViewInfo *view) { gboolean busy; LISTS_LOCK busy = view->listcontrols.norefresh || view->listcontrols.refresh_working; LISTS_UNLOCK #ifdef E2_STATUS_BLOCK if (!busy) busy = (view == curr_view && app.status_working); #endif if (busy) { printd (DEBUG, "BLOCKED start refresh filelist for %s", view->dir); return NULL; //refresh is disabled, or the view is still being processed from last time } printd (DEBUG, "start refresh filelist for %s", view->dir); #ifdef E2_VFSTMP if ((curr_view->spacedata == other_view->spacedata) && g_str_equal (curr_view->dir, other_view->dir)) { //setup to process both views in sequence, and clear any refresh_requested flags } #endif // gchar *utf = g_strdup (view->dir); //copy, so that view->dir not clobbered #ifdef E2_VFSTMP //FIXME valid path check for v-dirs //FIXME get path to goto when when dir is v-dir, may include a space-change #endif gdk_threads_enter (); //in case of downstream error message if (e2_fs_get_valid_path (&utf, TRUE E2_ERR_NONE())) g_free (utf); else { //desired path not accessible, go to somewhere that is .. gchar *msg = g_strdup_printf ("Cannot access %s, going to %s instead", view->dir, utf); #ifdef E2_VFSTMP //FIXME cater for any space-change message #endif e2_output_print_error (msg, TRUE); gdk_threads_leave (); E2_PaneRuntime *rt = (view == curr_view) ? curr_pane : other_pane; #ifdef E2_VFSTMP //FIXME if needed PlaceInfo *spacedata = NULL; e2_pane_change_space_pointer (rt, spacedata); #endif e2_pane_change_dir (rt, utf); g_free (utf); return GINT_TO_POINTER (2); //non-NULL to signal no more refresh needed } #ifdef E2_REFRESH_DEBUG printd (DEBUG, "disable refresh, fileview_refresh list"); #endif E2_ListChoice pnum = (view == &app.pane1_view) ? PANE1:PANE2; e2_filelist_disable_one_refresh (pnum); e2_window_set_cursor (GDK_WATCH); gdk_threads_leave (); LISTS_LOCK view->listcontrols.refresh_working = TRUE; LISTS_UNLOCK gchar *local = F_FILENAME_TO_LOCALE (view->dir); //construct relevant items data for store-filler //get a fresh set of directory item-names from source //FIXME if possible, just get relevant items for the refresh // printd (DEBUG, "grab directory names data"); #ifdef E2_VFS VPATH ddata = { local, view->spacedata }; GList *entries = (GList *)e2_fs_dir_foreach (&ddata, #else GList *entries = (GList *)e2_fs_dir_foreach (local, #endif E2_DIRWATCH_CHECK, //this is irrelevant for non-local dirs NULL, NULL, NULL E2_ERR_NONE()); /* each ->data in entries is a FileInfo. They are cleared when no longer needed. Some go into replacement liststore data and are cleared with the store. Others that belong to unchanged items are cleared when it's confirmed that they are the same */ if (!E2DREAD_FAILED (entries)) { guint i, indx, itemcount = g_list_length (entries); GList *member; E2_RefreshInfo *modes = (E2_RefreshInfo *) #ifdef USE_GLIB2_10 //can't use ALLOCATE0 cuz > 1 item g_slice_alloc0 (sizeof (E2_RefreshInfo) * itemcount); #elif defined (USE_GLIB2_8) g_try_malloc0 (sizeof (E2_RefreshInfo) * itemcount); #else calloc (itemcount, sizeof (E2_RefreshInfo)); #endif CHECKALLOCATEDWARN (modes, ) //FIXME properly clean entries list if allocation failed if (modes != NULL) { //FIXME rationalise this with modes creation if (!_e2_fileview_make_all_infos (local, &entries)) { //cleanup of entries (possibly with mixed data types) done downstream //don't goto accessible path until next refresh, if any F_FREE (local); #ifdef USE_GLIB2_10 //can't use DEALLOCATE cuz > 1 item g_slice_free1 (sizeof (E2_RefreshInfo) * itemcount, modes); #else g_free (modes); #endif LISTS_LOCK view->listcontrols.refresh_working = FALSE; LISTS_UNLOCK gdk_threads_enter (); e2_window_set_cursor (GDK_LEFT_PTR); gdk_threads_leave (); e2_filelist_enable_one_refresh (pnum); return NULL; } //even when FAM is working, some things (e.g. treedialog) must poll for //changes, so set times reflecting the completed poll struct stat sb; #ifdef E2_VFS VPATH data = { local, view->spacedata }; if (e2_fs_stat (&data, &sb E2_ERR_NONE())) //through links #else if (e2_fs_stat (local, &sb E2_ERR_NONE())) //through links #endif { printd (WARN, "Unable to stat directory: %s", view->dir); F_FREE (local); #ifdef USE_GLIB2_10 //can't use DEALLOCATE cuz > 1 item g_slice_free1 (sizeof (E2_RefreshInfo) * itemcount, modes); #else g_free (modes); #endif LISTS_LOCK view->listcontrols.refresh_working = FALSE; LISTS_UNLOCK gdk_threads_enter (); e2_window_set_cursor (GDK_LEFT_PTR); gdk_threads_leave (); e2_filelist_enable_one_refresh (pnum); return GINT_TO_POINTER (3); //no more refresh needed } view->dir_mtime = sb.st_mtime; view->dir_ctime = sb.st_ctime; //construct hash for faster matching current and new items // printd (DEBUG, "create matches hash"); //do not free keys when destroying, they're not copies GHashTable *newlookup = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL); i = 0; for (member = entries; member != NULL; member = member->next) { g_hash_table_insert (newlookup, ((FileInfo *)member->data)->filename, GINT_TO_POINTER (i)); i++; } //this bit may separately apply to both views when they're the same i = 0; guint newindx = 0; GList *updates = NULL; FileInfo *currinfoptr, *newinfoptr; gpointer pindx, orig_key; GtkTreeIter iter; GtkTreeModel *mdl = GTK_TREE_MODEL (view->store); // printd (DEBUG, "check all current FileInfos"); //walk current FileInfo's, deleting as we go, logging any that are changed //(there will always be at least one entry, "..") gtk_tree_model_get_iter_first (mdl, &iter); do { loopstart: gtk_tree_model_get (mdl, &iter, FINFO, &currinfoptr, -1); if (g_hash_table_lookup_extended (newlookup, currinfoptr->filename, &orig_key, &pindx)) { indx = (guint) pindx; newinfoptr = (FileInfo *) g_list_nth_data (entries, indx); //FIXME vfs may not have same data as local //FIXME make this faster if (newinfoptr->statbuf.st_atime != currinfoptr->statbuf.st_atime || newinfoptr->statbuf.st_size != currinfoptr->statbuf.st_size || newinfoptr->statbuf.st_mtime != currinfoptr->statbuf.st_mtime || newinfoptr->statbuf.st_ctime != currinfoptr->statbuf.st_ctime || newinfoptr->statbuf.st_mode != currinfoptr->statbuf.st_mode || newinfoptr->statbuf.st_uid != currinfoptr->statbuf.st_uid || newinfoptr->statbuf.st_gid != currinfoptr->statbuf.st_gid ) { modes [indx].oldindx = i; modes [indx].oldmode = REFRESH_CHANGE; modes [indx].newindx = newindx++; updates = g_list_append (updates, newinfoptr); } else { //we don't want to use or add this one, later modes [indx].oldmode = REFRESH_KEEP; //so now get rid of data to prevent leakage #ifdef USE_GLIB2_10 g_slice_free1 (sizeof (FileInfo), newinfoptr); // DEALLOCATE (FileInfo, newinfoptr); #else DEALLOCATE (FileInfo, newinfoptr); #endif } i++; } else { //current item is not hashed i.e. gone from dir gdk_threads_enter (); gboolean more = gtk_list_store_remove (view->store, &iter); gdk_threads_leave (); if (more) goto loopstart; //prevent double-iter-next, i unchanged else break; } } while (gtk_tree_model_iter_next (mdl, &iter)); //tag any additions, and clean up redundant FileInfos to prevent leakage for (i = 0; i < itemcount; i++) { if (modes[i].oldmode == REFRESH_ADD) //not previously detected = default (addition) { modes [i].newindx = newindx++; //this will match the store index when created updates = g_list_append (updates, g_list_nth_data (entries, i)); } } if (updates != NULL) { // printd (DEBUG, "create replacements store"); //make store just with update information //(for each view because the indices are different) GtkListStore *newstore = e2_filelist_fill_store (updates, view); if (newstore != NULL) { //FIXME handle both views as appropriate GtkTreeSortable *sortable = GTK_TREE_SORTABLE (view->store); //speedups ... //remember the current sorting arrangements gtk_tree_sortable_get_sort_column_id (sortable, &view->sort_column, &view->sort_order); //turn off model sorting before any rows are changed/added gtk_tree_sortable_set_sort_column_id (sortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING); //this kills the current selection, no evident way to prevent that // gtk_tree_view_set_model (GTK_TREE_VIEW (view->treeview), NULL); //walk modes array amending/appending relevant rows (maybe both views) // printd (DEBUG, "update current store"); gchar *name, *size, *perm, *owner, *group, *mod, *access, *change, *key; GdkColor *foreground; for (i = 0; i < itemcount; i++) { if (modes[i].oldmode == REFRESH_CHANGE || modes[i].oldmode == REFRESH_ADD) { gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (newstore), &iter, NULL, modes[i].newindx); gtk_tree_model_get (GTK_TREE_MODEL (newstore), &iter, FILENAME, &name, SIZE, &size, PERM, &perm, OWNER, &owner, GROUP, &group, MODIFIED, &mod, ACCESSED, &access, CHANGED, &change, NAMEKEY, &key, FINFO, &newinfoptr, FORECOLOR, &foreground, -1); if (modes[i].oldmode == REFRESH_CHANGE) { gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (view->store), &iter, NULL, modes[i].oldindx); gtk_tree_model_get (GTK_TREE_MODEL (view->store), &iter, FINFO, &currinfoptr, -1); //simply replace all changeable contents gdk_threads_enter (); gtk_list_store_set (view->store, &iter, SIZE, size, PERM, perm, OWNER, owner, GROUP, group, MODIFIED, mod, ACCESSED, access, CHANGED, change, FORECOLOR, foreground, //maybe changed executable status -1); gdk_threads_leave (); //copy this so original can be cleared currinfoptr->statbuf = newinfoptr->statbuf; } else { //append //the new FileInfo will be destroyed with its store //so get a copy for the main liststore // currinfoptr = ALLOCATE (FileInfo); #ifdef USE_GLIB2_10 currinfoptr = (FileInfo *) g_slice_alloc (sizeof (FileInfo)); //currinfoptr = ALLOCATE (FileInfo); #else currinfoptr = ALLOCATE (FileInfo); #endif CHECKALLOCATEDWARN (currinfoptr, ) if (currinfoptr != NULL) { *currinfoptr = *newinfoptr; //gtk >= 2.10 can handle &iter = NULL gdk_threads_enter (); gtk_list_store_insert_with_values (view->store, &iter, -1, FILENAME, name, SIZE, size, PERM, perm, OWNER, owner, GROUP, group, MODIFIED, mod, ACCESSED, access, CHANGED, change, NAMEKEY, key, FINFO, currinfoptr, FORECOLOR, foreground, -1); gdk_threads_leave (); } } g_free (name); g_free (size); g_free (perm); g_free (owner); g_free (group); g_free (mod); g_free (access); g_free (change); g_free (key); } } gdk_threads_enter (); //re-sort the view using the current sort column & direction gtk_tree_sortable_set_sort_column_id (sortable, view->sort_column, view->sort_order); e2_fileview_refilter_list (view); gdk_threads_leave (); // g_idle_add ((GSourceFunc)_e2_fileview_run_hooks, view); //fixme do this when other view is processed gboolean newtimer = (app.used_stores == NULL); //FIXME not all entries member->data are cleared with newstore app.used_stores = g_slist_append (app.used_stores, newstore); if (newtimer) { printd (DEBUG, "setup to clear stores later"); g_idle_add (e2_filelist_clear_old_stores, NULL); } } //no data free, the data are same as entries list data g_list_free (updates); //the store is enough for other view ? FIXME idle ? } else //updates = NULL { //this is normally done in e2_fileview_refilter_list() // printd (DEBUG, "no need to update any item still in filelist"); if (view->row > 0) { guint visible_rows = gtk_tree_model_iter_n_children (view->model, NULL); if (view->row > visible_rows - 1) view->row = visible_rows - 1; //set to last line of list //if there was no selection, or the former selection is gone (after filter or deletion) //we want relative cursor-moves to stay in the same vicinity if (gtk_tree_selection_count_selected_rows (view->selection) == 0) { GtkTreePath *tpath = gtk_tree_path_new_from_indices (view->row, -1); gdk_threads_enter (); //this "marks" the item, but mere selection is not enough for this to work gtk_tree_view_set_cursor (GTK_TREE_VIEW (view->treeview), tpath, NULL, FALSE); gtk_tree_selection_unselect_path (view->selection, tpath); gdk_threads_leave (); gtk_tree_path_free (tpath); } } } view->total_items = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (view->store), NULL) - 1; //omit the ".." entry from the count g_hash_table_destroy (newlookup); //CHECKME better at idle ? #ifdef USE_GLIB2_10 //can't use DEALLOCATE cuz > 1 item g_slice_free1 (sizeof (E2_RefreshInfo) * itemcount, modes); #else g_free (modes); #endif } F_FREE (local); //FIXME at idle? g_list_free (entries); } //else //FIXME warn user about error in reading data, with BGL closed #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, fileview_refresh list"); #endif //signal to the world ... LISTS_LOCK view->listcontrols.refresh_working = FALSE; LISTS_UNLOCK gdk_threads_enter (); e2_window_set_cursor (GDK_LEFT_PTR); gdk_threads_leave (); e2_filelist_enable_one_refresh (pnum); // printd (DEBUG, "finish refresh filelist for %s", view->dir); return GINT_TO_POINTER (1); //normal exit, no more refresh } /* #else //ndef E2_NEWREFRESH / * * @brief reload list store for @a view and re-select relevant items This records the names of currently-selected items in the view, reloads the directory contents (which clears the old selection), finds the rows in the new store that match the selected names, and selects them. If the directory associated with @a view is no longer accessible, a suitable alternative is found and displayed. Downstream functions expect gtk's BGL to be closed, hence this func does too = gdk mutex management must be handled by the caller as appropriate. @param view data structure for view being processed @return * / void e2_fileview_refresh_list (ViewInfo *view) { if (view->listing) return; //the filelist is still being processed from last time printd (DEBUG, "start refresh filelist for %s", view->dir); gchar *utf = g_strdup (view->dir); //copy, so that view->dir not clobbered #ifdef E2_VFSTMP //CHECKME confirm valid path func works for v-dirs //FIXME goto dir when not mounted local #endif if (e2_fs_get_valid_path (&utf, TRUE E2_ERR_NONE())) g_free (utf); else { //desired path not accessible, go to somewhere that is .. gchar *msg = g_strdup_printf ("Cannot access %s, going to %s instead", view->dir, utf); #ifdef E2_VFSTMP //FIXME cater for space-change message #endif e2_output_print_error (msg, TRUE); #ifdef E2_VFSTMP //FIXME if needed PlaceInfo *spacedata = ??; e2_pane_change_space_pointer (rt, spacedata); #endif E2_PaneRuntime *rt = (view == curr_view ) ? curr_pane : other_pane; e2_pane_change_dir (rt, utf); g_free (utf); return; } if (view->selected_names != NULL) { //setup to get rid of any old hash g_idle_add_full (G_PRIORITY_LOW, (GSourceFunc) e2_fileview_treehash_free, view->selected_names, NULL); } //log the names of selected items in this view // printd (DEBUG, "log any selected items"); view->selected_names = e2_fileview_log_selected_names (view); //clear and refill the store //FIXME support incremental updates // printd (DEBUG, "replace list content"); view->refreshtype = E2_REFRESH; if (!e2_fileview_prepare_list (mounted, view)) return; GtkTreeModel *mdl = gtk_tree_view_get_model (GTK_TREE_VIEW (view->treeview)); GtkTreeIter iter; if (gtk_tree_model_get_iter_first (mdl, &iter)) { //restore scroll positions / * Not clear why this works, but .... the adjustments' values are unchanged, but the sw doesn't know about them unless signals are issued * / GtkAdjustment *adj; adj = gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (view->treeview)); gtk_adjustment_value_changed (adj); // adj = gtk_tree_view_get_hadjustment (GTK_TREE_VIEW (view->treeview)); // gtk_adjustment_value_changed (adj); } if (view->selected_names != NULL) { // printd (DEBUG, "reselect items"); // clear out any old selection // gtk_tree_selection_unselect_all (view->selection); //try to reselect items, then at least clean the hash e2_fileview_reselect_names (view); // printd (DEBUG, "finish reselection for %s", view->dir); } printd (DEBUG, "finish refresh filelist for %s", view->dir); } #endif //ndef E2_NEWREFRESH */ /** @brief create filelist for the pane related to @a view This creates the backend list store, and related scrolled window containing a treeview @param view runtime data for view being processed @return a scrolled window widget */ GtkWidget *e2_fileview_create_list (ViewInfo *view) { //create liststore framework for the pane view->store = e2_filelist_make_store (); //setup column-order array gint array_row = (view == app.pane1.view) ? 0 : 1; e2_fileview_translate_cols_array (stored_col_order[array_row], displayed_col_order[array_row], MAX_COLUMNS); gint *width_array = col_width_store[array_row]; gint *order_array = displayed_col_order[array_row]; //element of cache-name strings gchar *panename = (view == app.pane1.view) ? app.pane1.name : app.pane2.name; /*now create tree view column order is determined by array displayed_col_order[]. columns are named "0" to "7" for later searching */ gint i, m; gchar *option_name; E2_OptionSet *set; gboolean show_this; GtkCellRenderer *renderer; GtkTreeViewColumn *column; GtkTreeSelection *sel; gchar *fontstr = (e2_option_bool_get ("custom-list-font")) ? e2_option_str_get ("list-font") : NULL; //NULL will cause default font static gchar id[] = {'0', '\0'}; //for column header titles, used to check which column //unfiltered model needed for getting the sortable GtkTreeModel *mdl = GTK_TREE_MODEL (view->store); view->model = gtk_tree_model_filter_new (mdl, NULL); GtkWidget *tvw = gtk_tree_view_new_with_model (view->model); g_object_unref (G_OBJECT (view->store)); //kill the ref from model creation g_object_unref (G_OBJECT (view->model)); //kill the ref from treeview creation view->filtered_before = FALSE; //start with fresh filters //save it for others to use view->treeview = tvw; GtkTreeSortable *sortable = GTK_TREE_SORTABLE (view->store); //allow unsorted display using GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID // gtk_tree_sortable_set_default_sort_func (sortable, NULL, NULL, NULL); //set general treeview properties gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (tvw), TRUE); gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (tvw), //useless with managed backcolors e2_option_bool_get ("panes-hinted")); //#ifdef USE_GTK2_10 //FIXME use this if it's as functional as E2_ALTLEFTMOUSE, seems not so // gtk_tree_view_set_rubber_banding (GTK_TREE_VIEW (tvw), TRUE); //"drag-selection" //#endif //cache the view's selection pointer CHECKME maybe a problem with filtermodel view->selection = sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (tvw)); gtk_tree_selection_set_mode (sel, GTK_SELECTION_MULTIPLE); #ifdef E2_SELTXT_RECOLOR if (e2_option_bool_get ("selectedtext-colorchange")) gtk_tree_selection_set_select_function (sel, (void *) _e2_fileview_selection_func, view, NULL); #endif /* //when both panes are "same", make the slave-pane columns un-draggable gboolean fixed_order = ( (e2_option_bool_get ("pane1-uses-other") && view == app.pane1.view) || (e2_option_bool_get ("pane2-uses-other") && view == app.pane2.view) ); */ //set columns' properties for (i = 0; i < MAX_COLUMNS; i++) { m = order_array[i]; //get the model/'external' index gtk_tree_sortable_set_sort_func (sortable, m, e2_all_columns[m].sort_func, &view->sort_order, NULL); //get the column visibility option_name = g_strdup_printf ("%s-show-column%d", panename, m); // e2_option_bool_get defaults to FALSE set = e2_option_get (option_name); show_this = (set != NULL) ? e2_option_bool_get_direct (set) : TRUE; g_free (option_name); //create header widget, to support styling and avoid gtk bug with arrows GtkWidget *headerbox = gtk_hbox_new (FALSE, 1); GtkWidget *label = gtk_label_new (gettext(e2_all_columns[m].title)); //same title for each pane //expand = TRUE prevents col width < title width + sort indicator (if any) //spacing 0 when label expand = FALSE (or ese it crashes when col is too narrow to show all) gtk_box_pack_start (GTK_BOX(headerbox), label, FALSE, TRUE, 0); gtk_widget_show (label); if (m == FILENAME) //remember the Filename column label in case we need to use it for active-pane flagging view->name_label = GTK_LABEL (label); //set the current sort arrow GtkArrowType arrow; if (m == view->sort_column && view->sort_order == GTK_SORT_DESCENDING) arrow = GTK_ARROW_UP; else arrow = GTK_ARROW_DOWN; view->sort_arrows[m] = gtk_arrow_new (arrow, GTK_SHADOW_IN); //this doesn't show the indicator at the end of the header ! (and different exp, fill don't help) gtk_box_pack_end (GTK_BOX(headerbox), view->sort_arrows[m], FALSE, FALSE, 0); gtk_widget_show (headerbox); renderer = gtk_cell_renderer_text_new (); g_object_set (G_OBJECT (renderer), "yalign", 0.0, "ypad", 0, //supposedly default, but this makes the rows closer "font", fontstr, //this property not stored in model, as it doesn't change on the fly ... NULL); // gtk_cell_renderer_text_set_fixed_height_from_font // (GTK_CELL_RENDERER_TEXT (renderer), 1); //right-align the size column only if (m == SIZE) g_object_set (G_OBJECT (renderer), "xalign", 1.0, "xpad", 0, NULL); id[0] = m+'0'; column = gtk_tree_view_column_new_with_attributes ( id, //this is for seaching & sorting, displayed title is in widget label renderer, "text", m, "foreground-gdk", FORECOLOR, "cell-background-gdk", BACKCOLOR, //this column is generally NULLed, except for drop-target highlighting NULL); gint thiswidth = width_array[m]; //doesn't like 0 widths set by gtk when a col is hidden if (thiswidth == 0) thiswidth = e2_all_columns[m].size; g_object_set (column, "sizing", GTK_TREE_VIEW_COLUMN_FIXED, "resizable", TRUE, "fixed-width", thiswidth, //width_array[m], "visible", show_this, "clickable", TRUE, // "reorderable", !fixed_order, "reorderable", TRUE, "widget", headerbox, NULL); gtk_tree_view_append_column (GTK_TREE_VIEW (tvw), column); #ifdef EDIT_INPLACE if (m == FILENAME) { //setup for in-cell name-changing g_object_set (G_OBJECT (renderer), "editable", TRUE, NULL); g_signal_connect (G_OBJECT (renderer), "edited", G_CALLBACK (_e2_fileview_name_edited_cb), view); g_signal_connect (G_OBJECT (renderer), "editing-started", G_CALLBACK (_e2_fileview_edit_start_cb), view); g_signal_connect (G_OBJECT (renderer), "editing-canceled", G_CALLBACK (_e2_fileview_edit_cancel_cb), view); } #endif g_signal_connect (column, "clicked", G_CALLBACK (_e2_fileview_column_header_clicked_cb), view); //NB undocumented ->button //workaround for detecting header clicks to distinguish from drags g_signal_connect (column->button, "button-press-event", G_CALLBACK (_e2_fileview_column_header_buttonpress_cb), view); g_signal_connect (column->button, "button-release-event", G_CALLBACK (_e2_fileview_column_header_buttonrel_cb), view); } //setup inital sort parameters, ascending name col // view->sort_order = GTK_SORT_ASCENDING; // gtk_widget_show (view->sort_arrows[0]); //inital sort parameters set from cache gtk_tree_sortable_set_sort_column_id (sortable, view->sort_column, view->sort_order); gtk_widget_show (view->sort_arrows[view->sort_column]); //FIXME do this once, not for both panes button2updir = e2_option_bool_get ("button2-updir"); g_signal_connect (tvw, "columns-changed", G_CALLBACK(_e2_fileview_col_change_cb), view); //for E2_ALTLEFTMOUSE, double-clicks are handled in the click callback //BUT gtk issues this signal when Enter is pressed during a type-ahead //search, which we want g_signal_connect (tvw, "row-activated", G_CALLBACK (_e2_fileview_row_activated_cb), view); //by default, type-ahead searching is enabled on column 0 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (tvw), (GtkTreeViewSearchEqualFunc)_e2_fileview_match_filename, view, NULL); //DnD connections // gtk_drag_source_set (tvw, GDK_BUTTON1_MASK, target_table, n_targets, //-1 if XDS is last, // GDK_ACTION_COPY); //can't use 2 of this fn, it seems gtk_drag_source_set (tvw, GDK_BUTTON1_MASK | GDK_BUTTON2_MASK, target_table, n_targets, //-1, //the last target (XDS) is supported for dest only GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK); gtk_drag_dest_set (tvw, GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP, target_table, n_targets, GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK); g_signal_connect (G_OBJECT (tvw), "drag-begin", G_CALLBACK (e2_dnd_drag_begin_cb), view); g_signal_connect (G_OBJECT (tvw), "drag-data-get", G_CALLBACK (e2_dnd_drag_data_get_cb), view); g_signal_connect (G_OBJECT (tvw), "drag-motion", G_CALLBACK (e2_dnd_drag_motion_cb), view); g_signal_connect (G_OBJECT (tvw), "drag-leave", G_CALLBACK (e2_dnd_drag_leave_cb), view); //needed if GTK_DEST_DEFAULT_DROP not set, above // g_signal_connect (G_OBJECT (tvw), "drag-drop", // G_CALLBACK (e2_dnd_drag_drop_cb), view); // g_signal_connect (G_OBJECT (tvw), "drag-drop", // G_CALLBACK(e2_dnd_drag_drop_cb), view); g_signal_connect (G_OBJECT (tvw), "drag-data-received", G_CALLBACK(e2_dnd_drag_data_received_cb), view); //FIXME do these once only // atom_text_uri_list = gdk_atom_intern (target_table[0,0], FALSE); // atom_text_plain = gdk_atom_intern (target_table[1,0], FALSE); // atom_XdndDirectSave0 = gdk_atom_intern (target_table[2,0], FALSE); // g_signal_connect (G_OBJECT (tvw), "drag-data-delete", // G_CALLBACK (e2_dnd_drag_delete_cb), view); g_signal_connect (G_OBJECT (tvw), "button-press-event", G_CALLBACK (_e2_fileview_button_press_cb), view); g_signal_connect (G_OBJECT (tvw), "button-release-event", G_CALLBACK (_e2_fileview_button_release_cb), view); //pick up any key-bindings before the general key-press //BUT CHECKME order probably does not matter gchar *category = g_strconcat(_C(17),".",_C(32),NULL); e2_keybinding_register (category, tvw); //_("general.panes g_free (category); // g_signal_connect (G_OBJECT (tvw), "popup-menu", handled by keypress cb now // G_CALLBACK (_e2_fileview_popup_cb), view); g_signal_connect (G_OBJECT (tvw), "key-press-event", G_CALLBACK (_e2_fileview_key_press_cb), view); g_signal_connect (G_OBJECT (tvw), "cursor-changed", G_CALLBACK (_e2_fileview_cursor_change_cb), view); GtkWidget *sw = e2_widget_get_sw (GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, GTK_SHADOW_ETCHED_IN); gtk_scrolled_window_set_placement (GTK_SCROLLED_WINDOW (sw), e2_option_int_get ("scrollbar-position")); gtk_container_add (GTK_CONTAINER (sw), tvw); gtk_widget_show (tvw); gtk_widget_show (sw); return sw; } emelfm2-0.4.1/src/e2_bookmark.c0000600000175000017500000005105010776374631015154 0ustar cairocairo/* $Id: e2_bookmark.c 853 2008-04-07 10:38:17Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelfm2. emelfm2 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 3, or (at your option) any later version. emelfm2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/e2_bookmark.c @brief bookmark functions */ #include "e2_bookmark.h" #include #include "e2_dialog.h" #include "e2_task.h" #include "e2_option_tree.h" static E2_OptionSet *set; /*****************/ /***** utils *****/ /*****************/ /** @brief re-create all toolbars which have bookmarks @return */ static void _e2_bookmark_recreate_toolbars (void) { E2_ToolbarData **thisbar; for (thisbar = app.bars; *thisbar != NULL; thisbar++) { if ((*thisbar)->rt->has_bookmarks) //no need to check here for bar presence e2_toolbar_recreate ((*thisbar)->rt); } } /** @brief delete bookmark at @a iter @param iter pointer to tree iter for row to be deleted @return */ static void _e2_bookmark_delete_mark (GtkTreeIter *iter) { e2_option_tree_del_direct (set, iter); _e2_bookmark_recreate_toolbars (); if ((app.context_menu != NULL) && (GTK_IS_MENU (app.context_menu))) gtk_menu_popdown (GTK_MENU (app.context_menu)); } /** @brief refresh the bookmarks menus @return */ static void _e2_bookmark_apply_new (void) { //FIXME = find a smarter way to do this _e2_bookmark_recreate_toolbars (); } /*********************/ /***** callbacks *****/ /*********************/ /** @brief (de)sensitize option tree buttons for selected option tree row Config dialog page buttons are de-sensitized if the row is the toolbar 'parent' @param selection pointer to selection @param model UNUSED @param path @param path_currently_selected UNUSED @param set data struct for the keybings option @return TRUE always (the row is always selectable) */ static gboolean _e2_bookmark_tree_selection_check_cb (GtkTreeSelection *selection, GtkTreeModel *model, GtkTreePath *path, gboolean path_currently_selected, E2_OptionSet *set) { GtkTreeIter iter; if (gtk_tree_model_get_iter (set->ex.tree.model, &iter, path)) { gchar *label; gtk_tree_model_get (set->ex.tree.model, &iter, 0, &label, -1); gboolean result = ! g_str_equal (label, _C(39)); g_free (label); GtkTreeView *view = gtk_tree_selection_get_tree_view (selection); e2_option_tree_adjust_buttons (view, result); } return TRUE; } /** @brief ensure that cells in the same row as any category name are hidden Checks whether @a iter in @a model has the name of the taskbar 'parent' in column 0. If so, the cell content is hidden, @param model treemodel for the bookmarks option tree @param iter pointer to iter with data for the current model row @param cell cell renderer UNUSED @param data pointer to data UNUSED @return TRUE (cell is visible) if row is not the task bar parent */ static gboolean _e2_bookmark_visible_check_cb (GtkTreeModel *model, GtkTreeIter *iter, GtkCellRenderer *cell, gpointer data) { gchar *label; gtk_tree_model_get (model, iter, 0, &label, -1); gboolean result = !g_str_equal (label, _C(39)); g_free (label); return result; } /** @brief decide whether tree row is draggable Checks whether column 0 of the current row has the string which is the name of the "tool bar" 'parent' row. If so, the row is not draggable @param drag_source GtkTreeDragSource data struct @param path tree path to a row on which user is initiating a drag @return TRUE if the row can be dragged */ static gboolean _e2_bookmark_tree_draggable_check_cb (GtkTreeDragSource *drag_source, GtkTreePath *path) { if (!GTK_IS_TREE_MODEL (drag_source)) return TRUE; GtkTreeModel *model = GTK_TREE_MODEL (drag_source); GtkTreeIter iter; if (gtk_tree_model_get_iter (model, &iter, path)) { gchar *label; gtk_tree_model_get (model, &iter, 0, &label, -1); gboolean result = !g_str_equal (label, _C(39)); g_free (label); return result; } return TRUE; } /** @brief process result from delete-confirmation dialog @param dialog UNUSED the confirmation-dialog @param response the response from the dialog @param ref treerow reference for the bookmark to be deleted @return */ static void _e2_bookmark_confirm_response_cb (GtkDialog *dialog, gint response, GtkTreeRowReference *ref) { if (response == GTK_RESPONSE_YES) { if (ref != NULL) { GtkTreePath *path = gtk_tree_row_reference_get_path (ref); if (path != NULL) { GtkTreeIter iter; if (gtk_tree_model_get_iter (set->ex.tree.model, &iter, path)) _e2_bookmark_delete_mark (&iter); } } } gtk_widget_destroy (GTK_WIDGET (dialog)); } /** @brief add an item (directory) to the bookmarks list This is a callback. The activated item @a widget may be a menu item (eg in the panebar bookmarks sub-menu) or a context menu item (file pane or bookmarks widget). In the former case, it will have been processed as an action, and will have a string argument @a arg, with "pane1" or "pane2", and possibly including one of "top", "child" (othewise the add is treated as "after"). When @a widget is a context menu item, @a arg will be NULL. This function needs to know which pane to use for the path to add to the list. For menu callbacks, @a arg needs to include "pane1" or "pane2" as appropriate. For context-menu callbacks, @a widget will have associated data "bookmark-pane" = 0 (active), 1 or 2. @param widget NULL if a bookmarks menu item widget was selected, else a context menu item widget @param arg string which indicates where to put the new bookmark, or NULL @return */ static void _e2_bookmark_add_cb (GtkWidget *widget, gchar *arg) { gboolean before = FALSE; gboolean parent = FALSE; guint pane = E2PANECUR; //default to active pane if ((arg != NULL) && (*arg != '\0')) { if (strstr (arg, _A(11))) //_(pane1 pane = E2PANE1; else if (strstr (arg, _A(12))) //_(pane2 pane = E2PANE2; if (strstr (arg, _A(114))) //_(top before = TRUE; if (strstr (arg, _A(104))) //_(child { parent = TRUE; before = TRUE; } } else if (widget != NULL) pane = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (widget), "bookmark-pane")); //get the relevant dir to add to the marks list gchar *dirname; switch (pane) { #ifdef E2_VFSTMP //FIXME dirs when not mounted local #else case E2PANE1: dirname = app.pane1_view.dir; break; case E2PANE2: dirname = app.pane2_view.dir; break; default: dirname = curr_view->dir; #endif break; } gchar *basename = g_path_get_basename (dirname); //double any '_' CHECKME what's this about ? /* if (strchr (basename, '_')) { gchar *freeme = basename; basename = e2_utils_str_replace (basename, "_", "__"); g_free (freeme); } */ void *options[4]; options[0] = basename; //menu label options[1] = ""; //no icon //strip the trailing / for paths other than root gchar *s = NULL; gint len = strlen (dirname); if (len > 1) { s = g_strdup (dirname); //non-NULL = freeme later *(s+len-1) = '\0'; options[2] = s; } else options[2] = dirname; //tip = path options[3] = options[2]; //path // E2_OptionSet *set = e2_option_get ("bookmarks"); //internal name USE THE GLOBAL VALUE GtkTreeIter iter; GtkTreeIter iter2; GtkTreePath *path = NULL; if (widget != NULL) path = g_object_get_data (G_OBJECT (widget), "parent-mark"); if ((path != NULL) && gtk_tree_model_get_iter (set->ex.tree.model, &iter2, path)) e2_option_tree_add (set, &iter, &iter2, !parent, before, 4, options); else //with no parent, the behaviour of "before" is vice versa e2_option_tree_add (set, &iter, NULL, FALSE, !before, 4, options); _e2_bookmark_recreate_toolbars (); if (s != NULL) g_free (s); g_free (basename); } /** @brief callback for deleting a bookmark (with any children) @param widget UNUSED the widget which was activated @param path treestore path of bookmark to be deleted @return */ static void _e2_bookmark_delete_cb (GtkWidget *widget, GtkTreePath *path) { GtkTreeIter iter; if (gtk_tree_model_get_iter (set->ex.tree.model, &iter, path)) { gint children = gtk_tree_model_iter_n_children (set->ex.tree.model, &iter); if (e2_option_bool_get ("bookmarks-confirm-delete") || ((children > 0) && (e2_option_bool_get ("bookmarks-confirm-multi-delete"))) ) { gchar *name; gtk_tree_model_get (set->ex.tree.model, &iter, 0, &name, -1); gchar *question; gchar *prompt = _("Are you sure that you want to delete the bookmark"); //we fudge here on translating the trailing '?' if (children > 0) question = g_strdup_printf ("%s '%s' %s %d %s ?", prompt, name, _("and"), children, (children == 1) ? _("child") : _("children")); else question = g_strdup_printf ("%s '%s' ?", prompt, name); GtkWidget *dialog = e2_dialog_create (GTK_STOCK_DIALOG_QUESTION, question, _("confirm bookmark delete"), _e2_bookmark_confirm_response_cb, gtk_tree_row_reference_new (set->ex.tree.model, path)); e2_dialog_set_negative_response (dialog, GTK_RESPONSE_NO); e2_dialog_show (dialog, app.main_window, 0, &E2_BUTTON_NO, &E2_BUTTON_YES, NULL); g_free (question); g_free (name); } else //no confirmation _e2_bookmark_delete_mark (&iter); } } /** @brief button click callback Middle button will open mark in other pane, and make that pane active, if the relevant options are in force. Right button will create and pop up a context menu, if not blocked (blocks will be in place for toolbar buttons, but not for (sub)menu items) Context menu items 'add' and 'add-child' need to know which pane (directory) to use for the mark @param widget clicked button or menu item @param event gdk event data struct @param path pointer to bookmarks set treemodel path to the string which holds the bookmark directory path @return TRUE (block further handlers) if certain btn 2 or 3 conditions are satisfied */ gboolean e2_bookmark_click_cb (GtkWidget *widget, GdkEventButton *event, GtkTreePath* path) { printd (DEBUG, "e2_bookmark_click_cb"); //get which pane's menu was clicked (NULL for active pane) gpointer pane = g_object_get_data (G_OBJECT (widget), "bookmark-pane"); switch (event->button) { case 2: { if (e2_option_bool_get ("bookmarks-button2-other")) { gint other = (curr_pane == &app.pane1) ? 2 : 1; if (GPOINTER_TO_INT (pane) != other) { gchar *mark = g_object_get_data (G_OBJECT (widget), "bookmark-path"); if (mark != NULL) { #ifdef E2_VFSTMP //FIXME bookmark parsing and setting for v-dirs if (0) e2_pane_change_space_byuri (other_pane, "some string"); #endif e2_pane_change_dir (other_pane, mark); //CHECKME mark utf8 if (e2_option_bool_get ("bookmarks-focus-after-open")) e2_pane_activate_other (); return TRUE; } } } } break; case 3: { //check whether context menu is valid for this widget gpointer context = g_object_get_data (G_OBJECT (widget), "with-context"); if (context != NULL && GPOINTER_TO_INT (context) == 1) return FALSE; printd (DEBUG, "bookmark menu"); GtkWidget *menu = gtk_menu_new (); GtkWidget *menuitem; menuitem = e2_menu_add (menu, _("_Add after"), GTK_STOCK_ADD, _("Bookmark the current directory after the selected bookmark"), _e2_bookmark_add_cb, NULL); g_object_set_data (G_OBJECT (menuitem), "parent-mark", path); //pass through the pane no. (or NULL for default) to use when adding an item g_object_set_data (G_OBJECT (menuitem), "bookmark-pane", pane); menuitem = e2_menu_add (menu, _("Add as _child"), GTK_STOCK_INDENT, _("Bookmark the current directory a a child of the selected bookmark"), _e2_bookmark_add_cb, _A(104)); //_(child g_object_set_data (G_OBJECT (menuitem), "parent-mark", path); //pass through the pane no. (or NULL for default) to use when adding an item g_object_set_data (G_OBJECT (menuitem), "bookmark-pane", pane); e2_menu_add (menu, _("_Delete"), GTK_STOCK_DELETE, _("Delete the selected bookmark, and its children if any"), _e2_bookmark_delete_cb, path); /* just rely on the edit item in the context menu, now e2_menu_add_separator (menu); e2_menu_add (menu, _("_Edit"), GTK_STOCK_PREFERENCES, _("Open the bookmarks configuration dialog"), _e2_bookmarks_configure, NULL); */ e2_menu_popup (menu, event->button, event->time); return TRUE; } break; default: break; } return FALSE; } /*******************/ /***** actions *****/ /*******************/ /** @brief add item to bookmarks menu @param from pointer to widget activated to initiate the action @param art pointer to runtime data for the action @return TRUE always */ static gboolean _e2_bookmark_add (gpointer from, E2_ActionRuntime *art) { _e2_bookmark_add_cb (NULL, (gchar *) art->data); return TRUE; } /** @brief show bookmarks menu in active pane @param from pointer to widget activated to initiate the action @param art pointer to runtime data for the action @return TRUE if menu was created */ static gboolean _e2_bookmark_show (gpointer from, E2_ActionRuntime *art) { E2_OptionSet *set = e2_option_get ("bookmarks"); GtkTreeIter iter; if (gtk_tree_model_get_iter_first (set->ex.tree.model, &iter)) { gchar *action_name = g_strconcat (_A(0),".",_A(24),NULL); E2_Action *action = e2_action_get (action_name); g_free (action_name); GtkWidget *menu = gtk_menu_new (); e2_menu_create_bookmarks_menu (action, set, menu, &iter); guint32 time = gtk_get_current_event_time (); gtk_menu_popup (GTK_MENU (menu), NULL, NULL, (GtkMenuPositionFunc) e2_fileview_set_menu_position, curr_view->treeview, 0, time); return TRUE; } return FALSE; } /** @brief open bookmark @a path, in pane @a num Action data includes pointerized 0 (from "" action), 1 or 2 (pane number), or NULL for current pane Action data includes path of directory to open, utf8 string Downstream functions expect BGL to be closed @param from pointer to widget activated to initiate the action @param art pointer to runtime data for the action @return TRUE always */ gboolean e2_bookmark_open (gpointer from, E2_ActionRuntime *art) { guint panenum = GPOINTER_TO_UINT (art->action->data); gchar *path = (gchar *) art->data; printd (DEBUG, "e2_bookmark_open (num:%d,path:%s)", panenum, path); switch (panenum) { case E2PANE1: #ifdef E2_VFSTMP //FIXME handle case where pane is non-mounted if (0) e2_pane_change_space_byuri (&app.pane1, "someplace"); #endif e2_pane_change_dir (&app.pane1, path); if (e2_option_bool_get ("bookmarks-focus-after-open") && curr_pane != &app.pane1) e2_pane_activate_other (); break; case E2PANE2: #ifdef E2_VFSTMP //FIXME handle case where pane is non-mounted if (0) e2_pane_change_space_byuri (&app.pane2, "someplace"); #endif e2_pane_change_dir (&app.pane2, path); if (e2_option_bool_get ("bookmarks-focus-after-open") && curr_pane != &app.pane2) e2_pane_activate_other (); break; default: #ifdef E2_VFSTMP //FIXME handle case where pane is non-mounted if (0) e2_pane_change_space_byuri (curr_pane, "someplace"); #endif e2_pane_change_dir (NULL, path); break; } return TRUE; } /** @brief open a dialog showing bookmarks config data @param from pointer to widget activated to initiate the action @param art pointer to runtime data for the action @return TRUE if the dialog was created */ static gboolean _e2_bookmarks_configure (gpointer from, E2_ActionRuntime *art) { return ( e2_config_dialog_single ("bookmarks", _e2_bookmark_apply_new, TRUE) //internal name, no translation != NULL); } /******************/ /***** public *****/ /******************/ /* this used only at session end, don't bother void e2_bookmark_clean () { // e2_action_unregister (_A(21)); //_("")); } */ /** @brief register bookmark-related actions @return */ void e2_bookmark_actions_register (void) { gchar *action_name = g_strconcat(_A(10),".",_A(21),NULL); //pane_active. e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_bookmark_show, NULL, FALSE); action_name = g_strconcat(_A(0),".",_A(26),NULL); //bookmark.add e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_bookmark_add, NULL, TRUE); action_name = g_strconcat(_A(0),".",_A(24),NULL); //bookmark. = not pane specific e2_action_register (action_name, E2_ACTION_TYPE_BOOKMARKS, e2_bookmark_open, NULL, TRUE, //data = pointerised 0 = current pane E2_ACTION_EXCLUDE_ACCEL, NULL); action_name = g_strconcat(_A(2),".",_C(1),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_bookmarks_configure, NULL, FALSE); } /** @brief install default tree options for boookmarks This function is called only if the default is missing from the config file @param set pointer to set data @return */ static void _e2_bookmark_tree_defaults (E2_OptionSet *set) { e2_option_tree_setup_defaults (set, g_strdup("bookmarks=<"), //internal name g_strconcat(_C(39),"|||",NULL), //needs to be same as arg to taskbar item - see e2_toolbar.c g_strconcat("\t","",_("_home"),"|gtk-home|$HOME|~",NULL), //this next is too specific for a default value // g_strconcat("\t",_("down"),"||",_("/mnt/downloads"),"|"G_DIR_SEPARATOR_S"mnt"G_DIR_SEPARATOR_S"downloads",NULL), g_strconcat("\t",_("cdrom"),"|gtk-cdrom|",G_DIR_SEPARATOR_S"media"G_DIR_SEPARATOR_S"cdrom", "|"G_DIR_SEPARATOR_S"media"G_DIR_SEPARATOR_S"cdrom",NULL), g_strconcat(G_DIR_SEPARATOR_S"||",_("root"),"|"G_DIR_SEPARATOR_S,NULL), g_strconcat(_("home"),"||",_("Your home directory"),"|~",NULL), g_strconcat(_("media"),"||",G_DIR_SEPARATOR_S"media","|"G_DIR_SEPARATOR_S"media",NULL), //_I( no point in translating tip = path g_strconcat(_("mnt"),"||",G_DIR_SEPARATOR_S"mnt","|"G_DIR_SEPARATOR_S"mnt",NULL), //ditto g_strconcat(_("usr"),"||",G_DIR_SEPARATOR_S"usr","|"G_DIR_SEPARATOR_S"usr",NULL), //ditto g_strconcat(_("usr/local"),"||",G_DIR_SEPARATOR_S"usr"G_DIR_SEPARATOR_S"local", "|"G_DIR_SEPARATOR_S"usr"G_DIR_SEPARATOR_S"local",NULL), //ditto g_strconcat(_("trash"),"||",_("default trash directory"), "|~"G_DIR_SEPARATOR_S".local"G_DIR_SEPARATOR_S"share"G_DIR_SEPARATOR_S"Trash"G_DIR_SEPARATOR_S"files", NULL), g_strdup(">"), NULL); } /** @brief register bookmark-related options @return */ void e2_bookmark_options_register (void) { //no screen rebuilds needed after any change to these options // E2_OptionSet * SET THE GLOBAL VALUE set = e2_option_tree_register ("bookmarks", _C(1), _C(1), //_("bookmarks" NULL, _e2_bookmark_tree_selection_check_cb, _e2_bookmark_tree_draggable_check_cb, E2_OPTION_TREE_UP_DOWN | E2_OPTION_TREE_ADD_DEL, E2_OPTION_FLAG_BASIC); e2_option_tree_add_column (set, _("Label"), E2_OPTION_TREE_TYPE_STR, 0, G_DIR_SEPARATOR_S, 0, NULL, NULL); e2_option_tree_add_column (set, _("Icon"), E2_OPTION_TREE_TYPE_ICON, 0, "", 0, _e2_bookmark_visible_check_cb, NULL); e2_option_tree_add_column (set, _("Tooltip"), E2_OPTION_TREE_TYPE_STR, 0, "", 0, _e2_bookmark_visible_check_cb, NULL); e2_option_tree_add_column (set, _("Path"), E2_OPTION_TREE_TYPE_STR, 0, G_DIR_SEPARATOR_S, 0, _e2_bookmark_visible_check_cb, NULL); e2_option_tree_create_store (set); e2_option_tree_prepare_defaults (set, _e2_bookmark_tree_defaults); gchar* group_name = g_strconcat(_C(1),".",_C(26),":",_C(25),NULL); //_("bookmarks.options:miscellaneous" e2_option_bool_register ("bookmarks-button2-other", group_name, _("open bookmark in other pane on middle-button click"), _("Clicking the middle mouse button on a bookmark will open it in the other file pane"), NULL, TRUE, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP); e2_option_bool_register ("bookmarks-focus-after-open", group_name, _("focus file pane after opening a bookmark in it"), _("After opening a bookmark in the inactive file pane, that pane will become the active one"), NULL, TRUE, E2_OPTION_FLAG_ADVANCED); group_name = g_strconcat(_C(1),".",_C(26),":",_C(7),NULL); //_("bookmarks.options:confirmation" e2_option_bool_register ("bookmarks-confirm-delete", group_name, _("confirm any delete of a selected bookmark"), _("You will be asked to confirm, before deleting any bookmark"), "bookmarks-confirm-multi-delete", FALSE, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP); e2_option_bool_register ("bookmarks-confirm-multi-delete", group_name, _("confirm any delete of multiple bookmarks"), _("You will be asked to confirm, before deleting any bookmark that has 'children'"), NULL, TRUE, E2_OPTION_FLAG_ADVANCED); } emelfm2-0.4.1/src/config/0000700000175000017500000000000011015120161014025 5ustar cairocairoemelfm2-0.4.1/src/config/e2_option_sel.h0000600000175000017500000000301711010340377016752 0ustar cairocairo/* $Id: e2_option_sel.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_OPTION_SEL_H__ #define __E2_OPTION_SEL_H__ #include "emelfm2.h" #include "e2_option.h" GtkWidget *e2_option_sel_add_menu_widget (GtkWidget *controller, GtkWidget *menu, E2_OptionSet *set, gpointer func, gpointer data); void e2_option_sel_add_widget (GtkWidget *dialog, GtkWidget *box, GtkSizeGroup *size_group, E2_OptionSet *set); E2_OptionSet *e2_option_sel_register (gchar *name, gchar *group, gchar *desc, gchar *tip, gchar *depends, gint def, const gchar **values, E2_OptionFlags flags); gint e2_option_sel_set (gchar *option, gint value); gint e2_option_sel_set_direct (E2_OptionSet *set, gint value); gint e2_option_sel_get (gchar *option); gint e2_option_sel_get_direct (E2_OptionSet *set); #endif // ndef __E2_OPTION_SEL_H__ emelfm2-0.4.1/src/config/e2_option.c0000600000175000017500000010000210667417456016116 0ustar cairocairo/* $Id: e2_option.c 641 2007-09-05 03:14:54Z tpgww $ Copyright (C) 2003-2007 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/config/e2_option.c @brief general option handling This file contains general functions to register, unregister and set options. */ /** \page options the configuration system ToDo - description of how options work */ #include "emelfm2.h" #include #include "e2_fs.h" #include "e2_option.h" #include "e2_option_tree.h" #include "e2_action.h" #include "e2_task.h" #include "e2_filetype.h" #include "e2_plugins.h" #include "e2_cl_option.h" #include "e2_filelist.h" static void _e2_option_clean1 (E2_OptionSet *set); /******************/ /***** public *****/ /******************/ /** @brief Initialize the config directory pointer It's intended that config dirs be native, not virtual The config directory pointer (e2_cl_options.config_dir) is set from command line or default dir. If the dir doesn't exist, create it if possible. Any problem results in an appropriate error message. @return TRUE if dir exists or is successfully created, else FALSE */ gboolean e2_option_set_config_dir (void) { //check if the config dir is really there gchar *local = F_FILENAME_TO_LOCALE (e2_cl_options.config_dir); #ifdef E2_VFS VPATH ddata = { local, NULL }; //only local config data if (!e2_fs_is_dir3 (&ddata E2_ERR_NONE())) #else if (!e2_fs_is_dir3 (local E2_ERR_NONE())) #endif { //if not, try to create it printd (INFO, "creating config directory '%s'", e2_cl_options.config_dir); #ifdef E2_VFS if (e2_fs_recurse_mkdir (&ddata, 0755 E2_ERR_NONE())) #else if (e2_fs_recurse_mkdir (local, 0755 E2_ERR_NONE())) #endif { printd (WARN, "could not create config directory '%s'", e2_cl_options.config_dir); F_FREE (local); return FALSE; } } //it's there, but can we use it ? #ifdef E2_VFS else if (e2_fs_access (&ddata, R_OK | W_OK | X_OK E2_ERR_NONE())) #else else if (e2_fs_access (local, R_OK | W_OK | X_OK E2_ERR_NONE())) #endif { //nope printd (WARN, "cannot access config directory '%s'", e2_cl_options.config_dir); F_FREE (local); return FALSE; } F_FREE (local); return TRUE; } /** @brief Initialize the default trash directory and a pointer to it It is intended that this default be native, not virtual Uses command-line option if that was provided. Otherwise, tries to use XDG standard, or if that is not available, tries the e2 config dir. Creates dir if doesn't exist, or print error message. Sets e2_cl_options.trash_dir to the path (a trailing '/'), or it stays NULL if dir not available. Needs to be called after the config dir is checked/created Expects BGL off/open @return */ void e2_option_set_trash_dir (void) { /* gchar *local; if (e2_cl_options.trash_dir == NULL || *e2_cl_options.trash_dir == '\0') { //trash dir was not determined by a command line option if (e2_cl_options.trash_dir != NULL) { g_free (e2_cl_options.trash_dir); e2_cl_options.trash_dir = NULL; } //look for the defaults gboolean freeme = FALSE; gchar *dhome = (gchar *) g_getenv ("XDG_DATA_HOME"); if (dhome == NULL || *dhome == '\0') { const gchar *home = g_get_home_dir (); if (home != NULL) { dhome = g_build_filename (home, ".local", "share", NULL); freeme = TRUE; } else if (e2_cl_options.config_dir != NULL) dhome = e2_cl_options.config_dir; else { gdk_threads_enter (); e2_output_print_error ( _("Cannot identify where to put trash. Please set the XDG_DATA_HOME or HOME environment variable."), FALSE); gdk_threads_leave (); return; } } e2_cl_options.trash_dir = g_build_filename (dhome, "Trash", "files", NULL); if (freeme) g_free (dhome); } else //config dir was specified in the startup command { //if the command line dir-path is not absolute, //correct this to prevent problems later on if (!g_path_is_absolute (e2_cl_options.trash_dir)) { gchar *current_dir = g_get_current_dir (); gchar *tr_dir = g_build_filename (current_dir, e2_cl_options.trash_dir, NULL); g_free (current_dir); g_free (e2_cl_options.trash_dir); e2_cl_options.trash_dir = tr_dir; } } */ //make the trash dir if it doesn't exist gchar *local = F_FILENAME_TO_LOCALE (e2_cl_options.trash_dir); #ifdef E2_VFS VPATH ddata = { local, NULL }; //only local config data if (!e2_fs_is_dir3 (&ddata E2_ERR_NONE())) #else if (!e2_fs_is_dir3 (local E2_ERR_NONE())) #endif { printd (INFO, "creating trash directory '%s'", e2_cl_options.trash_dir); #ifdef E2_VFS if (e2_fs_recurse_mkdir (&ddata, 0755 E2_ERR_NONE())) #else if (e2_fs_recurse_mkdir (local, 0755 E2_ERR_NONE())) #endif { gchar *message = g_strdup_printf (_("Cannot create trash directory %s"), e2_cl_options.trash_dir); gdk_threads_enter (); e2_output_print_error (message, TRUE); gdk_threads_leave (); g_free (e2_cl_options.trash_dir); e2_cl_options.trash_dir = NULL; } } //it's there, but can we use it ? #ifdef E2_VFS else if (e2_fs_access (&ddata, R_OK | X_OK E2_ERR_NONE())) #else else if (e2_fs_access (local, R_OK | X_OK E2_ERR_NONE())) #endif { //nope printd (WARN, "cannot access trash directory '%s'", e2_cl_options.trash_dir); F_FREE (local); g_free (e2_cl_options.trash_dir); e2_cl_options.trash_dir = NULL; return; } F_FREE (local); //cleanup, add trailer if (e2_cl_options.trash_dir != NULL) e2_cl_options.trash_dir = e2_utils_path_clean (e2_cl_options.trash_dir); } /** @brief dump all current configuration data and recreate it, with window content updates as appropriate This may be called from within a timer function, or manually It must not be run when there is an open config dialog (closing that causes crash) @param reload TRUE to reload the config file before recreating config data @param recreate TRUE to recreate main window in accord with updated data @return */ void e2_option_refresh (gboolean reload, gboolean recreate) { app.rebuild_enabled = FALSE; //block recursion //this is the one option that we want to keep ... gboolean advanced = e2_option_bool_get ("advanced-config"); e2_keybinding_clean (); g_hash_table_destroy (app.filetypes); e2_plugins_unload_all (FALSE); //do this before data are cleared //clear relevant current information e2_option_clear_data (); //now re-create things, in the same order as at session-start e2_option_default_register (); if (reload) { printd (INFO, "reloading config due to external change"); e2_option_file_read (); } e2_option_tree_install_defaults (); e2_plugins_load_all (); // load plugins (if any) //re-initialise things that are not done in normal 'recreate' functions e2_pane_create_option_data (&app.pane1); e2_pane_create_option_data (&app.pane2); e2_toolbar_initialise (E2_BAR_PANE1); e2_toolbar_initialise (E2_BAR_PANE2); e2_toolbar_initialise (E2_BAR_TASK); e2_toolbar_initialise (E2_BAR_COMMAND); if (recreate) e2_window_recreate (&app.window); //this also recreates key bindings else e2_keybinding_register_all (); e2_filetype_add_all (); e2_option_bool_set ("advanced-config", advanced); app.rebuild_enabled = TRUE; //unblock } /** @brief disable checking for changed config file, if that is in effect @return */ void e2_option_disable_config_checks (void) { if (e2_option_bool_get ("auto-refresh-config") && app.timers[CONFIG_T] != 0) { #ifdef E2_FAM if (app.monitor_type != E2_MONITOR_DEFAULT) e2_fs_FAM_cancel_monitor_config (); //don't trigger a config file reload due to this write #endif g_source_remove (app.timers[CONFIG_T]); app.timers[CONFIG_T] = 0; } } /** @brief enable checking for changed config file, if that is in effect @return */ void e2_option_enable_config_checks (void) { if (e2_option_bool_get ("auto-refresh-config") && app.timers[CONFIG_T] == 0) { #ifdef E2_FAM if (app.monitor_type != E2_MONITOR_DEFAULT) e2_fs_FAM_monitor_config (); #endif app.timers[CONFIG_T] = #ifdef USE_GLIB2_14 g_timeout_add_seconds (E2_CONFIGCHECK_INTERVAL_S, #else g_timeout_add (E2_CONFIGCHECK_INTERVAL, #endif (GSourceFunc) e2_option_check_config_files, NULL); } } gboolean cfgdirty; /** @brief check for and respond to changed config file This is the fn called by the check-config-files timer With FAM use, update is normally detected and flagged as part of the filepane monitoring, as all change-data arrives via the same stream Otherwise, here we check config file mtim.tvsec against a stored value. If update is indicated, recreate window after re-reading config data @param user_data UNUSED pointer specified when the timer was initiated @return TRUE, always, so timer continues */ gboolean e2_option_check_config_files (gpointer User_data) { #ifdef E2_FAM if (app.monitor_type == E2_MONITOR_DEFAULT) { #endif struct stat stat_buf; gchar *filename = g_build_filename (e2_cl_options.config_dir, default_config_file, NULL); gchar *local = F_FILENAME_TO_LOCALE (filename); #ifdef E2_VFS VPATH ddata = { local, NULL }; //config data always local cfgdirty = (!e2_fs_stat (&ddata, &stat_buf E2_ERR_NONE()) #else cfgdirty = (!e2_fs_stat (local, &stat_buf E2_ERR_NONE()) #endif && stat_buf.st_mtime != app.config_mtime); if (cfgdirty && app.rebuild_enabled) app.config_mtime = stat_buf.st_mtime; F_FREE (local); g_free (filename); #ifdef E2_FAM } #endif if (cfgdirty && app.rebuild_enabled) { //we're in a timer fn, so gtk's callback lock doesn't apply //explicit lock - or else the filelists won't rebuild FIXME why is that gdk_threads_enter (); e2_option_refresh (TRUE, TRUE); gdk_threads_leave (); e2_output_print (&app.tab, _("Configuration data re-loaded"), NULL, TRUE, NULL); cfgdirty = FALSE; } return TRUE; } /** @brief register a config option @param type flag for the type of set that it is @param name name of the option, a constant string @param group group the option belongs to, used in config dialog, a r-t string FREEME @param desc textual description of the option used in config dialog, a r-t _() string FREEME ? @param tip tooltip used when displaying the config dialog, a _() string, or NULL @param depends name of another option this one depends on, or NULL @param flags bitflags determining how the option data is handled @return the option data struct */ E2_OptionSet *e2_option_register (E2_OptionType type, gchar *name, gchar *group, gchar *desc, gchar *tip, gchar *depends, E2_OptionFlags flags) { E2_OptionSet *set = e2_option_get_simple (name); if (set == NULL) { #ifdef USE_GLIB2_10 set = (E2_OptionSet *) g_slice_alloc (sizeof (E2_OptionSet)); // set = ALLOCATE (E2_OptionSet); #else set = ALLOCATE (E2_OptionSet); #endif CHECKALLOCATEDWARN (set, ) if (set != NULL) { g_ptr_array_add (options_array, set); g_hash_table_insert (options_hash, name, set); //replace not valid //non-freeable parameters set only once set->type = type; set->flags = flags; set->hook_freezed = FALSE; set->widget = NULL; } } //CHECKME handle re-registration of a different type ? //string parameters set each registration, so clear any old versions elsewhere if (set != NULL) { set->name = name; set->group = group; set->desc = desc; set->tip = tip; set->depends = depends; g_hook_list_init (&set->hook_value_changed, sizeof (GHook)); } else { //FIXME advise user and cleanup exit (1); } return set; } /** @brief unregister option named @a name This is for plugin options, essentially @param name option name string @return TRUE if the option was registered */ gboolean e2_option_unregister (gchar *name) { E2_OptionSet *set = g_hash_table_lookup (options_hash, name); if (set == NULL) return FALSE; g_ptr_array_remove (options_array, set); g_hash_table_remove (options_hash, name); return TRUE; } /** @brief get E2_OptionSet with warning @param option name of the option @return option or NULL if it cannot be found */ E2_OptionSet *e2_option_get (gchar *option) { E2_OptionSet *set = (E2_OptionSet *) g_hash_table_lookup (options_hash, option); if (set == NULL) printd (WARN, "trying to get option '%s' which doesn't exist", option); return set; } /** @brief get E2_OptionSet without warning @param option name of the option @return option or NULL if the option cannot be found */ E2_OptionSet *e2_option_get_simple (gchar *option) { return (E2_OptionSet *) g_hash_table_lookup (options_hash, option); } /* UNUSED void *e2_option_void_get (E2_OptionSet *set, gchar *option) { switch (set->type) { case E2_OPTION_TYPE_BOOL: if (g_str_equal (option, "true")) return (void *) TRUE; else return (void *) FALSE; break; case E2_OPTION_TYPE_STR: case E2_OPTION_TYPE_FONT: case E2_OPTION_TYPE_ICON: case E2_OPTION_TYPE_SEL: return (void *) option; default: return NULL; break; } return NULL; } */ E2_OptionSet *e2_option_attach_value_changed (gchar *option, GtkWidget *widget, gpointer func, gpointer data) { E2_OptionSet *set = e2_option_get (option); if (set != NULL) e2_option_attach_value_changed_simple (set, widget, func, data); return set; } void e2_option_attach_value_changed_simple (E2_OptionSet *set, GtkWidget *widget, gpointer func, gpointer data) { GHook *hook = e2_hook_register (&set->hook_value_changed, func, data); E2_Duo *duo = MALLOCATE (E2_Duo); //too small for slice duo->a = hook; duo->b = &set->hook_value_changed; g_object_set_data_full (G_OBJECT (widget), "e2-option-attach-value-changed", duo, (GDestroyNotify) e2_hook_unattach_cb); } void e2_option_connect (GtkWidget *controller, gboolean active) { g_object_set_data (G_OBJECT (controller), "e2-controller-blocked", GINT_TO_POINTER (!active)); } /** @brief clear all relevant option data prior to a refresh of all options @return */ void e2_option_clear_data (void) { /*FIXME if we only clear relevant data, when we re-walk the array later, it has some items with crap content more-or-less same code as _e2_option_clean1() guint i; gpointer *walker; for (i = 0, walker = options_array->pdata; i < options_array->len; i++, walker++) { E2_OptionSet *set = *walker; E2_OptionFlags flags = set->flags; //several strings have to be re-installed when re-registering a set, //so get rid of existing ones if (flags & E2_OPTION_FLAG_FREENAME) g_free (set->name); if (flags & E2_OPTION_FLAG_FREEGROUP) g_free (set->group); if (flags & E2_OPTION_FLAG_FREEDESC) g_free (set->desc); if (flags & E2_OPTION_FLAG_FREETIP) g_free (set->tip); if (flags & E2_OPTION_FLAG_FREEDEPENDS) g_free (set->depends); E2_OptionType type = set->type; if (type & ( E2_OPTION_TYPE_INT | E2_OPTION_TYPE_STR | E2_OPTION_TYPE_FONT | E2_OPTION_TYPE_COLOR )) g_free (set->sval); else if (type & E2_OPTION_TYPE_SEL) g_strfreev (set->ex.sel.def); else if (type & E2_OPTION_TYPE_TREE) { if (set->ex.tree.def != NULL) { g_strfreev (set->ex.tree.def); set->ex.tree.def = NULL; } e2_list_free_with_data (&set->ex.tree.columns); set->ex.tree.columns_num = 0; //insurance, not really needed g_object_unref (G_OBJECT (set->ex.tree.model)); } //set-related hooks are un-registered when config dialog is destroyed //anyway, we may want to run the hook after this change } */ g_ptr_array_free (options_array, TRUE); options_array = g_ptr_array_new (); g_hash_table_destroy (options_hash); options_hash = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) _e2_option_clean1); } /*void e2_option_tree_stores_clear (void) { guint i; gpointer *walker; E2_OptionSet *set; for (i = 0, walker = options_array->pdata; i < options_array->len; i++, walker++) { set = *walker; if (set->type == E2_OPTION_TYPE_TREE) gtk_tree_store_clear (set->ex.tree.model); } } */ /* this was used only at session end, so don't bother to cleanup void destroy_config () { g_ptr_array_free (options_array, FALSE); g_hash_table_destroy (options_hash); g_hash_table_destroy (options_queue); } */ gboolean write_error = FALSE; static void _e2_option_file_write_unknowns (gpointer key, gpointer value, E2_FILE *f) { if (e2_fs_file_write (f, "%s\n", (gchar *)value) == 0) write_error = TRUE; } /** @brief write current configuration to native file @a utfpath The current configuration will be written to the default config file 'config'. Expects BGL to be on/closed @param utfpath absolute path of file to write (utf-8 string), or NULL to use default @return */ void e2_option_file_write (const gchar *utfpath) { #ifdef E2_VFS VPATH ddata; VPATH tdata; #endif gboolean freepath = (utfpath == NULL); gchar *usepath = (freepath) ? g_build_filename (e2_cl_options.config_dir, default_config_file, NULL): (gchar *)utfpath; gchar *local = F_FILENAME_TO_LOCALE (usepath); gchar *tempname = e2_utils_get_tempname (local); E2_FILE *f = e2_fs_open_writestream (tempname E2_ERR_NONE()); if (f != NULL) { printd (DEBUG, "write config file: %s", usepath); if (e2_fs_file_write (f, //the 1st line is language-independent, for version verification "# "PROGNAME" (v "VERSION RELEASE")\n\n" ) == 0) goto error_handler; if (e2_fs_file_write (f, //FIXME = remind translators not to remove # from line starts _("# This is the %s configuration data file.\n" "# It will be overwritten each time the program is run!\n\n" "# If you're inclined to edit the file between program sessions, note this:\n" "# for tree options, you have to use \\| to escape | and you have to use \\< to escape <,\n" "# if that is the first non-space character on a line.\n\n"), PROGNAME ) == 0) goto error_handler; guint i; gpointer *walker; E2_OptionSet *set; for (i = 0, walker = options_array->pdata; i < options_array->len; i++, walker++) { set = *walker; switch (set->type) { case E2_OPTION_TYPE_INT: if (e2_fs_file_write (f, "# %s\n%s=%d\n", set->desc, set->name, set->ival) == 0) goto error_handler; break; case E2_OPTION_TYPE_BOOL: // if (e2_fs_file_write (f, "# %s\n%s=%s\n", // set->desc, set->name, set->sval) == 0) // goto error_handler; // break; case E2_OPTION_TYPE_STR: case E2_OPTION_TYPE_FONT: case E2_OPTION_TYPE_COLOR: // if (e2_fs_file_write (f, "# %s\n%s=%s\n", // set->desc, set->name, set->sval) == 0) // goto error_handler; // break; case E2_OPTION_TYPE_SEL: if (e2_fs_file_write (f, "# %s\n%s=%s\n", set->desc, set->name, set->sval) == 0) goto error_handler; break; case E2_OPTION_TYPE_TREE: if (e2_fs_file_write (f, "# %s\n%s=<\n", set->name, set->name) == 0) goto error_handler; GtkTreeIter iter; if (gtk_tree_model_get_iter_first (set->ex.tree.model, &iter)) e2_option_tree_write_to_file (f, set, &iter, 0); //error handling ? if (e2_fs_file_write (f, ">\n") == 0) goto error_handler; break; default: printd (WARN, "don't know how to write option '%s' to config file", set->name); break; } } //now the unknown options, if any //need an error flag write_error = FALSE; g_hash_table_foreach (options_queue, (GHFunc) _e2_option_file_write_unknowns, f); if (write_error) goto error_handler; e2_fs_close_stream (f); #ifdef E2_VFS tdata.localpath = tempname; tdata.spacedata = NULL; ddata.localpath = local; ddata.spacedata = NULL; #endif gdk_threads_leave (); //downstream errors invoke local mutex locking #ifdef E2_VFS e2_task_backend_rename (&tdata, &ddata); e2_fs_chmod (&ddata, 0600 E2_ERR_NONE()); #else e2_task_backend_rename (tempname, local); e2_fs_chmod (local, 0600 E2_ERR_NONE()); #endif gdk_threads_enter (); goto cleanup; } error_handler: if (f != NULL) { e2_fs_close_stream (f); gdk_threads_leave (); //downstream errors invoke local mutex locking #ifdef E2_VFS e2_task_backend_delete (&tdata); #else e2_task_backend_delete (tempname); #endif gdk_threads_enter (); } gchar *msg = g_strdup_printf (_("Cannot write config file %s - %s"), usepath, g_strerror (errno)); //ok for native-only config file e2_output_print_error (msg, TRUE); sleep (1); cleanup: if (freepath) g_free (usepath); F_FREE (local); g_free (tempname); } /** @brief Set value of "single-valued" option named @a option This is called from the config file line parser. If the set data exists already, its value is set according to @a str. If the set data does not exist already, the set is logged in the 'unknown' queue @param option name of option to be set @param str string with value to be stored for option named @a option @return TRUE if @a option was set successfully, even if in the unknown options queue */ gboolean e2_option_set_from_string (gchar *option, gchar *str) { E2_OptionSet *set = e2_option_get_simple (option); if (set == NULL) { //reinstate the form of the config line gchar *saveme = g_strconcat (option, "=", str, NULL); //and log the freeable strings e2_option_unknown_record (g_strdup (option), saveme); return FALSE; } return e2_option_set_value_from_string (set, str); } /** @brief Setup the default value for @a set in accord with parameter @a str This sets the appropriate set->sval, depending on the type of option There is no check for existence of @a set @param set pointer to data struct for the option being processed @param str string form of value to be stored for @a set @return TRUE if the value was valid and stored */ gboolean e2_option_set_value_from_string (E2_OptionSet *set, gchar *str) { gboolean retval = TRUE; switch (set->type) { case E2_OPTION_TYPE_BOOL: set->hook_freezed = TRUE; e2_option_bool_set_direct (set, g_str_equal (str, "true") ? TRUE : FALSE); set->hook_freezed = FALSE; break; case E2_OPTION_TYPE_INT: { //FIXME: check for conversion success gint i = (gint) g_ascii_strtoull (str, NULL, 10); set->hook_freezed = TRUE; e2_option_int_set_direct (set, i); set->hook_freezed = FALSE; break; } case E2_OPTION_TYPE_STR: case E2_OPTION_TYPE_FONT: e2_option_str_set_direct (set, str); break; case E2_OPTION_TYPE_COLOR: return e2_option_color_set_str_direct (set, str); break; case E2_OPTION_TYPE_SEL: { const gchar *val; gint i = 0; set->ival = -1; while ((val = set->ex.sel.def[i]) != NULL) { if (g_str_equal (str, val)) { set->ival = i; set->sval = (gchar *) val; break; } i++; } if (set->ival == -1) { printd (WARN, "bad value for sel option '%s'", set->name); retval = FALSE; set->ival = 0; set->sval = (gchar *) set->ex.sel.def[0]; } // if (!set->hook_freezed) // e2_hook_list_run (&set->hook_value_changed, GINT_TO_POINTER (set->ival)); } break; default: break; } return retval; } /** @brief Read config options from @a f and parse them Critical characters in @a f are assumed to be ascii This function is more-or-less replicated in the config plugin @param f NULL-terminated array of strings in config file format @return */ void e2_option_read_array (gchar *f[]) { gint i = -1; //array index gchar *line; //pointer to the current line gchar **split; while ((line = f[++i]) != NULL) { g_strchomp (line); //ignore empty lines and comments if (*line == '\0') continue; if (line[0] == '#') continue; split = g_strsplit (line, "=", 2); if (split[1] != NULL) { if (!g_str_equal (split[1], "<")) //not a tree set { if (!e2_option_set_from_string (split[0], split[1])) printd (WARN, "could not set option '%s'", split[0]); } else //a tree set if (!e2_option_tree_set_from_array (split[0], f, &i, NULL)) printd (WARN, "could not set tree option '%s'", split[0]); } g_strfreev (split); } } /** @brief read config file into memory at @a contents It's intended that a config file be native, not virtual. If the file is read successfully, log the post-read config dir timestamp @param contents pointer to place to store the loaded file content @return TRUE if the file was read */ static gboolean _e2_option_config_file_read (gpointer *contents) { //find absolute path to config file gchar *filepath = g_build_filename (e2_cl_options.config_dir, default_config_file, NULL); gchar *local = F_FILENAME_TO_LOCALE (filepath); #ifdef E2_VFS VPATH ddata = { local, NULL }; //only local config data if (e2_fs_access (&ddata, R_OK E2_ERR_NONE())) #else if (e2_fs_access (local, R_OK E2_ERR_NONE())) #endif { //try for the old-style name g_free (filepath); F_FREE (local); filepath = g_build_filename (e2_cl_options.config_dir, "config", NULL); local = F_FILENAME_TO_LOCALE (filepath); #ifdef E2_VFS ddata.localpath = local; if (e2_fs_access (&ddata, R_OK E2_ERR_NONE())) #else if (e2_fs_access (local, R_OK E2_ERR_NONE())) #endif { g_free (filepath); F_FREE (local); return FALSE; } } gboolean retval; //get file #ifdef E2_VFS if (e2_fs_get_file_contents (&ddata, contents, NULL, TRUE E2_ERR_NONE())) #else if (e2_fs_get_file_contents (local, contents, NULL, TRUE E2_ERR_NONE())) #endif { //#ifndef E2_FAM do this anyway ... //log the current timestamp struct stat stat_buf; #ifdef E2_VFS e2_fs_stat (&ddata, &stat_buf E2_ERR_NONE()); #else e2_fs_stat (local, &stat_buf E2_ERR_NONE()); #endif app.config_mtime = stat_buf.st_mtime; //#endif printd (DEBUG, "config file '%s' read", filepath); retval = TRUE; } else { printd (WARN, "could not read config file '%s'", filepath); retval = FALSE; } g_free (filepath); F_FREE (local); return retval; } /** @brief read config file First, the configuration is read, then its content is parsed with e2_option_read_array(). If tree options have no configuration, they are initialized with a default one. (All other options have been intialized when they were registered.) @return TRUE if the config file was read */ gboolean e2_option_file_read (void) { gpointer contents; if (_e2_option_config_file_read (&contents)) { gboolean retval; //everything goes into an array gchar **split = g_strsplit ((gchar *)contents, "\n", -1); if (split[0] != NULL) { //there is something to work with, so parse it //get the config file version (always ascii) from 1st line of file //(done here to avoid problems when line(s) not from the file are processed if (strstr (split[0], "(v") != NULL ) { gchar *sp = g_strrstr (split[0], ")"); if (sp != NULL) *sp = '\0'; sp = g_strstrip (strstr (split[0], "(v") + 2); g_strlcpy (app.cfgfile_version, sp, sizeof(app.cfgfile_version)); } e2_option_read_array (split); retval = TRUE; } else retval = FALSE; g_strfreev (split); g_free (contents); //need free() if file buffer allocated by malloc() return retval; } else return FALSE; } /** @brief cleanup data for an item in the options hash @set pointer to data struct to be processed @return */ static void _e2_option_clean1 (E2_OptionSet *set) { // printd (DEBUG, "destroying set %s", set->name); E2_OptionFlags flags = set->flags; if (flags & E2_OPTION_FLAG_FREENAME) g_free (set->name); if (flags & E2_OPTION_FLAG_FREEGROUP) g_free (set->group); if (flags & E2_OPTION_FLAG_FREEDESC) g_free (set->desc); if (flags & E2_OPTION_FLAG_FREETIP) g_free (set->tip); if (flags & E2_OPTION_FLAG_FREEDEPENDS) g_free (set->depends); E2_OptionType type = set->type; if (type & ( E2_OPTION_TYPE_INT | E2_OPTION_TYPE_STR | E2_OPTION_TYPE_FONT | E2_OPTION_TYPE_COLOR )) g_free (set->sval); else if (type & E2_OPTION_TYPE_SEL) g_strfreev (set->ex.sel.def); else if (type & E2_OPTION_TYPE_TREE) { if (set->ex.tree.def != NULL) g_strfreev (set->ex.tree.def); e2_list_free_with_data (&set->ex.tree.columns); g_object_unref (G_OBJECT (set->ex.tree.model)); } //CHECKME set-related hooks are un-registered when config dialog is destroyed //so this may be redundant, or at least, needs to happen after that destruction //or we may want to run the hooks after this change if (set->hook_value_changed.is_setup) g_hook_list_clear (&set->hook_value_changed); #ifdef USE_GLIB2_10 g_slice_free1 (sizeof (E2_OptionSet), set); // DEALLOCATE (E2_OptionSet, set); #else DEALLOCATE (E2_OptionSet, set); #endif } /** @brief initialize option system internal data structures of the option system will be initialized. this includes creation of a hash table for fast option lookup. this function is called on startup, by the main function @return */ void e2_option_init (void) { /* "core" options are registered before a config file (if any) is read. For those that are non-tree, a default value is registered during the registration process. For core tree options, a pointer to the default initialisation function is stored, to reduce needless parsing. When a config file is read, option values from that are installed, replacing defaults or installing trees. Finally, missing tree values are installed. "Transient" (con-core) options are not registered at session-start, so any such that's found in a config file is simply listed as is. Any later registration of a transient option should be followed by a check of that list, and installation of the config file values if found there, and then, for a tree option, installation of default values if nothing was queued. The list has strings in the same format as was in the config file. Hence tree-options are multi-lined. Installation requires parsing. Writing a config file finishes with the then-current contents of the unknown-list. The listed strings do not need to be reformatted before writing */ const gchar *lang = g_getenv ("LC_MESSAGES"); if (lang == NULL) lang = g_getenv ("LANG"); if (lang == NULL) lang = g_getenv ("LC_ALL"); if (lang == NULL) lang = "C"; default_config_file = g_strconcat ("config-", lang, NULL); //DO NOT TRANSLATE options_array = g_ptr_array_new (); options_hash = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) _e2_option_clean1); options_queue = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); //before config loaded, setup all option types, default values, tips etc //tree option default values are not set yet e2_option_default_register (); #ifndef E2_FAM e2_fs_FAM_config_stamp (); //set baseline time, for config refreshing #endif app.rebuild_enabled = TRUE; //set signal which allows config refreshes #ifdef E2_IMAGECACHE e2_cache_icons_init (); #endif } /** @brief setup array of i18n config dialog labels Some of these are the same as action labels Array size defined in e2_config_dialog.h @return */ void e2_option_setup_labels (void) { config_labels[0]=_("aliases"); config_labels[1]=_("bookmarks"); config_labels[2]=_("colors"); config_labels[3]=_("columns"); config_labels[4]=_("command bar"); config_labels[5]=_("command line"); config_labels[6]=_("commands"); config_labels[7]=_("confirmation"); config_labels[8]=_("context menu"); config_labels[9]=_("custom menus"); config_labels[10]=_("delays"); config_labels[11]=_("dialogs"); config_labels[12]=_("directory lines"); config_labels[13]=_("extensions"); config_labels[14]=_("file actions"); config_labels[15]=_("filetypes"); config_labels[16]=_("fonts"); config_labels[17]=_("general"); config_labels[18]=_("history"); config_labels[19]=_("icons"); config_labels[20]=_("interface"); config_labels[21]=_("item types"); config_labels[22]=_("key bindings"); config_labels[23]=_("make directory"); config_labels[24]=_("menus"); config_labels[25]=_("miscellaneous"); config_labels[26]=_("options"); config_labels[27]=_("output"); config_labels[28]=_("pane 1"); config_labels[29]=_("pane1 bar"); config_labels[30]=_("pane 2"); config_labels[31]=_("pane2 bar"); config_labels[32]=_("panes"); config_labels[33]=_("plugins"); config_labels[34]=_("position"); config_labels[35]=_("search"); config_labels[36]=_("startup"); config_labels[37]=_("style"); config_labels[38]=_("tab completion"); config_labels[39] =_("task bar"); config_labels[40]=_("view"); //maybe more space here, see array definition in header file } emelfm2-0.4.1/src/config/e2_option_int.c0000600000175000017500000000645410711017436016770 0ustar cairocairo/* $Id: e2_option_int.c 682 2007-10-28 05:33:18Z tpgww $ Copyright (C) 2004-2007 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "emelfm2.h" #include "e2_option.h" #include "e2_option_int.h" void e2_option_int_add_widget (GtkWidget *dialog, GtkWidget *box, GtkSizeGroup *size_group, E2_OptionSet *set) { GtkWidget *event = e2_widget_add_eventbox (box, TRUE, 0); GtkWidget *hbox = gtk_hbox_new (FALSE, E2_PADDING); gtk_container_add (GTK_CONTAINER (event), hbox); gchar *label_text = g_strconcat (set->desc, ":", NULL); GtkWidget *label = e2_widget_add_mid_label (hbox, label_text, 0.0, FALSE, 0); g_free (label_text); gtk_size_group_add_widget (size_group, label); gdouble max_percent = set->ex.num.max / 100.0; GtkAdjustment *spinner_adj = (GtkAdjustment *) gtk_adjustment_new (set->ival, set->ex.num.min, set->ex.num.max, 1.0, max_percent, max_percent); set->widget = gtk_spin_button_new (spinner_adj, 1.0, 0); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif event, set->tip); gtk_box_pack_start (GTK_BOX (hbox), set->widget, FALSE, FALSE, E2_PADDING); } E2_OptionSet *e2_option_int_register (gchar *name, gchar *group, gchar *desc, gchar *tip, gchar *depends, gint def, gint min, gint max, E2_OptionFlags flags) { E2_OptionSet *set = e2_option_register (E2_OPTION_TYPE_INT, name, group, desc, tip, depends, flags); set->ex.num.min = min; set->ex.num.max = max; // set s/ival set->sval = g_strdup (""); //it's freed in the value-set func set->hook_freezed = TRUE; e2_option_int_set_direct (set, def); set->hook_freezed = FALSE; return set; } gint e2_option_int_get (gchar *option) { E2_OptionSet *set; set = e2_option_get (option); if ((set->type == E2_OPTION_TYPE_SEL) || (set->type == E2_OPTION_TYPE_INT)) { return e2_option_int_get_direct (set); } else { printd (WARN, "trying to get int option '%s' which isn't an int", option); return -1; } } gint e2_option_int_get_direct (E2_OptionSet *set) { return set->ival; } gint e2_option_int_set (gchar *option, gint value) { E2_OptionSet *set; set = e2_option_get (option); if (set->type == E2_OPTION_TYPE_INT) return e2_option_int_set_direct (set, value); else { printd (WARN, "trying to set int option '%s' which isn't an int", option); return 0; } } gint e2_option_int_set_direct (E2_OptionSet *set, gint value) { if (value < set->ex.num.min) value = set->ex.num.min; if (value > set->ex.num.max) value = set->ex.num.max; set->ival = value; g_free (set->sval); set->sval = g_strdup_printf ("%d", set->ival); if (!set->hook_freezed) e2_hook_list_run (&set->hook_value_changed, GINT_TO_POINTER (value)); return value; } emelfm2-0.4.1/src/config/e2_option_sel.c0000600000175000017500000001241410711017436016752 0ustar cairocairo/* $Id: e2_option_sel.c 682 2007-10-28 05:33:18Z tpgww $ Copyright (C) 2004-2007 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "emelfm2.h" #include "e2_option.h" #include "e2_option_sel.h" static void _e2_option_sel_activated_cb (GtkWidget *menu_item, E2_OptionSet *set) { GtkWidget *controller = g_object_get_data (G_OBJECT (menu_item), "e2-controller-widget"); if (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (controller), "e2-controller-blocked"))) return; if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (menu_item))) return; GSList *group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (menu_item)); gint index = 2 - g_slist_index (group, menu_item); e2_option_sel_set_direct (set, index); } static gboolean _e2_option_sel_value_changed_cb2 (gpointer data, GtkWidget *submenu) { GtkWidget *controller = g_object_get_data (G_OBJECT (submenu), "e2-controller-widget"); if (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (controller), "e2-controller-blocked"))) return TRUE; gint value = GPOINTER_TO_INT (data); GList *children = gtk_container_get_children (GTK_CONTAINER (submenu)); GtkWidget *item = g_list_nth_data (children, value); gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE); return TRUE; } /******************/ /***** public *****/ /******************/ GtkWidget *e2_option_sel_add_menu_widget (GtkWidget *controller, GtkWidget *menu, E2_OptionSet *set, gpointer func, gpointer data) { //note set->tip not effective for an item with a submenu GtkWidget *submenu = e2_menu_add_submenu (menu, set->desc, NULL); g_object_set_data (G_OBJECT (submenu), "e2-controller-widget", controller); //this will update the menu in case the option value changes e2_option_attach_value_changed_simple (set, submenu, _e2_option_sel_value_changed_cb2, submenu); GSList *group = NULL; gint i = 0; while (set->ex.sel.def[i] != NULL) { GtkWidget *item = e2_menu_add_radio (submenu, &group, (gchar *)set->ex.sel.def[i], (e2_option_int_get_direct (set) == i), func, data); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif item, set->tip); g_object_set_data (G_OBJECT (item), "e2-controller-widget", controller); g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (_e2_option_sel_activated_cb), set); i++; } return submenu; } void e2_option_sel_add_widget (GtkWidget *dialog, GtkWidget *box, GtkSizeGroup *size_group, E2_OptionSet *set) { GtkWidget *event = e2_widget_add_eventbox (box, FALSE, 0); GtkWidget *hbox = gtk_hbox_new (FALSE, E2_PADDING); gtk_container_add (GTK_CONTAINER (event), hbox); gchar *label_text = g_strconcat (set->desc, ":", NULL); GtkWidget *label = e2_widget_add_mid_label (hbox, label_text, 0.0, FALSE, 0); g_free (label_text); gtk_size_group_add_widget (size_group, label); GtkWidget *combo = e2_combobox_add (hbox, FALSE, E2_PADDING, NULL, NULL, NULL, E2_COMBOBOX_MENU_STYLE); e2_combobox_append_history_strv (combo, set->ex.sel.def); gtk_combo_box_set_active (GTK_COMBO_BOX (combo), set->ival); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif event, set->tip); set->widget = combo; gtk_widget_show_all (event); } E2_OptionSet *e2_option_sel_register (gchar *name, gchar *group, gchar *desc, gchar *tip, gchar *depends, gint def, const gchar **values, E2_OptionFlags flags) { E2_OptionSet *set = e2_option_register (E2_OPTION_TYPE_SEL, name, group, desc, tip, depends, flags); set->ex.sel.def = g_strdupv ((gchar **)values); gint i = 0; while (set->ex.sel.def[i++] != NULL); set->ex.sel.def_count = i - 1; //set s/ival set->hook_freezed = TRUE; e2_option_sel_set_direct (set, def); set->hook_freezed = FALSE; return set; } gint e2_option_sel_set (gchar *option, gint value) { E2_OptionSet *set; set = e2_option_get (option); if (set != NULL && set->type == E2_OPTION_TYPE_SEL) return e2_option_sel_set_direct (set, value); else { printd (WARN, "trying to set sel option '%s' which isn't a sel", option); return 0; } } gint e2_option_sel_set_direct (E2_OptionSet *set, gint value) { if (value < 0) value = 0; if (value > set->ex.sel.def_count) value = set->ex.sel.def_count; set->ival = value; set->sval = (gchar *) set->ex.sel.def[value]; if (!set->hook_freezed) e2_hook_list_run (&set->hook_value_changed, GINT_TO_POINTER (value)); return value; } gint e2_option_sel_get (gchar *option) { E2_OptionSet *set; set = e2_option_get (option); if (set != NULL && set->type == E2_OPTION_TYPE_SEL) return e2_option_sel_get_direct (set); return -1; } gint e2_option_sel_get_direct (E2_OptionSet *set) { return set->ival; } emelfm2-0.4.1/src/config/e2_option.h0000600000175000017500000001550711010340377016116 0ustar cairocairo/* $Id: e2_option.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2003-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_OPTION_H__ #define __E2_OPTION_H__ #include "emelfm2.h" typedef enum { E2_TREE_STARTED, E2_TREE_COMPLETED, E2_TREE_ABORTED, E2_TREE_IGNORED, E2_TREE_UNKNOWN } E2_TreeStatus; //simple types, (bits, for easier checks of multiple types) typedef enum { E2_OPTION_TYPE_BOOL = 1 << 0, E2_OPTION_TYPE_INT = 1 << 1, E2_OPTION_TYPE_STR = 1 << 2, E2_OPTION_TYPE_SEL = 1 << 3, E2_OPTION_TYPE_FONT = 1 << 4, E2_OPTION_TYPE_COLOR = 1 << 5, E2_OPTION_TYPE_FILE = 1 << 6, //UNUSED E2_OPTION_TYPE_ICON = 1 << 7, E2_OPTION_TYPE_TREE = 1 << 8, } E2_OptionType; //these are flags that may be set when a tree option is registered //stored at set->ex.tree.flags typedef enum { E2_OPTION_TREE_UP_DOWN = 1 << 0, E2_OPTION_TREE_ADD_DEL = 1 << 1, E2_OPTION_TREE_LAST_COL_EMPTY = 1 << 2, E2_OPTION_TREE_LIST = 1 << 3, //this is set/cleared in or around a config dialog, as a signal //that the set has been edited E2_OPTION_TREE_SET_EDITED = 1<< 8, } E2_OptionTreeTypeFlags; typedef enum { E2_OPTION_FLAG_BASIC = 1 << 0, E2_OPTION_FLAG_BASICONLY = 1 << 1, E2_OPTION_FLAG_ADVANCED = 1 << 2, E2_OPTION_FLAG_HIDDEN = 1 << 3, //for items not shown in config dialog //signals for things to free when config data is dumped E2_OPTION_FLAG_FREENAME = 1 << 4, E2_OPTION_FLAG_FREEGROUP = 1 << 5, E2_OPTION_FLAG_FREEDESC = 1 << 6, E2_OPTION_FLAG_FREETIP = 1 << 7, E2_OPTION_FLAG_FREEDEPENDS = 1 << 8, //flags for things to rebuild when config dialog is completed E2_OPTION_FLAG_BUILDICONS = 1 << 9, //clear icons cache E2_OPTION_FLAG_BUILDALL = 1 << 10, E2_OPTION_FLAG_BUILDPANES = 1 << 11, E2_OPTION_FLAG_BUILDLISTS = 1 << 12, E2_OPTION_FLAG_BUILDBARS = 1 << 13, E2_OPTION_FLAG_BUILDSAMEBARS = 1 << 14, //flags for 'extra' data structures to possibly rebuild when config dialog is completed E2_OPTION_FLAG_BUILDFILES = 1 << 15, //dirty filetypes tree-option E2_OPTION_FLAG_BUILDPLUGS = 1 << 16, //dirty plugins tree-option E2_OPTION_FLAG_BUILDKEYS = 1 << 17, //dirty keybindings tree-option E2_OPTION_FLAG_BUILDALIAS = 1 << 18, //dirty aliases tree-option } E2_OptionFlags; typedef struct _E2_OptionTypeExtraInt { gint min; gint max; } E2_OptionTypeExtraInt; typedef struct _E2_OptionTypeExtraSel { gchar **def; gint def_count; } E2_OptionTypeExtraSel; //stored as set->ex.tree typedef struct _E2_OptionTypeExtraTree { gpointer model; //shared store: at session start, set to a void (*func) which is called //to install default tree values if needed //later, can be used for a gchar **, a null-terminated array of //string-pointers, each addressing a line of backed-up values for the tree gpointer def; //holds tree values in case the config file //has been read before the tree option was registered // gchar **unknown; // gint def_num; gboolean synced; //TRUE when string data converted to treestore gint columns_num; GList *columns; gpointer selection_check_func; gpointer draggable_check_func; E2_OptionTreeTypeFlags flags; //flags set when registering a tree option } E2_OptionTypeExtraTree; typedef struct _E2_OptionTypeExtraColor { GdkColor value; } E2_OptionTypeExtraColor; typedef struct _E2_OptionSet { E2_OptionType type; gchar *name; //'internal' name of option, used for checking, finding gchar *group; //config dialog group gchar *desc; //config dialog label gchar *tip; //config dialog tooltip gchar *depends; //'internal' name of an option that must be true for this //one to be 'changeable', or can start with "!" for false precedent gint ival; gchar *sval; //extra option data union _E2_OptionTypeExtra { E2_OptionTypeExtraInt num; E2_OptionTypeExtraSel sel; E2_OptionTypeExtraTree tree; E2_OptionTypeExtraColor color; } ex; GtkWidget *widget; //config dialog widget with data for this option E2_OptionFlags flags; //flags for destroying, rebuilding stuff related to the option gboolean hook_freezed; //TRUE to block running of anything in hooklist GHookList hook_value_changed; } E2_OptionSet; typedef gchar *_config_labels[42]; //make sure there's enough for all array items _config_labels config_labels; #define _C(d) config_labels[d] gchar *default_config_file; GHashTable *options_hash; //for quick lookups GPtrArray *options_array; //for processing options in order they were registered GHashTable *options_queue; //for as-yet unregisted options //void e2_option_clean1 (E2_OptionSet *set); void e2_option_setup_labels (void); gboolean e2_option_set_config_dir (void); gboolean e2_option_check_config_files (gpointer user_data); void e2_option_disable_config_checks (void); void e2_option_enable_config_checks (void); void e2_option_refresh (gboolean reload, gboolean recreate); void e2_option_set_trash_dir (void); void e2_option_init (void); gboolean e2_option_set_from_string (gchar *option, gchar *str); gboolean e2_option_set_value_from_string (E2_OptionSet *set, gchar *str); void e2_option_read_array (gchar *f[]); //void e2_option_tree_stores_clear (void); void e2_option_clear_data (void); E2_OptionSet *e2_option_register (E2_OptionType type, gchar *name, gchar *group, gchar *desc, gchar *tip, gchar *depends, E2_OptionFlags flags); gboolean e2_option_unregister (gchar *name); E2_OptionSet *e2_option_get (gchar *option); E2_OptionSet *e2_option_get_simple (gchar *option); //void *e2_option_void_get (E2_OptionSet *set, gchar *option); E2_OptionSet *e2_option_attach_value_changed (gchar *option, GtkWidget *widget, gpointer func, gpointer data); void e2_option_attach_value_changed_simple (E2_OptionSet *set, GtkWidget *widget, gpointer func, gpointer data); void e2_option_connect (GtkWidget *controller, gboolean active); gboolean e2_option_file_read (void); void e2_option_file_write (const gchar *utfpath); //void destroy_config (); #include "e2_option_bool.h" #include "e2_option_int.h" #include "e2_option_str.h" #include "e2_option_sel.h" #include "e2_option_color.h" #include "e2_option_tree.h" #include "e2_option_unknown.h" /***********************************************/ /***** functions from e2_option__default.c *****/ /***********************************************/ void e2_option_default_register (void); #endif //ndef __E2_OPTION_H__ emelfm2-0.4.1/src/config/e2_option_str.c0000600000175000017500000000340410643544426017006 0ustar cairocairo/* $Id: e2_option_str.c 469 2007-07-06 22:58:30Z tpgww $ Copyright (C) 2004-2007 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "emelfm2.h" #include #include "e2_option.h" #include "e2_option_str.h" E2_OptionSet *e2_option_str_register (gchar *name, gchar *group, gchar *desc, gchar *tip, gchar *depends, gchar *value, E2_OptionFlags flags) { E2_OptionSet *set = e2_option_register (E2_OPTION_TYPE_STR, name, group, desc, tip, depends, flags); set->ival = -1; set->sval = g_strdup (value); return set; } E2_OptionSet *e2_option_font_register (gchar *name, gchar *group, gchar *desc, gchar *tip, gchar *depends, gchar *def, E2_OptionFlags flags) { E2_OptionSet *set = e2_option_str_register (name, group, desc, tip, depends, def, flags); set->type = E2_OPTION_TYPE_FONT; return set; } gchar *e2_option_str_get (gchar *option) { E2_OptionSet *set = e2_option_get (option); return set->sval; } gchar *e2_option_str_get_direct (E2_OptionSet *set) { return set->sval; } void e2_option_str_set_direct (E2_OptionSet *set, gchar *value) { g_free (set->sval); set->sval = g_strdup (value); } emelfm2-0.4.1/src/config/e2_option_int.h0000600000175000017500000000260711010340377016765 0ustar cairocairo/* $Id: e2_option_int.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_OPTION_INT_H__ #define __E2_OPTION_INT_H__ #include "emelfm2.h" #include "e2_option.h" void e2_option_int_add_widget (GtkWidget *dialog, GtkWidget *box, GtkSizeGroup *size_group, E2_OptionSet *set); E2_OptionSet *e2_option_int_register (gchar *name, gchar *group, gchar *desc, gchar *tip, gchar *depends, gint def, gint min, gint max, E2_OptionFlags flags); gint e2_option_int_get (gchar *option); gint e2_option_int_get_direct (E2_OptionSet *set); gint e2_option_int_set (gchar *option, gint value); gint e2_option_int_set_direct (E2_OptionSet *set, gint value); #endif //ndef __E2_OPTION_INT_H__ emelfm2-0.4.1/src/config/e2_option_tree_context_menu.c0000600000175000017500000002671411007760641021730 0ustar cairocairo/* $Id: e2_option_tree_context_menu.c 868 2008-05-06 04:42:09Z tpgww $ Copyright (C) 2003-2008 tooar Portions copyright (C) 2004 Florian Zaehringer This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /* Florian Zaehringer is the source of inspiration, and some of the code, here */ /** @file src/config/e2_option_tree_context_menu.c @brief context menu functions for config-dialog tree options This file is #included in e2_option_tree.c (i.e. this won't rebuild unless that is rebuilt) */ //prevent building of separate object file #ifdef INCLUDED_IN_PARENT /*******************/ /***** private *****/ /*******************/ /** @brief set popup menu position This function is supplied when calling gtk_menu_popup(), to position the displayed menu. set @a push_in to TRUE for menu completely inside the screen, FALSE for menu clamped to screen size @param menu UNUSED the GtkMenu to be positioned @param x place to store gint representing the menu left @param y place to store gint representing the menu top @param push_in place to store pushin flag @param treeview where the menu is to be shown @return */ static void _e2_option_tree_menu_set_position (GtkMenu *menu, gint *x, gint *y, gboolean *push_in, GtkWidget *treeview) { gint left, top; gtk_window_get_position (GTK_WINDOW (config_dialog), &left, &top); GtkAllocation alloc = treeview->allocation; *x = left + alloc.x + alloc.width; *y = top + alloc.y + alloc.height/2; *push_in = FALSE; } /** @brief create treeview signature This gets the name of the current option. That string is used as a signature to confirm the correct treeview, to enable pasting @param treeview treeview to which the menu belongs @return */ static const gchar *_e2_option_tree_menu_get_signature (GtkTreeView *treeview) { GtkTreeModel *model = gtk_tree_view_get_model (treeview); E2_OptionSet *set = g_object_get_data (G_OBJECT (model), "e2-option-set"); //all toolbars have same data format, so their data are interchangeable E2_ToolbarData **thisbar = app.bars; while (*thisbar != NULL) { if (g_str_equal ((*thisbar)->name, set->name)) return "toolbar"; thisbar++; } return (set->name); } /** @brief tree expand-all callback @param widget the menu item widget which activated the callback @param treeview the treeview to be collapsed @return */ static void _e2_option_tree_expand_all_cb (GtkWidget *widget, GtkTreeView *treeview) { gtk_widget_set_sensitive (widget, FALSE); //get the paired item gpointer m = g_object_get_data (G_OBJECT (widget), "e2-menu-pair"); gtk_widget_set_sensitive (GTK_WIDGET (m), TRUE); e2_tree_expand_all_cb (widget, treeview); } /** @brief tree collapse-all callback @param widget the menu item widget which activated the callback @param treeview the treeview to be collapsed @return */ static void _e2_option_tree_collapse_all_cb (GtkWidget *widget, GtkTreeView *treeview) { gtk_widget_set_sensitive (widget, FALSE); //get the paired button gpointer m = g_object_get_data (G_OBJECT (widget), "e2-menu-pair"); gtk_widget_set_sensitive (GTK_WIDGET (m), TRUE); e2_tree_collapse_all_cb (widget, treeview); } /** @brief context-menu copy callback This is called in response to the copy and cut menu items It works on all selected rows, and all their descendants (though robust relation-checks among various selected rows have NOT been implemented, as multiple selection is not currently allowed) A signature string for the treeview and a list of arrays, each with a path string and a whole row contents, are added to a hash table. The table may contain entries for more than 1 treeview @param menu_item UNUSED the selected menu widget @param treeview where the action is to occur @return */ static void _e2_option_tree_menu_copy_cb (GtkWidget *menu_item, GtkTreeView *treeview) { GtkTreeSelection *sel = gtk_tree_view_get_selection (treeview); if (gtk_tree_selection_count_selected_rows (sel) > 0) { GList *rowscopied = e2_tree_copy (treeview); const gchar *id_string = _e2_option_tree_menu_get_signature (treeview); printd (DEBUG, "adding to buffer: key %s and %d rows", id_string, g_list_length (rowscopied)); g_hash_table_insert (tree_view_buffer_hash, (gchar*)id_string, rowscopied); //NOT replace } } /** @brief context-menu paste callback An id string for the treeview is first constructed, to ensure that the paste is appropriate Pasted row(s) are placed after the selected row in the destination treeview, as child(ren) if the path depth is appropriate @param menu_item the selected menu widget @param treeview widget where the action is to occur @return */ static void _e2_option_tree_menu_paste_cb (GtkWidget *menu_item, GtkTreeView *treeview) { //we only paste after the 1st selected row GtkTreeSelection *sel = gtk_tree_view_get_selection (treeview); if (gtk_tree_selection_count_selected_rows (sel) > 0) { //get data for the current page/view const gchar *id_string = _e2_option_tree_menu_get_signature (treeview); //get the buffered data for the current page GList *copied = g_hash_table_lookup (tree_view_buffer_hash, id_string); if (copied != NULL) { e2_tree_paste (copied, treeview); GtkTreeModel *mdl = gtk_tree_view_get_model (treeview); E2_OptionSet *set = g_object_get_data (G_OBJECT (mdl), "e2-option-set"); e2_option_tree_flag_change (set); } } } /** @brief context-menu cut callback @param menu_item the selected menu widget @param treeview widget where the action is to occur @return */ static void _e2_option_tree_menu_cut_cb (GtkWidget *menu_item, GtkTreeView *treeview) { GtkTreeSelection *sel = gtk_tree_view_get_selection (treeview); if (gtk_tree_selection_count_selected_rows (sel) > 0) { _e2_option_tree_menu_copy_cb (menu_item, treeview); e2_tree_delete (treeview); GtkTreeModel *mdl = gtk_tree_view_get_model (treeview); E2_OptionSet *set = g_object_get_data (G_OBJECT (mdl), "e2-option-set"); e2_option_tree_flag_change (set); } } /** @brief add treeview context menu items @param treeview where the action is to occur @param menu the menu widget to which the items are to be added @param flags indicator of menu items to be created @param set pointer to data struct for the option that is being edited @return */ static void _e2_option_tree_menu_items_add (GtkTreeView *treeview, GtkWidget *menu, E2_TreeContextMenuFlags flags, E2_OptionSet *set) { E2_Nontet *n = e2_utils_nontet_new (); g_object_set_data_full (G_OBJECT (menu), "e2-config-menu", n, (GDestroyNotify) e2_utils_nontet_destroy); if (n == NULL) return; //menu items which modify store content need set for callback data, //other items if ((flags & E2_TREE_CONTEXT_CP_PST) || (flags & E2_TREE_CONTEXT_DEFAULT)) { n->a = e2_menu_add (menu, _("Cu_t"), GTK_STOCK_CUT, _("Cut selected row and any descendant(s)"), _e2_option_tree_menu_cut_cb, treeview); n->b = e2_menu_add (menu, _("_Copy"), GTK_STOCK_COPY, _("Copy selected row and any descendant(s)"), _e2_option_tree_menu_copy_cb, treeview); n->c = e2_menu_add (menu, _("_Paste"), GTK_STOCK_PASTE, _("Paste previously copied or cut row(s) after current row"), _e2_option_tree_menu_paste_cb, treeview); e2_menu_add_separator (menu); } if (flags & E2_TREE_CONTEXT_EXP_COL) { n->d = e2_menu_add (menu, _("_Expand"), GTK_STOCK_ZOOM_IN, _("Expand all rows on this page"), _e2_option_tree_expand_all_cb, treeview); gtk_widget_set_sensitive (GTK_WIDGET(n->d), FALSE); //widget starts with rows expanded n->e = e2_menu_add (menu, _("C_ollapse"), GTK_STOCK_ZOOM_OUT, _("Collapse all rows on this page"), _e2_option_tree_collapse_all_cb, treeview); //set pointers to the paired buttons g_object_set_data (G_OBJECT (n->d), "e2-menu-pair", n->e); g_object_set_data (G_OBJECT (n->e), "e2-menu-pair", n->d); e2_menu_add_separator (menu); //more items added in the calling function } n->f = e2_menu_add (menu, _("_Add"), GTK_STOCK_ADD, _("Add a row after the current one"), _e2_option_tree_add_below_cb, set); if (flags & E2_TREE_CONTEXT_EXP_COL) n->g = e2_menu_add (menu, _("Add c_hild"), GTK_STOCK_INDENT, _("Add a child to the selected row"), _e2_option_tree_add_child_cb, set); e2_menu_add_separator (menu); n->h = e2_menu_add (menu, _("_Up"), GTK_STOCK_GO_UP, _("Move selected row up"), _e2_option_tree_move_up_cb, set); n->i = e2_menu_add (menu, _("_Down"), GTK_STOCK_GO_DOWN, _("Move selected row down"), _e2_option_tree_move_down_cb, set); } /******************/ /***** public *****/ /******************/ /** @brief (de)sensitize tree-option context-menu items This changes the sensitivity of the paste menu item, depending on whether the current treeview's set name is a key in the hash buffer @param menu the context-menu widget @param treeview widget where the menu was initiated @return */ void e2_option_tree_menu_set_sensitive (GtkWidget *menu, GtkWidget *treeview) { E2_Nontet *m = g_object_get_data (G_OBJECT (menu), "e2-config-menu"); if (m == NULL) return; gboolean state; //first deal with the paste item if (m->c != NULL) { const gchar *id_string = _e2_option_tree_menu_get_signature ( GTK_TREE_VIEW (treeview)); state = (g_hash_table_lookup (tree_view_buffer_hash, id_string) != NULL); //FIXME also check for valid path depth of currently-selected item ? gtk_widget_set_sensitive (m->c, state); } //now adjust other menu items to conform with the page buttons E2_Sextet *s2 = g_object_get_data (G_OBJECT (treeview), "e2-config-buttons"); if (s2 == NULL) return; if (s2->b != NULL) //up { state = GTK_WIDGET_SENSITIVE(s2->b); if (m->h != NULL) //up gtk_widget_set_sensitive (m->h, state); } if (s2->c != NULL) //down { state = GTK_WIDGET_SENSITIVE(s2->c); if (m->i != NULL) //down gtk_widget_set_sensitive (m->i, state); } if (s2->d != NULL) //remove { state = GTK_WIDGET_SENSITIVE(s2->d); if (m->a != NULL) //cut gtk_widget_set_sensitive (m->a, state); if (m->b != NULL) //copy gtk_widget_set_sensitive (m->b, state); } if (s2->e != NULL) //add { state = GTK_WIDGET_SENSITIVE(s2->e); if (m->f != NULL) //add gtk_widget_set_sensitive (m->f, state); } if (s2->f != NULL) //add child { state = GTK_WIDGET_SENSITIVE(s2->f); if (m->g != NULL) //add child gtk_widget_set_sensitive (m->g, state); } } /** @brief free memory used by tree-option context menu hash table entry Each entry in the hash buffer is a glist of value arrays, one array for each row copied to the buffer The first item in an array is the the row's source-view path @param list glist to be freed @return */ void e2_option_tree_menu_hash_clean (GList *list) { GList *tmp; for (tmp = list; tmp != NULL; tmp = tmp->next) { //clear treepath at start of each array GValueArray *value_array = tmp->data; GValue *value = g_value_array_get_nth (value_array, 0); GtkTreePath *path = (GtkTreePath *) g_value_get_pointer (value); gtk_tree_path_free (path); g_value_array_free (value_array); } g_list_free (list); } #endif //def INCLUDED_IN_PARENT emelfm2-0.4.1/src/config/e2_option_unknown.c0000600000175000017500000000677710643544426017715 0ustar cairocairo/* $Id: e2_option_unknown.c 469 2007-07-06 22:58:30Z tpgww $ Copyright (C) 2004-2007 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "emelfm2.h" #include "e2_option.h" //#include "e2_option_unknown.h" /** @brief record a transient option string Any already-registerd option with the same name will be removed first. For single-valued options, @a string will be like name=value For tree-options, @a string will be multi-lined, like @param option string with the name of the option @param value option string, in config file format @return */ void e2_option_unknown_record (gchar *option, gchar *value) { g_hash_table_replace (options_queue, option, value); } /** @brief get option value(s) from un-registered options list @param set option data struct, including default values @return */ void e2_option_transient_value_get (E2_OptionSet *set) { gchar *value; gchar **split; if ((value = g_hash_table_lookup (options_queue, set->name)) != NULL) { if (set->type == E2_OPTION_TYPE_TREE) { split = g_strsplit (value, "\n", -1); //parse the data e2_option_tree_set_from_array (set->name, split, NULL, NULL); } else { split = g_strsplit (value, "=", 2); //store value for the set if (split[1] != NULL) e2_option_set_value_from_string (set, split[1]); else printd (WARN, "could not find value for option '%s'", set->name); } g_strfreev (split); g_hash_table_remove (options_queue, set->name); } } /* model for a transient tree option static void _e2_cfgdlg_tree_defaults (E2_OptionSet *set) { e2_option_tree_setup_defaults (set, g_strdup("unknown-tree=<", NULL), g_strdup("STRING 10"), g_strdup("string 20"), g_strdup("STRING 30"), g_strdup(">"), NULL); } E2_OptionSet *set = e2_option_tree_register ("unknown-tree", "unknown-tree", "unknown-tree", //no translation NULL, NULL, NULL, E2_OPTION_TREE_UP_DOWN | E2_OPTION_TREE_ADD_DEL, E2_OPTION_FLAG_BASIC | E2_OPTION_FLAG_BUILDBARS); e2_option_tree_add_column (set, _("Label"), E2_OPTION_TREE_TYPE_STR, 0, "", NULL, NULL, 0, NULL, NULL); [more columns] e2_option_tree_create_store (set); // e2_option_tree_prepare_defaults (set, _e2_cfgdlg_tree_defaults); e2_option_transient_value_get (set); if (!set->ex.tree.synced) //option needs default data { // void (*install_func) (E2_OptionSet *) = set->ex.tree.def; // (*install_func) (set); _e2_cfgdlg_tree_defaults (set); //parse the data e2_option_tree_set_from_array (set->name, (gchar **) set->ex.tree.def, NULL, NULL); //cleanup g_strfreev ((gchar **) set->ex.tree.def); } set->ex.tree.def = NULL; //always zap the function/vector pointer } model for a transient int option { e2_option_int_register ("unknown-int", group_name, _("label"), _("Tip"), NULL, 50, 0, 10000, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP); //no rebuild e2_option_transient_value_get (set); } */ emelfm2-0.4.1/src/config/e2_option_str.h0000600000175000017500000000254411010340377017003 0ustar cairocairo/* $Id: e2_option_str.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_OPTION_STR_H__ #define __E2_OPTION_STR_H__ #include "emelfm2.h" #include "e2_option.h" E2_OptionSet *e2_option_str_register (gchar *name, gchar *group, gchar *desc, gchar *tip, gchar *depends, gchar *def, E2_OptionFlags flags); E2_OptionSet *e2_option_font_register (gchar *name, gchar *group, gchar *desc, gchar *tip, gchar *depends, gchar *def, E2_OptionFlags flags); gchar *e2_option_str_get (gchar *option); gchar *e2_option_str_get_direct (E2_OptionSet *set); void e2_option_str_set_direct (E2_OptionSet *set, gchar *value); #endif //ndef __E2_OPTION_STR_H__ emelfm2-0.4.1/src/config/e2_cl_option.h0000600000175000017500000000301011010340377016556 0ustar cairocairo/* $Id: e2_cl_option.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2003-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_CL_OPTION_H__ #define __E2_CL_OPTION_H__ #include "emelfm2.h" typedef struct _E2_CommandLineOptions { gchar *pane1_path; //entered string, converted before use to utf-8 if needed gchar *pane2_path; //ditto gchar *config_dir; //utf-8 string, no trailing / gchar *trash_dir; //ditto gchar *encoding; gchar *fallback_encoding; gchar **overrides; #ifdef DEBUG_MESSAGES gint debug_level; #endif gboolean detached; gboolean verbose; gboolean suppress_gtk_log; gboolean ignore_problems; GList *option_overrides; GList *startup_commands; } E2_CommandLineOptions; E2_CommandLineOptions e2_cl_options; //FIXME cleanup when session ends ? void e2_cl_option_process (gint argc, gchar *argv[]); #endif //ndef __E2_CL_OPTION_H__ emelfm2-0.4.1/src/config/e2_option_color.c0000600000175000017500000001746510643544426017330 0ustar cairocairo/* $Id: e2_option_color.c 469 2007-07-06 22:58:30Z tpgww $ Copyright (C) 2004-2007 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "emelfm2.h" #include #include "e2_option.h" #include "e2_option_color.h" #ifdef E2_RAINBOW /** @brief setup heap space for a block of file-text color data Allocates memory for ATOMSPERCHUNK GdkColor data structs and their associated pointers. @return pointer to array of GdkColors, or NULL */ static GdkColor **_e2_option_color_create_chunk (void) { E2_ColorData *data = MALLOCATE (E2_ColorData); //FIXME too small for slice CHECKALLOCATEDWARN (data, return NULL;) //space for a bunch of pointers data->pointers = g_try_malloc (sizeof (gpointer) * ATOMSPERCHUNK); //never freed CHECKALLOCATEDWARN (data->pointers, ) if (data->pointers == NULL) { DEMALLOCATE (E2_ColorData, data); return NULL; } gint i; //a GMemChunk for the associated color structs //CHECKME we do not need the chunk itself to keep track of //freed space when an extension is repeated, hence re-hashed ? data->chunk = g_mem_chunk_new (NULL, sizeof(GdkColor), sizeof(GdkColor)*ATOMSPERCHUNK, G_ALLOC_ONLY); //allocate all its atoms for (i = 0; i < ATOMSPERCHUNK; i++) data->pointers[i] = g_chunk_new (GdkColor, data->chunk); //remember the array and chunk addresses, for later use app.colorchunks = g_list_append (app.colorchunks, data); return (data->pointers); } /** @brief cleanup all space/data for color data structs @return */ static void _e2_option_color_clear_data (void) { if (app.colors != NULL) { g_hash_table_destroy (app.colors); app.colors = NULL; } if (app.colorchunks != NULL) { GList *tmp; for (tmp = app.colorchunks; tmp != NULL; tmp = tmp->next) { E2_ColorData *data = (E2_ColorData *) tmp->data; g_mem_chunk_destroy (data->chunk); g_free (data->pointers); DEMALLOCATE (E2_ColorData, data); //too small for slice } g_list_free (app.colorchunks); app.colorchunks = NULL; } } /** @brief scan all filetypes in the config data, to parse any associated text-color Filetype extensions are hashed in localised form, for faster comparison when loading filelists Any color string in the field adjacent to a catgory or extension is converted to color data Extension color will over-ride category color @return */ void e2_option_color_filetypes_sync (void) { E2_OptionSet *typeset = e2_option_get_simple ("filetypes"); if (typeset == NULL) return; //can't do anything _e2_option_color_clear_data (); app.colors = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); //data are chunked, and not worth individually clearing gint i = ATOMSPERCHUNK; //force initial chunk creation //#warning ignore compiler warning about unitialized usage of id GdkColor **id = NULL; //assignment for complier-warning prevention only gchar *def_color; gboolean freedef; GtkTreeModel *mdl = typeset->ex.tree.model; GtkTreeIter iter; if (gtk_tree_model_get_iter_first (mdl, &iter)) { do { GtkTreeIter iter2, iter3; //for cfg levels gchar *node_label, *ext_type, *ext_color; //should always be level-2 child nodes, but test anyway if (gtk_tree_model_iter_children (mdl, &iter2, &iter)) { do { // extension or command loop = level 2 gtk_tree_model_get (mdl, &iter2, 1, &node_label, -1); if (gtk_tree_model_iter_children (mdl, &iter3, &iter2)) { if (g_str_equal (node_label, _C(13))) //extensions node found { //get category-default color, if any gtk_tree_model_get (mdl, &iter2, 2, &def_color, -1); //CHECKME better to dup the hashed string ? freedef = TRUE; //assume the default will not be used do { //extension loop = level 3, process the extensions gtk_tree_model_get (mdl, &iter3, 1, &ext_type, 2,&ext_color, -1); //ignore the pseudo-file-types & empty colors if (!g_str_has_prefix (ext_type, "<")) { gchar *usecolor, *local; if (*ext_color != '\0') usecolor = ext_color; else if (*def_color != '\0') { usecolor = def_color; freedef = FALSE; } else usecolor = NULL; if (usecolor != NULL) { if (i == ATOMSPERCHUNK) { //full (or no) chunk, create another id = _e2_option_color_create_chunk (); i = 0; } gdk_color_parse (usecolor, id[i]); local = F_FILENAME_TO_LOCALE (ext_type); if (local != ext_type) //conversion really happened g_free (ext_type); //CHECKME do we want to cleanup any replaced color struct ? g_hash_table_replace (app.colors, local, id[i]); i++; } } else { //clean this if not hashed g_free (ext_type); } g_free (ext_color); } while (gtk_tree_model_iter_next (mdl, &iter3)); if (freedef) g_free (def_color); } else if (!g_str_equal (node_label, _C(6))) //commands node is ignored { //OOPS printd (WARN, "un-recognised node in filetypes config"); } } g_free (node_label); //just cleanup } while (gtk_tree_model_iter_next (mdl, &iter2)); } } while (gtk_tree_model_iter_next (mdl, &iter)); } } #endif /** @brief register new color option register new color option. @param name name of the option, a constant string @param group group the option belongs to, used in config dialog, a r-t string FREEME @param desc textual description of the option used in config dialog, a r-t _() string FREEME ? @param tip tooltip used when displaying the config dialog, a _() string @param depends name of another option this one depends on, or NULL @param value value for the option @param flags bitflags determining how the option data is to be handled @return E2_OptionSet of the new option */ E2_OptionSet *e2_option_color_register (gchar *name, gchar *group, gchar *desc, gchar *tip, gchar *depends, gchar *value, E2_OptionFlags flags) { E2_OptionSet *set = e2_option_register (E2_OPTION_TYPE_COLOR, name, group, desc, tip, depends, flags); set->ival = -1; if (gdk_color_parse (value, &set->ex.color.value)) set->sval = g_strdup (value); else { set->sval = g_strdup ("#000000"); gdk_color_parse (set->sval, &set->ex.color.value); } return set; } GdkColor *e2_option_color_get (gchar *name) { E2_OptionSet *set = e2_option_get (name); if (set == NULL) return NULL; if (set->type == E2_OPTION_TYPE_COLOR) { return &set->ex.color.value; } else { printd (WARN, "trying to get color option '%s', which isn't a color option", set->name); return NULL; } } gboolean e2_option_color_set_str (gchar *name, gchar *value) { E2_OptionSet *set = e2_option_get (name); if (set->type == E2_OPTION_TYPE_COLOR) { return e2_option_color_set_str_direct (set, value); } else { printd (WARN, "trying to set color option '%s', which isn't a color option", set->name); return FALSE; } } gboolean e2_option_color_set_str_direct (E2_OptionSet *set, gchar *value) { if (!gdk_color_parse (value, &set->ex.color.value)) { gdk_color_parse (set->sval, &set->ex.color.value); return FALSE; } g_free (set->sval); set->sval = g_strdup (value); return TRUE; } emelfm2-0.4.1/src/config/e2_option_bool.c0000600000175000017500000001061510772054116017126 0ustar cairocairo/* $Id: e2_option_bool.c 841 2008-03-25 01:41:34Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/config/e2_option_bool.c @brief boolean options handling boolean options handling */ #include "emelfm2.h" #include "e2_option.h" #include "e2_option_bool.h" /** @brief register new boolean option register new boolean option. @param name name of the option, generally a constant string but sometimes runtime-created @param group group the option belongs to, used in config dialog, a r-t string @param desc textual description of the option used in config dialog, a r-t _() string @param tip tooltip used when displaying the config dialog, a _() string @param depends name of another option this one depends on @param value value for the option @param flags bitflags determining how the option data is to be handled @return E2_OptionSet of the new option */ E2_OptionSet *e2_option_bool_register (gchar *name, gchar *group, gchar *desc, gchar *tip, gchar *depends, gboolean value, E2_OptionFlags flags) { E2_OptionSet *set = e2_option_register (E2_OPTION_TYPE_BOOL, name, group, desc, tip, depends, flags); //set ->i/sval set->hook_freezed = TRUE; e2_option_bool_set_direct (set, value); set->hook_freezed = FALSE; return set; } /** @brief get current value of boolean option @param option name of the option @return current value (T/F) of the option */ gboolean e2_option_bool_get (gchar *option) { E2_OptionSet *set = e2_option_get (option); if (set == NULL) { printd (WARN, "trying to get option '%s' which doesn't exist", option); return FALSE; } if (set->type == E2_OPTION_TYPE_BOOL) return e2_option_bool_get_direct (set); else { printd (WARN, "trying to get option '%s' as boolean", option); return FALSE; } } /** @brief get current value of boolean option @param set pointer to data struct for the option @return current value (T/F) of the option */ gboolean e2_option_bool_get_direct (E2_OptionSet *set) { return set->ival; } /** @brief toggle value of boolean option @param option name of the option @return the new value (T/F) of the option */ gboolean e2_option_bool_toggle (gchar *option) { E2_OptionSet *set = e2_option_get (option); if (set == NULL) return FALSE; if (set->type == E2_OPTION_TYPE_BOOL) return e2_option_bool_toggle_direct (set); else { printd (WARN, "trying to toggle bool option '%s', which isn't a bool", set->name); return FALSE; } } /** @brief toggle value of boolean option @param set pointer to data struct for the option @return the new value (T/F) of the option */ gboolean e2_option_bool_toggle_direct (E2_OptionSet *set) { if (e2_option_bool_get_direct (set)) { e2_option_bool_set_direct (set, FALSE); return FALSE; } else { e2_option_bool_set_direct (set, TRUE); return TRUE; } } /** @brief set current value of boolean option @param option name of the option @param value the state (T/F) to be set @return the new value (T/F) of the option, FALSE if it doesn't exist */ gboolean e2_option_bool_set (gchar *option, gboolean value) { E2_OptionSet *set = e2_option_get (option); if (set == NULL) return FALSE; if (set->type == E2_OPTION_TYPE_BOOL) e2_option_bool_set_direct (set, value); else { printd (WARN, "trying to bool set option '%s' which isn't a bool", option); return FALSE; } return value; } /** @brief set current value of boolean option @param set pointer to data struct for the option @param value the state (T/F) to be set @return the new value (T/F) of the option */ gboolean e2_option_bool_set_direct (E2_OptionSet *set, gboolean value) { set->ival = value; set->sval = (value) ? "true" : "false"; if (!set->hook_freezed) e2_hook_list_run (&set->hook_value_changed, GINT_TO_POINTER (value)); return value; } emelfm2-0.4.1/src/config/e2_option__default.c0000600000175000017500000004276610714030042017756 0ustar cairocairo/* $Id: e2_option__default.c 706 2007-11-06 09:13:06Z tpgww $ Copyright (C) 2003-2007 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/config/e2_option__default.c @brief default place to register all config options Registration of all configuration options, at session start The order of registration determines the order of the config dialog */ #include "emelfm2.h" #include "e2_option.h" #include "e2_dialog.h" #include "e2_mkdir_dialog.h" #include "e2_toolbar.h" #include "e2_context_menu.h" #include "e2_plugins.h" #include "e2_filetype.h" /** @brief setup and register all options, plus a few other initialisation tasks Option values, tips etc are established. Each option has flags which indicate the way it should be presented in a config dialog, what things (esp. part(s) of the main window) need to be rebuilt if the option value is changed in a config dialog, and what cleanups are needed if the config data are dumped. Tree option data is not stored yet, but only prepared for storage. After the config file (if any) is parsed, and relevant tree option data in that file are processed, the 'prepared' tree data are either stored or abandoned, as need be. See e2_option_trees_confirm(). The order of the entries in this function determines the order of options shown in a config dialog. As a convenience, some of the included functions involve a few initialisation tasks which are not strictly option-related. @return */ void e2_option_default_register (void) { printd (DEBUG, "registering all default (non-tree) options"); /*******************/ /***** general *****/ /*******************/ //NOTE: the option after each 'constructed' group_name string must include E2_OPTION_FLAG_FREEGROUP gchar *group_name = g_strconcat(_C(17),":",_C(25),NULL); //_("general:miscellaneous" e2_option_bool_register ("advanced-config", group_name, _("show all options in config dialogs"), NULL, NULL, FALSE, E2_OPTION_FLAG_HIDDEN | E2_OPTION_FLAG_FREEGROUP); //no rebuild e2_option_bool_register ("auto-refresh-config", group_name, _("reload config on external change"), _("This enables automatic reloading of the configuration data for this program, if that data is changed by another program instance"), NULL, TRUE, E2_OPTION_FLAG_ADVANCED); //no rebuild /* e2_option_int_register ("sort-before-show-count", group_name, _("threshold number of directory items processed before display"), _("Directories with more than this many entries will be displayed in an interim state, until loading and sorting are completed"), NULL, 100, 0, 1<<(sizeof (gint)*4, E2_OPTION_FLAG_ADVANCED); ); */ gchar *utf = F_FILENAME_FROM_LOCALE (DOC_DIR G_DIR_SEPARATOR_S MAIN_HELP); e2_option_str_register ("usage-help-doc", group_name, _("document containing usage advice"), _("This document is opened from the help dialog usage page"), NULL, utf, E2_OPTION_FLAG_ADVANCED); //no rebuild F_FREE (utf); utf = F_FILENAME_FROM_LOCALE (DOC_DIR G_DIR_SEPARATOR_S CFG_HELP); e2_option_str_register ("config-help-doc", group_name, _("document containing configuration advice"), _("This document is opened from the help dialog configuration page"), NULL, utf, E2_OPTION_FLAG_ADVANCED); //no rebuild F_FREE (utf); e2_option_bool_register ("session-end-warning", group_name, _("warn about running processes when shutting down"), _("This enables a reminder about incomplete commands and actions"), NULL,TRUE, E2_OPTION_FLAG_ADVANCED); //no rebuild e2_bookmark_options_register (); e2_command_options_register (); e2_filetype_options_register(); e2_plugins_options_register (); /*********************/ /***** interface *****/ /*********************/ group_name = g_strconcat(_C(20) ,":",_C(25),NULL); //_("interface:miscellaneous" #ifdef E2_COMPOSIT // UNCOMMENT THIS e2_option_int_register ("window-opacity", group_name, _("opacity"), _("Window translucence, 30 (faint) to 100 (opaque)"), NULL, 100, 30, 100, E2_OPTION_FLAG_BASIC | E2_OPTION_FLAG_BUILDALL); #endif e2_option_bool_register ("button2-updir", group_name, _("'go up' on middle-button click"), _("This is a faster alternative to double clicking '..' or clicking the 'go up' button"), NULL, TRUE, E2_OPTION_FLAG_BASIC | E2_OPTION_FLAG_BUILDPANES | E2_OPTION_FLAG_FREEGROUP); e2_option_bool_register ("windows-right-click", group_name, _("match windows (TM) right-click behaviour"), _("If activated, clicking the right mouse button will also select the row where the mouse cursor is"), NULL, FALSE, E2_OPTION_FLAG_ADVANCED); //no rebuild e2_option_bool_register ("windows-right-click-extra", group_name, _("advanced windows (TM) right-click behaviour"), _("If activated, clicking on a free area will not clear the current selection"), "windows-right-click", FALSE, E2_OPTION_FLAG_ADVANCED); //no rebuild e2_option_bool_register ("dialog-button-icons", group_name, _("show icons in dialog buttons"), _("If activated, dialog buttons will show an icon as well as a label"), NULL, TRUE, E2_OPTION_FLAG_BASIC); //no rebuild group_name = g_strconcat(_C(20) ,":",_C(19),NULL); //_("interface:icons" e2_option_bool_register ("use-icon-dir", group_name, _("use icons directory"), _("If activated, icon files in the directory shown below will be used for toolbars etc"), NULL, FALSE, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_BUILDICONS | E2_OPTION_FLAG_BUILDBARS | E2_OPTION_FLAG_FREEGROUP); e2_option_str_register ("icon-dir", group_name, _("icons directory"), _("The directory from which icon files will be retrieved"), "use-icon-dir", "/YOURHOME/."BINNAME, E2_OPTION_FLAG_BUILDICONS | E2_OPTION_FLAG_BUILDBARS | E2_OPTION_FLAG_ADVANCED); e2_keybinding_options_register (); e2_menu_custom_option_register (); /***************************/ /***** interface.menus *****/ /***************************/ group_name = g_strconcat(_C(20),".",_C(24),":",_C(37),NULL); //_("interface.menus:style" e2_option_bool_register ("menu-show-icons", group_name, _("show icons in menus"), _("Some people think that icons help you recognize items more quickly"), NULL, TRUE, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP); //no rebuild const gchar *opt_menu_isize[] = {_("menu"), _("toolbar small"), _("toolbar large"), _("button"), _("dnd"), _("dialog"), NULL}; e2_option_sel_register ("menu-isize", group_name, _("menu icon size"), _("This sets the icon size for ALL menus"), "menu-show-icons", 0, opt_menu_isize, E2_OPTION_FLAG_ADVANCED); //no rebuild group_name = g_strconcat(_C(20),".",_C(24),":",_C(10),NULL); //_("interface.menus:delays" e2_option_int_register ("submenu-up-delay", group_name, _("menu popup delay (ms)"), _("The delay (in milliseconds) after the mouse pointer arrives at a menu item, before any submenu will pop up"), NULL, 50, 0, 10000, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP); //no rebuild e2_option_int_register ("submenu-down-delay", group_name, _("menu popdown delay (ms)"), _("The delay (in milliseconds) after the mouse pointer leaves a menu item, before popping down an activated submenu"), NULL, 400, 0, 10000, E2_OPTION_FLAG_ADVANCED); //no rebuild /*****************/ /***** panes *****/ /*****************/ group_name = g_strconcat(_C(32) ,":",_C(25),NULL); //_("panes:miscellaneous" //FREEME group_name not here ! e2_option_bool_register ("auto-refresh", group_name, _("auto refresh"), _("This enables automatic checking for changes to the content of displayed directories. Otherwise you need to refresh manually"), NULL, TRUE, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP ); //no rebuild e2_option_bool_register ("dir-line-focus-after-activate", group_name, _("focus the pane after completing a directory entry"), _("If activated, after return/enter is pressed in a directory line, the pane containing that dir line will become the active one"), NULL, TRUE, E2_OPTION_FLAG_ADVANCED); //no rebuild e2_option_bool_register ("select-first-item", group_name, _("select first item in newly-opened directories"), _("If activated, when a directory is first displayed in a session, the first item there will be selected"), NULL, FALSE, E2_OPTION_FLAG_ADVANCED); //no rebuild group_name = g_strconcat(_C(32) ,":",_C(37),NULL); //_("panes:style" e2_option_bool_register ("panes-horizontal", group_name, _("use horizontal panes"), _("Horizontal (vertical-stacked) panes present more columns at once"), NULL, #ifdef E2_PANES_HORIZONTAL TRUE, #else FALSE, #endif E2_OPTION_FLAG_BASIC | E2_OPTION_FLAG_FREEGROUP | E2_OPTION_FLAG_BUILDALL); const gchar *opt_scrollbar_position[] = {_("bottom-right"), _("top-right"), _("bottom-left"), _("top-left"), NULL}; e2_option_sel_register ("scrollbar-position", group_name, _("scrollbar position"), _("The default (bottom-right) should be ok for most users"), NULL, 0, opt_scrollbar_position, //make this more informative E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_BUILDPANES); const gchar *opt_menu_activeflag[] = {_("colored headers"), _("bold name header"),_("sensitivity"),NULL}; e2_option_sel_register ("active-pane-signal", group_name, _("type of indicator for active pane"), _("This determines how the active pane is indicated on-screen. Some GTK themes do not allow column-header re-coloring"), NULL, 0, opt_menu_activeflag, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_BUILDPANES); e2_option_bool_register ("panes-hinted", group_name, _("banded background"), _("If activated, lines in file lists will alternate background color"), NULL, FALSE, E2_OPTION_FLAG_BASIC | E2_OPTION_FLAG_BUILDPANES); e2_option_bool_register ("custom-list-font", group_name, _("use custom font"), _("If activated, the font specified below will be used, instead of the theme default"), NULL, FALSE, E2_OPTION_FLAG_BASIC | E2_OPTION_FLAG_BUILDLISTS); e2_option_font_register ("list-font", group_name, _("custom font"), _("This is the font used for flle pane text"), "custom-list-font", "Sans 10",//_I( font name E2_OPTION_FLAG_BASIC | E2_OPTION_FLAG_BUILDLISTS); const gchar *opt_filelist_date_string[] = {_("Default: May 20 09:11"), _("Standard: 20/05/04 09:11"), _("American: 05/20/04 09:11"), "ISO8601: 2004-05-20T09:11", _("LC_TIME locale specified"), NULL}; //no need to translate ISO string e2_option_sel_register ("date-string", group_name, _("date format"), _("This determines the format of all dates displayed in the panes"), NULL, 0, opt_filelist_date_string, E2_OPTION_FLAG_BASIC | E2_OPTION_FLAG_BUILDLISTS); const gchar *opt_filelist_size_string[] = {_("condensed"), _("exact"), NULL}; e2_option_sel_register ("size-string", group_name, _("size format"), _("Displayed item-sizes can be 'condensed' to show kB, MB where appropriate"), NULL, 1, opt_filelist_size_string, E2_OPTION_FLAG_BASIC | E2_OPTION_FLAG_BUILDLISTS); e2_option_bool_register ("show-updir-entry", group_name, _("show parent directory entry '..' in file lists"), _("This slows status-line updates"), NULL, FALSE, E2_OPTION_FLAG_BASIC | E2_OPTION_FLAG_BUILDLISTS); e2_option_bool_register ("namesort-case-sensitive", group_name, _("filename sort is case sensitive"), _("This places all the capitalised file/directory names ahead of the others, if your LANG_C suports that"), NULL, FALSE, E2_OPTION_FLAG_BASIC | E2_OPTION_FLAG_BUILDLISTS); e2_option_bool_register ("pane2-uses-other", group_name, _("both panes are like pane 1"), g_strdup_printf ( _("This makes pane %d options (other than toolbar placement and content) apply to pane %d"), 1,2), "!pane1-uses-other", FALSE, E2_OPTION_FLAG_BASIC | E2_OPTION_FLAG_FREETIP); e2_option_bool_register ("pane1-uses-other", group_name, _("both panes are like pane 2"), g_strdup_printf ( _("This makes pane %d options (other than toolbar placement and content) apply to pane %d"), 2,1), "!pane2-uses-other", FALSE, E2_OPTION_FLAG_BASIC | E2_OPTION_FLAG_FREETIP); /******************/ /***** colors *****/ /******************/ group_name = g_strconcat(_C(32),".",_C(2),":",_C(21),NULL); //_("panes.colors:filetypes" e2_option_color_register ("color-ft-exec", group_name, _("executable"), _("Executable file names are listed in this color"), NULL, "forest green", E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP | E2_OPTION_FLAG_BUILDLISTS); e2_option_color_register ("color-ft-dir", group_name, _("directory"), _("Directory names are listed in this color"), NULL, "blue", E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_BUILDLISTS); e2_option_color_register ("color-ft-link", group_name, _("symlink"), _("Symbolic link names are listed in this color"), NULL, "sky blue", E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_BUILDLISTS); e2_option_color_register ("color-ft-dev", group_name, _("device"), _("Device names are listed in this color"), NULL, "orange", E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_BUILDLISTS); e2_option_color_register ("color-ft-socket", group_name, _("socket"), _("Sockets are listed in this color"), NULL, "purple", E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_BUILDLISTS); group_name = g_strconcat(_C(32),".",_C(2),":",_C(25),NULL); //_("panes.colors:miscellaneous" #ifdef E2_ASSISTED e2_option_bool_register ("color-background-set", group_name, _("custom background color"), _("If enabled, the color specified below will be used for background"), NULL, FALSE, E2_OPTION_FLAG_BASIC | E2_OPTION_FLAG_BUILDLISTS); e2_option_color_register ("color-background", group_name, _("background color"), _("Background color used instead of theme color"), "color-background-set", "#2E3436", E2_OPTION_FLAG_BASIC | E2_OPTION_FLAG_BUILDLISTS); #endif e2_option_color_register ("color-active-pane", group_name, _("active pane header color"), _("This color is used for the background of column headers in the active pane"), NULL, "dark grey", E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP | E2_OPTION_FLAG_BUILDPANES); e2_option_color_register ("color-highlight", group_name, _("highlight color"), _("This color is used for the background of highlighed item-names and other text"), NULL, "wheat", E2_OPTION_FLAG_ADVANCED); //no rebuild /*just use theme color now e2_option_color_register ("color-inactive-selected", group_name, _("inactive selected item color"), _("This is the background color used for selected files and directories in the inactive pane"), "light grey", E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_BUILDLISTS); tags not supported now e2_option_color_register ("color-tag", group_name, _("tagged item color"), _("This is the background color used for tagged files and directories"), NULL, "yellow", E2_OPTION_FLAG_ADVANCED); */ e2_pane_options_register (1); e2_pane_options_register (2); /********************/ /***** toolbars *****/ /********************/ group_name = g_strconcat(_C(20) ,":",_C(37),NULL); const gchar *opt_toolbar_style[] = {_("theme"),_("icon only"), _("label only"), _("icon above label"), _("icon beside label"), NULL}; gchar *tip = g_strdup_printf (_("'%s' uses the Gtk default, '%s' leaves most space for other things"), opt_toolbar_style[0], opt_toolbar_style[1]); e2_option_sel_register ("allbars-style", group_name, _("toolbars button style"), tip, NULL, 1, opt_toolbar_style, E2_OPTION_FLAG_BASICONLY | E2_OPTION_FLAG_FREEGROUP | E2_OPTION_FLAG_FREETIP | E2_OPTION_FLAG_BUILDBARS | E2_OPTION_FLAG_BUILDSAMEBARS); const gchar *opt_toolbar_isize[] = {_("theme"),_("menu"), _("toolbar small"), _("toolbar large"), _("button"), _("dnd"), _("dialog"),NULL}; tip = g_strdup_printf (_("'%s' uses the Gtk default, '%s' is smallest, '%s' is largest"), opt_toolbar_isize[0], opt_toolbar_isize[1], opt_toolbar_isize[6]); e2_option_sel_register ("allbars-isize", group_name, _("toolbars icon size"), tip, NULL, 2, opt_toolbar_isize, E2_OPTION_FLAG_BASICONLY | E2_OPTION_FLAG_FREETIP | E2_OPTION_FLAG_BUILDBARS | E2_OPTION_FLAG_BUILDSAMEBARS); e2_toolbar_toolbaroptions_register (); /**********************/ /***** commandbar *****/ /**********************/ e2_toolbar_commandbaroptions_register (); // this is a consolidated list of all the 'externals' here //above e2_pane_options_register (_C(32), _C(29), 1); //_(panes: panebar 1 //above e2_pane_options_register (_C(32), _C(31), 2); //_(panes: panebar 2 //above e2_toolbar_toolbaroptions_register (); //above e2_toolbar_commandbaroptions_register () //above e2_context_menu_options_register (); //above e2_plugins_options_register (); e2_alias_init(); //this also does some other init stuff e2_command_line_options_register (); //above e2_bookmark_options_register (); //above e2_command_options_register (); /************************/ /***** context menu *****/ /************************/ e2_context_menu_options_register (); e2_output_options_register (); e2_dialog_options_register (); e2_mkdir_dialog_options_register (); // e2_search_dialog_options_register (); e2_view_dialog_options_register (); e2_edit_dialog_options_register (); // keybindings have a separate data structure too //above e2_keybinding_init(); //this also does some other stuff //above e2_filetype_options_register(); } emelfm2-0.4.1/src/config/e2_cache.c0000600000175000017500000010413410776374264015663 0ustar cairocairo/* $Id: e2_cache.c 851 2008-04-07 10:34:28Z tpgww $ Copyright (C) 2004 Florian Zaehringer (flo.zaehringer@web.de) Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/config/e2_cache.c @brief cache system This file contains functions for e2's cache system: eg. to register and unregister caches, and to write and read them to/from disk. */ /** \page cache cacheing of emelFM2 runtime data ToDo description of how cacheing works */ #include "e2_cache.h" #include #include "e2_fs.h" #include "e2_task.h" static gchar *default_cache_file; static GList *cache_list; static GList *unknown_list; /*****************/ /***** utils *****/ /*****************/ /** @brief check whether the name member of @a cache_element matches @a name @param cache_element struct with member to compare @param name name string to compare @return 0 if @a cache_element matches */ static gint _e2_cache_str_compare (E2_Cache *cache_element, const gchar *name) { return !g_str_equal (name, cache_element->name); } #ifdef E2_IMAGECACHE /** @brief cleanup helper for icon hash @param data pointer to E2_Image struct for an icon */ static void _e2_cache_iconhash_remove (E2_Image *data) { g_object_unref (G_OBJECT (data->pixbuf)); DEMALLOCATE (E2_Image, data); } /******************/ /***** public *****/ /******************/ /** @brief setup for icon caching The destroy func assumes that pixbufs have refcount 1 then when this is called */ void e2_cache_icons_init (void) { app.icons = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) _e2_cache_iconhash_remove); e2_utils_init_icon_sizes (); } /** @brief helper func to clear cached icon pixbuf @param key UNUSED key of hash table item being processed @param value value of hash table item being processed @param user_data UNUSED data specified when foreach was initiated @return */ /*static void _e2_cache_image_clear (gpointer key, E2_Image *value, gpointer user_data) { value->refcount--; // g_object_unref (G_OBJECT (value->pixbuf)); } */ /** @brief clear all cached icon pixbufs from X memory @return */ void e2_cache_image_clearall (void) { // g_hash_table_foreach (app.icons, (GHFunc) _e2_cache_image_clear, NULL); g_hash_table_destroy (app.icons); } /** @brief get cached image for icon named @a name with size @a size If not already cached, the relevant image will be created and added to the cache @param name gtk-stock-item name, or NULL for missing image icon, or localised custom-icon filename with or without path @param size icon size enumerator @return pointer to E2_Image struct for the image, or NULL if problem occurred */ E2_Image *e2_cache_image_get (const gchar *name, GtkIconSize size) { if (name == NULL) name = GTK_STOCK_MISSING_IMAGE; //revert to default icon image gint psize = e2_utils_get_icon_size (size); //make a unique cache key gchar *cachename = g_strdup_printf ("%s%d", name, psize); E2_Image *cached = g_hash_table_lookup (app.icons, cachename); if (cached == NULL) { //need to cache this icon cached = MALLOCATE (E2_Image); //too small for slice CHECKALLOCATEDWARN (cached, return NULL;) cached->size = psize; // cached->refcount = 1; if (e2_utils_check_stock_icon (name)) { //stock image // cached->type = E2_IMAGE_BMP; cached->pixbuf = gtk_icon_set_render_icon ( gtk_icon_factory_lookup_default (name), gtk_rc_get_style (app.main_window), gtk_widget_get_default_direction (), GTK_STATE_NORMAL, size, NULL, NULL); } else { //custom icon file gchar *fullname; if (g_path_is_absolute (name)) fullname = (gchar *)name; else { gchar *freeme = e2_utils_get_icons_path (TRUE); fullname = e2_utils_strcat (freeme, name); g_free (freeme); } cached->pixbuf = gdk_pixbuf_new_from_file_at_scale (fullname, psize, psize, FALSE, NULL); if (fullname != name) g_free (fullname); if (cached->pixbuf == NULL) { //can't do the custom image, revert to 'missing' DEMALLOCATE (E2_Image, cached); g_free (cachename); return e2_cache_image_get (NULL, size); //recurse } } g_hash_table_insert (app.icons, cachename, cached); } else { g_free (cachename); // cached->refcount++; // g_object_ref (G_OBJECT (cached->pixbuf)); } return cached; } #endif /** @brief check whether cache entry @a name exists @param name cache identifier string @return TRUE if cache item exists in cache_list or unknown_list */ gboolean e2_cache_check (gchar *name) { GList *found = g_list_find_custom (cache_list, name, (GCompareFunc) _e2_cache_str_compare); if (found != NULL) return TRUE; else { found = g_list_find_custom (unknown_list, name, (GCompareFunc) _e2_cache_str_compare); if (found != NULL) return TRUE; } return FALSE; } /** @brief set integer parameter from cache or default @param name identifier string used in cache list @param value pointer to store for cached value, if any, or @a def @param def default value to use if none found in cache @return */ void e2_cache_int_register (gchar *name, gint *value, gint def) { E2_Cache *cache; GList *found = g_list_find_custom (cache_list, name, (GCompareFunc) _e2_cache_str_compare); if (found != NULL) { printd (DEBUG, "re-register int cache '%s'", name); return; } //first-time processing cache = ALLOCATE (E2_Cache); //FIXME no deallocation of any caches CHECKALLOCATEDFATAL (cache) cache->type = E2_CACHE_TYPE_INT; found = g_list_find_custom (unknown_list, name, (GCompareFunc) _e2_cache_str_compare); if (found == NULL) { //not in the queue, either printd (DEBUG, "int cache '%s' not found, creating default with %d", name, def); cache->name = g_strdup (name); *value = def; } else { //process the queued item printd (DEBUG, "cache '%s' found", name); E2_Cache *unknown = found->data; cache->name = unknown->name; gchar *end = NULL; gchar *str = (gchar *) unknown->data; *value = g_ascii_strtoull (str, &end, 10); if (end == str) { printd (WARN, "int cache '%s' data conversion failed from '%s", name, str); *value = def; } else g_free (str); unknown_list = g_list_remove_link (unknown_list, found); g_list_free (found); DEALLOCATE (E2_Cache, unknown); } cache->data = GINT_TO_POINTER (value); cache_list = g_list_append (cache_list, cache); return; } /** @brief set long integer parameter from cache or default This function needed as we cannot determine at compile-time how big a time_t is If there are ever any uses for a long int cache item, this could be converted, and the uses for this changed to run-time checks @param name identifier string used in cache list @param value pointer to store for cached value, if any, or @a def @param def default value to use if none found in cache @return */ void e2_cache_time_register (gchar *name, time_t *value, time_t def) { E2_Cache *cache; GList *found = g_list_find_custom (cache_list, name, (GCompareFunc) _e2_cache_str_compare); if (found != NULL) { printd (DEBUG, "re-register long int cache '%s'", name); return; } //first-time processing cache = ALLOCATE (E2_Cache); CHECKALLOCATEDFATAL (cache) cache->type = E2_CACHE_TYPE_TIME; found = g_list_find_custom (unknown_list, name, (GCompareFunc) _e2_cache_str_compare); if (found == NULL) { //not in the queue, either printd (DEBUG, "time cache '%s' not found, creating default with %d", name, def); cache->name = g_strdup (name); *value = def; } else { //process the queued item printd (DEBUG, "cache '%s' found", name); E2_Cache *unknown = found->data; cache->name = unknown->name; gchar *end = NULL; gchar *str = (gchar *) unknown->data; *value = g_ascii_strtoull (str, &end, 10); if (end == str) { printd (WARN, "time cache '%s' data conversion failed from '%s", name, str); *value = def; } else g_free (str); unknown_list = g_list_remove_link (unknown_list, found); g_list_free (found); g_free (unknown); } cache->data = (gpointer) value; cache_list = g_list_append (cache_list, cache); return; } /** @brief set double parameter from cache or default @param name identifier string used in cache list @param value pointer to store for cached value, if any, or @a def @param def default value to use if none found in cache @return */ void e2_cache_double_register (gchar *name, gdouble *value, gdouble def) { E2_Cache *cache; GList *found = g_list_find_custom (cache_list, name, (GCompareFunc) _e2_cache_str_compare); if (found != NULL) { printd (DEBUG, "re-register double cache '%s'", name); return; } cache = ALLOCATE (E2_Cache); CHECKALLOCATEDFATAL (cache) cache->type = E2_CACHE_TYPE_DOUBLE; found = g_list_find_custom (unknown_list, name, (GCompareFunc) _e2_cache_str_compare); if (found == NULL) { //not in queue either printd (DEBUG, "double cache '%s' not found, creating default with %f", name, def); cache->name = g_strdup (name); *value = def; } else { //process queued item printd (DEBUG, "cache '%s' found", name); E2_Cache *unknown = found->data; cache->name = unknown->name; gchar *end = NULL; gchar *str = (gchar *) unknown->data; *value = g_ascii_strtod (str, &end); if (end == str) { printd (WARN, "double cache '%s' data conversion failed from '%s", name, str); *value = def; } else g_free (str); unknown_list = g_list_remove_link (unknown_list, found); g_list_free (found); g_free (unknown); } cache->data = (gpointer *) value; cache_list = g_list_append (cache_list, cache); return; } /** @brief set string parameter from cache or default @param name identifier string used in cache list @param str pointer to store for cached value, if any, or @a def @param def default value to use if none found in cache @return */ void e2_cache_str_register (gchar *name, gchar **str, gchar *def) { E2_Cache *cache; GList *found = g_list_find_custom (cache_list, name, (GCompareFunc) _e2_cache_str_compare); if (found != NULL) { printd (DEBUG, "re-register string cache '%s'", name); return; } cache = ALLOCATE (E2_Cache); CHECKALLOCATEDFATAL (cache) cache->type = E2_CACHE_TYPE_STR; found = g_list_find_custom (unknown_list, name, (GCompareFunc)_e2_cache_str_compare); if (found == NULL) { printd (DEBUG, "string cache '%s' not found, creating dedault with %s", name, def); cache->name = g_strdup (name); *str = strdup (def); } else { printd (DEBUG, "cache '%s' found", name); E2_Cache *unknown = found->data; cache->name = unknown->name; *str = (gchar *) unknown->data; unknown_list = g_list_remove_link (unknown_list, found); g_list_free (found); g_free (unknown); } cache->data = (gpointer *) str; cache_list = g_list_append (cache_list, cache); return; } /** @brief setup liststore or treestore with data from cache, or empty The element will be created if it does not exist Unlike other cache-register functions, this does not expect an argument which is the default to use when no matching cache is found. @a preparefunc must be void preparefunc (gpointer *, GList *), set @a store, and convert a list of rowstrings (possibly NULL), to data in that store. @a syncfunc must be void syncfunc (gpointer*) and convert store data from store *pointer to a GList of rowstrings, then set *pointer to address of that list As for lists, the value of the store pointer may be altered after cacheing, as the address of that pointer is remembered and used @param name identifier string used in cache file @param store address to save pointer of liststore (or treestore?) holding any ached data @param fillfunc function which creates store and adds any cached data @param syncfunc function which converts store data to a list ready for cacheing, or NULL @param syncdata data to be provided as second argument to @a syncfunc @return */ void e2_cache_store_register (gchar *name, gpointer *store, void (*fillfunc) (gpointer*, GList*), GList*(*syncfunc) (gpointer, gpointer), gpointer syncdata) { //any cached data will be a list. Try to get that GList *rowdata; E2_Cache *cache = e2_cache_list_register (name, &rowdata); // void (*func) (gpointer*, GList*) = fillfunc; // (*func) (store, rowdata); fillfunc (store, rowdata); e2_list_free_with_data (&rowdata); //data stored, this now redundant //adjust parameters to suit subsequent cacheing cache->type = E2_CACHE_TYPE_STORE; cache->data = store; cache->sync_func = syncfunc; cache->sync_data = syncdata; } /** @brief get data-list element corresponding to @a name from the cache list The element will be created if it does not exist Unlike other cache-register functions, this does not expect an argument which is the default to use when no matching cache is found. Instead, it sets the list pointer to NULL (the usual default is an empty list). This approach minimises irrelevant creation of default lists. Non-empty defaults need to be set after this function is called. Cache data is the address of the store pointer, NOT the value of that pointer, So @a list needs to be a known/constant address while the cache data is in use, and any transient cache list needs to be un-registered before that list's address is abandoned @param name identifier string used in cache file @param list pointer to list to store the cached values @return pointer to the cache data struct, or NULL if error occurred */ E2_Cache *e2_cache_list_register (gchar *name, GList **list) { E2_Cache *cache; GList *found = g_list_find_custom (cache_list, name, (GCompareFunc) _e2_cache_str_compare); if (found != NULL) { printd (DEBUG, "re-register list cache '%s'", name); cache = found->data; return cache; } cache = ALLOCATE (E2_Cache); CHECKALLOCATEDFATAL (cache) cache->type = E2_CACHE_TYPE_LIST; cache->sync_func = NULL; cache->sync_data = NULL; found = g_list_find_custom (unknown_list, name, (GCompareFunc) _e2_cache_str_compare); if (found == NULL) { printd (DEBUG, "list cache '%s' not found, creating NULL entry", name); cache->name = g_strdup (name); *list = NULL; } else { printd (DEBUG, "cache '%s' found", name); E2_Cache *unknown = found->data; cache->name = unknown->name; *list = (GList *) unknown->data; //the backup records list start, not its store unknown_list = g_list_remove_link (unknown_list, found); g_list_free (found); g_free (unknown); } cache->data = (gpointer *) list; cache_list = g_list_append (cache_list, cache); return cache; } /** @brief register a cached array of integers @param name identifier string used in cache list @param size the number of elements in the array @param values pointer to store for array of cached values, if any, or @a defs @param defs array of default values to use if any are mission from cache @return */ void e2_cache_array_register (gchar *name, guint size, gint *values, gint *defs) { E2_Cache *cache; GList *found = g_list_find_custom (cache_list, name, (GCompareFunc) _e2_cache_str_compare); if (found != NULL) { printd (DEBUG, "re-register array cache '%s'", name); return; } //first-time processing cache = ALLOCATE (E2_Cache); CHECKALLOCATEDFATAL (cache) cache->type = E2_CACHE_TYPE_ARRAY; cache->sync_func = NULL; // cache->sync_data = NULL; set below, to size of array found = g_list_find_custom (unknown_list, name, (GCompareFunc) _e2_cache_str_compare); gint *stores = values; guint i; if (found == NULL) { //not in the queue, either printd (DEBUG, "array cache '%s' not found, creating default", name); cache->name = g_strdup (name); for (i = 0 ; i < size ; i++) *stores++ = *defs++; } else { //process the queued item printd (DEBUG, "cache '%s' found", name); E2_Cache *unknown = found->data; cache->name = unknown->name; gchar *end = NULL; gchar *str = (gchar *) unknown->data; gchar **split = g_strsplit (str, ",", -1); if (g_strv_length (split) == size) { for (i = 0 ; split[i] != NULL ; i++) { *stores = g_ascii_strtoull (split[i], &end, 10); if (end == split[i]) { printd (WARN, "array cache '%s' data conversion failed from '%s", name, split[i]); *stores = defs[i]; } stores++; } } else { printd (WARN, "array cache '%s' wrong no. of value(s)", name); for (i = 0 ; i < size ; i++) *stores++ = *defs++; } /* if (i < size) { printd (WARN, "array cache '%s' missing value(s)", name); //set all values to default, in case earlier ones are missing stores = values; for (i = 0 ; i < size ; i++) *stores++ = defs[i]; //append missing values // for (; i < size ; i++) // *stores++ = defs[i]; } */ g_strfreev (split); g_free (str); unknown_list = g_list_remove_link (unknown_list, found); g_list_free (found); g_free (unknown); } cache->data = (gpointer) values; cache->sync_data = GINT_TO_POINTER (size); cache_list = g_list_append (cache_list, cache); return; } #ifdef E2_VFS /** @brief helper func to recursively walk a list/tree store and convert all its contents to strings CHECKME is there another func that does this already e.g. for config backup */ static void _e2_cache_store_walk (GtkTreeModel *model, GtkTreeIter *iter, gint level, GList **stringlist) { gchar *rowstring; GtkTreeIter child; do { #ifdef E2_VFSTMP //e2_tree_row_to_string() is currently parked in the vfs plugin // rowstring = e2_tree_row_to_string (model, &iter, -1, level); #endif rowstring = g_strdup ("FIXME"); *stringlist = g_list_append (*stringlist, rowstring); if (gtk_tree_model_iter_children (model, &child, iter)) _e2_cache_store_walk (model, &child, level+1, stringlist); } while (gtk_tree_model_iter_next (model, iter)); } #endif /** @brief remove an item from the cache list, and park in the unknown list All data is copied. Any cleanup should be done by the caller, using a saved pointer to the original cache->data (because that is changed here) @param name identifier string used in cache list @return */ void e2_cache_unregister (gchar *name) { GList *found = g_list_find_custom (cache_list, name, (GCompareFunc) _e2_cache_str_compare); if (found == NULL) { printd (WARN, "trying to unregister cache '%s' which doesn't exist", name); return; } cache_list = g_list_remove_link (cache_list, found); unknown_list = g_list_concat (unknown_list, found); E2_Cache *cache = found->data; //revert cache data to cache-file string form switch (cache->type) { case E2_CACHE_TYPE_BOOL: cache->data = (gpointer) g_strdup ((*cache->data) ? "true" : "false"); break; case E2_CACHE_TYPE_INT: cache->data = (gpointer) g_strdup_printf ("%d", *((gint *)cache->data)); break; case E2_CACHE_TYPE_TIME: //time_t may be int or long int cache->data = (gpointer) g_strdup_printf ("%ld", *((glong *)cache->data)); break; case E2_CACHE_TYPE_DOUBLE: cache->data = (gpointer) g_strdup_printf ("%f", *((gdouble *)cache->data)); break; case E2_CACHE_TYPE_STR: //copy this, in case original cleared cache->data = (gpointer) g_strdup ((gchar *) cache->data); break; #ifdef E2_VFS //sofar, only cached store is for vfs data case E2_CACHE_TYPE_STORE: if (*(gpointer *)cache->data == NULL) //make sure store still exists break; GList *stringlist; if (cache->sync_func != NULL) { //custom conversion to list for backup GList* (*fun) (gpointer, gpointer) = cache->sync_func; stringlist = fun (*(gpointer *)cache->data, cache->sync_data); } else { stringlist = NULL; GtkTreeIter iter; GtkTreeModel *model = GTK_TREE_MODEL ((GtkListStore *)*cache->data); if (gtk_tree_model_get_iter_first (model, &iter)) _e2_cache_store_walk (model, &iter, 0, &stringlist); } cache->data = (gpointer) stringlist; //backup records list start, not a pointer address break; #endif case E2_CACHE_TYPE_LIST: if (cache->sync_func != NULL) { void (*fun) (gpointer) = cache->sync_func; fun (cache->sync_data); } #ifndef E2_VFS GList * #endif stringlist = NULL; if ((cache->data != NULL) && (*cache->data != NULL)) { GList *node; for (node = (GList *) *cache->data; node != NULL; node = node->next) stringlist = g_list_append (stringlist, g_strdup ((gchar *) node->data)); } cache->data = (gpointer) stringlist; //backup records list start, not a pointer address break; case E2_CACHE_TYPE_ARRAY: if (cache->sync_func != NULL) { void (*fun) (gpointer) = cache->sync_func; fun (cache->sync_data); } GString *merged = g_string_new (""); gint *values = (gint *) cache->data; gint i, j = GPOINTER_TO_INT (cache->sync_data); if (j > 0) g_string_append_printf (merged,"%d", *values++); for (i = 1 ; i < j ; i++) g_string_append_printf (merged,",%d", *values++); cache->data = (gpointer) merged->str; g_string_free (merged, FALSE); break; // case E2_CACHE_TYPE_LONG: // case E2_CACHE_TYPE_FLOAT: // case E2_CACHE_TYPE_TREE: default: break; } } /** @brief write cache file to disk File contents are saved in utf-8 encoding, not localised. Expects BGL to be on/closed @return */ void e2_cache_file_write (void) { #ifdef E2_VFS VPATH ddata; VPATH tdata; #endif gchar *utfpath = g_build_filename (e2_cl_options.config_dir, default_cache_file, NULL); gchar *local = F_FILENAME_TO_LOCALE (utfpath); gchar *tempname = e2_utils_get_tempname (local); E2_FILE *f = e2_fs_open_writestream (tempname E2_ERR_NONE()); if (f != NULL) { printd (DEBUG, "write cache file: %s", utfpath); GList *member; E2_Cache *cache; if (e2_fs_file_write (f, //first line is language-independent, for version checking "# "PROGNAME" (v "VERSION")\n\n") == 0) goto error_handler; if (e2_fs_file_write (f, _("%sThis file stores runtime configuration data for %s.\n" "%sThe file will be overwritten each time %s is shut down.\n\n"), "# ", PROGNAME, "# ", PROGNAME) == 0) goto error_handler; if (cache_list != NULL) { extern gint real_width; extern gint real_height; //these data need special 'syncing' FIXME use array sync process for this e2_fileview_update_col_cachedata (); //do not cache current window size in fullscreen mode if (app.mainwindow_state & GDK_WINDOW_STATE_FULLSCREEN) { member = g_list_find_custom (cache_list, "window-width", (GCompareFunc) _e2_cache_str_compare); if (member != NULL) { cache = (E2_Cache *)member->data; *((gint *)cache->data) = real_width; } member = g_list_find_custom (cache_list, "window-height", (GCompareFunc) _e2_cache_str_compare); if (member != NULL) { cache = (E2_Cache *)member->data; *((gint *)cache->data) = real_height; } } for (member = cache_list; member != NULL; member = member->next) { cache = member->data; switch (cache->type) { case E2_CACHE_TYPE_BOOL: if (e2_fs_file_write (f, "%s=%s\n", cache->name, *cache->data ? "true" : "false") == 0) goto error_handler; break; case E2_CACHE_TYPE_INT: if (e2_fs_file_write (f, "%s=%d\n", cache->name, *((gint *)cache->data)) == 0) goto error_handler; break; case E2_CACHE_TYPE_TIME: //time_t may be int or long int if (e2_fs_file_write (f, "%s=%ld\n", cache->name, *((glong *)cache->data)) == 0) goto error_handler; break; case E2_CACHE_TYPE_DOUBLE: { gchar doubl[G_ASCII_DTOSTR_BUF_SIZE]; g_ascii_dtostr (doubl, G_ASCII_DTOSTR_BUF_SIZE, *((gdouble *)cache->data)); if (e2_fs_file_write (f, "%s=%s\n", cache->name, doubl) == 0) goto error_handler; } break; case E2_CACHE_TYPE_STR: if (e2_fs_file_write (f, "%s=%s\n", cache->name, (gchar *) *cache->data) == 0) goto error_handler; break; #ifdef E2_VFS //sofar, only cached store is for vfs data case E2_CACHE_TYPE_STORE: if (*(gpointer *)cache->data != NULL) //make sure store still exists { GList *stringlist; if (cache->sync_func != NULL) { //custom conversion to list for backup GList* (*fun) (gpointer, gpointer) = cache->sync_func; stringlist = fun (*(gpointer *)cache->data, cache->sync_data); } else { stringlist = NULL; GtkTreeIter iter; GtkTreeModel *model = GTK_TREE_MODEL ((GtkListStore *)*cache->data); if (gtk_tree_model_get_iter_first (model, &iter)) _e2_cache_store_walk (model, &iter, 0, &stringlist); } if (stringlist != NULL) { if (e2_fs_file_write (f, "%s=<\n", cache->name) == 0) goto error_handler; GList *node; for (node = stringlist; node != NULL; node = node->next) { //escape anything that would seem like a tree-option start or end if (//*((gchar *)tmp->data) == '<'//ascii check is ok here //|| *((gchar *)node->data) == '>') e2_fs_file_write (f, "\\"); if (e2_fs_file_write (f, "%s\n", (gchar *) node->data) == 0) goto error_handler; } if (e2_fs_file_write (f, ">\n") == 0) goto error_handler; //CHECKME = leaks when this is done before session-end ?? } } break; #endif case E2_CACHE_TYPE_LIST: if (cache->sync_func != NULL) { void (*fun) (gpointer) = cache->sync_func; fun (cache->sync_data); } if ((cache->data != NULL) && (*cache->data != NULL)) { // if (e2_fs_file_write (f, "<%s\n", cache->name) == 0) if (e2_fs_file_write (f, "%s=<\n", cache->name) == 0) goto error_handler; GList *node; for (node = (GList *) *cache->data; node != NULL; node = node->next) { //escape anything that would seem like a tree-option start or end if (//*((gchar *)tmp->data) == '<'//ascii check is ok here //|| *((gchar *)node->data) == '>') e2_fs_file_write (f, "\\"); if (e2_fs_file_write (f, "%s\n", (gchar *) node->data) == 0) goto error_handler; } if (e2_fs_file_write (f, ">\n") == 0) goto error_handler; //CHECKME = leaks when this is done before session-end ?? } break; case E2_CACHE_TYPE_ARRAY: if (cache->sync_func != NULL) { void (*fun) (gpointer) = cache->sync_func; fun (cache->sync_data); //sync_data = size of array } if (e2_fs_file_write (f, "%s=", cache->name) == 0) goto error_handler; gint *values = (gint *) cache->data; //j = array size - 1 - the last is handled separately gint i, j = GPOINTER_TO_INT (cache->sync_data) - 1; for (i = 0 ; i < j ; i++) if (e2_fs_file_write (f, "%d,", *values++) == 0) goto error_handler; if (e2_fs_file_write (f, "%d\n", *values) == 0) goto error_handler; break; // case E2_CACHE_TYPE_LONG: // case E2_CACHE_TYPE_FLOAT: // case E2_CACHE_TYPE_TREE: default: printd (WARN, "don't know how to write '%s' to cache file", cache->name); break; } } } else printd (DEBUG, "no caches registered"); for (member = unknown_list; member != NULL; member = member->next) { cache = member->data; switch (cache->type) { case E2_CACHE_TYPE_BOOL: case E2_CACHE_TYPE_INT: case E2_CACHE_TYPE_TIME: case E2_CACHE_TYPE_DOUBLE: case E2_CACHE_TYPE_STR: case E2_CACHE_TYPE_ARRAY: if (e2_fs_file_write (f, "%s=%s\n", cache->name, (gchar *) cache->data) == 0) goto error_handler; break; case E2_CACHE_TYPE_LIST: case E2_CACHE_TYPE_STORE: if (e2_fs_file_write (f, "%s=<\n", cache->name) == 0) goto error_handler; GList *node; for (node = (GList *) cache->data; node != NULL; node = node->next) { //escape anything that would seem like a tree-option start or end if (//*((gchar *)tmp->data) == '<'//ascii check is ok here //|| *((gchar *)node->data) == '>') e2_fs_file_write (f, "\\"); if (e2_fs_file_write (f, "%s\n", (gchar *) node->data) == 0) goto error_handler; } if (e2_fs_file_write (f, ">\n") == 0) goto error_handler; break; // case E2_CACHE_TYPE_LONG: // case E2_CACHE_TYPE_FLOAT: // case E2_CACHE_TYPE_TREE: default: printd (WARN, "don't know how to write '%s' to cache file", cache->name); break; } } e2_fs_close_stream (f); #ifdef E2_VFS tdata.localpath = tempname; tdata.spacedata = NULL; ddata.localpath = local; ddata.spacedata = NULL; #endif gdk_threads_leave (); //downstream error messages invoke local mutex management #ifdef E2_VFS e2_task_backend_rename (&tdata, &ddata); e2_fs_chmod (&ddata, 0600 E2_ERR_NONE()); #else e2_task_backend_rename (tempname, local); e2_fs_chmod (local, 0600 E2_ERR_NONE()); #endif gdk_threads_enter (); goto cleanup; } error_handler: if (f != NULL) { e2_fs_close_stream (f); gdk_threads_leave (); #ifdef E2_VFS e2_task_backend_delete (&tdata); #else e2_task_backend_delete (tempname); #endif gdk_threads_enter (); } gchar *msg = g_strdup_printf (_("Cannot write cache file %s - %s"), utfpath, g_strerror (errno)); //ok for native-only config file e2_output_print_error (msg, TRUE); cleanup: g_free (utfpath); F_FREE (local); g_free (tempname); } /** @brief interpret a line of the cache file The line content is assumed to be encoded in utf-8 For each new cache-item, an E2_Cache struct is created, and added to list unknown_list, ready for later use @param f pointer to the start of the string @return */ static void _e2_cache_read_from_string (gchar *f[]) { gboolean list_mode = FALSE; GList *list = NULL; E2_Cache *cache = NULL; gint i = 0; gchar *line; while ((line = f[i++]) != NULL) { g_strchomp (line); //ignore empty lines and comments if (*line == '\0') continue; if (line[0] == '#') continue; //list values start with a line like name=< gchar *s = strrchr (line, '='); if (s != NULL && *(s+1) == '<' && *(s+2) == '\0') { list_mode = TRUE; list = NULL; cache = ALLOCATE (E2_Cache); CHECKALLOCATEDFATAL (cache) *s = '\0'; cache->name = g_strdup (line); cache->type = E2_CACHE_TYPE_LIST; continue; } //deprecated format for list cache data - remove sometime ... if (line[0] == '<') { list_mode = TRUE; list = NULL; cache = ALLOCATE (E2_Cache); CHECKALLOCATEDFATAL (cache) cache->name = g_strdup (line + 1); cache->type = E2_CACHE_TYPE_LIST; continue; } if (line[0] == '>') { list_mode = FALSE; if (list == NULL) continue; cache->data = (gpointer *) list; unknown_list = g_list_append (unknown_list, cache); continue; } if (list_mode) { //cleanup any escaped action-names if (g_str_has_prefix (line, "\\>")) line++; list = g_list_append (list, g_strdup (line)); } else { cache = ALLOCATE (E2_Cache); CHECKALLOCATEDFATAL (cache) gchar **split = g_strsplit (line, "=", 2); cache->name = g_strdup (split[0]); cache->data = (gpointer *) g_strdup (split[1]); cache->type = E2_CACHE_TYPE_STR; //initially, all items assumed to be strings g_strfreev (split); unknown_list = g_list_append (unknown_list, cache); } } } /** @brief read cache file named @a fn from disk, and process its contents @param fn name of cache file @return */ static void _e2_cache_file_read (gchar *fn) { //find absolute path to config file gchar *filepath = g_build_filename (e2_cl_options.config_dir, fn, NULL); gchar *localpath = F_FILENAME_TO_LOCALE (filepath); #ifdef E2_VFS VPATH ddata = { localpath, NULL }; //only local cache file #endif gpointer contents; //NOTE any error during this read must not print error message, as //config and output data are not yet known if (e2_fs_get_file_contents ( #ifdef E2_VFS &ddata, #else localpath, #endif &contents, NULL, TRUE E2_ERR_NONE())) { printd (DEBUG, "cache read from file '%s'", filepath); gchar **split = g_strsplit ((gchar *)contents, "\n", -1); _e2_cache_read_from_string (split); g_strfreev (split); g_free (contents); //need free() if file buffer allocated by malloc() } else { printd (WARN, "could not open cache file '%s'", filepath); } g_free (filepath); F_FREE (localpath); } /** @brief initialize the cache This function will initilize the cache system of e2. It will automatically be called at startup. @param config_dir_ready TRUE when config dir found or created @return */ void e2_cache_init (gboolean config_dir_ready) { cache_list = NULL; unknown_list = NULL; default_cache_file = "cache"; //DO NOT TRANSLATE THIS if (config_dir_ready) _e2_cache_file_read (default_cache_file); } /** @brief clean cache memory-allocations @return */ void e2_cache_clean (void) { GList *member; if (cache_list != NULL) { for (member = cache_list; member != NULL; member = member->next) { g_free (((E2_Cache*) member->data)->name); DEALLOCATE (E2_Cache, (E2_Cache*) member->data); } g_list_free (cache_list); cache_list = NULL; } if (unknown_list != NULL) { for (member = unknown_list; member != NULL; member = member->next) { g_free (((E2_Cache*) member->data)->name); DEALLOCATE (E2_Cache, (E2_Cache*) member->data); } g_list_free (unknown_list); unknown_list = NULL; } } /** @brief clean one item from cache runtime data This is used only to cleanup things during a version-upgrade @param name the key of the item to clear @return */ void e2_cache_clean1 (const gchar *name) { GList *found = g_list_find_custom (cache_list, name, (GCompareFunc) _e2_cache_str_compare); if (found != NULL) { g_free (((E2_Cache*) found->data)->name); if (((E2_Cache*) found->data)->type == E2_CACHE_TYPE_STR) g_free (((E2_Cache*) found->data)->data); //other things will leak DEALLOCATE (E2_Cache, (E2_Cache*) found->data); cache_list = g_list_delete_link (cache_list, found); } found = g_list_find_custom (unknown_list, name, (GCompareFunc) _e2_cache_str_compare); if (found != NULL) { g_free (((E2_Cache*) found->data)->name); g_free (((E2_Cache*) found->data)->data); DEALLOCATE (E2_Cache, (E2_Cache*) found->data); unknown_list = g_list_delete_link (unknown_list, found); } } emelfm2-0.4.1/src/config/e2_option_tree.c0000600000175000017500000017024011014260365017126 0ustar cairocairo/* $Id: e2_option_tree.c 886 2008-05-19 11:13:57Z tpgww $ Copyright (C) 2003-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/config/e2_option_tree.c @brief functions to handle tree options Functions to handle tree options */ #include "emelfm2.h" #include #include "e2_option_tree.h" #include "e2_dialog.h" //data struct for a deferred tree-option overwrite dialog typedef struct _E2_OptionTreeCheck { E2_OptionSet *set; //pointer to set data struct GtkTreeRowReference *ref; gint colnum; gboolean *lock; gchar *newvalue; gchar *prompt; } E2_OptionTreeCheck; #define E2_OVERRIDE_MASK (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK) //data struct for key-binding change typedef struct _E2_KeyChangeData { E2_OptionSet *set; //pointer to set data struct gchar *path_string; //string form of gtk tree path to the row to be amended GtkCellRenderer *renderer; // gchar *oldtext; } E2_KeyChangeData; static void _e2_option_tree_set (E2_OptionSet *set, GtkTreeIter *iter, gint col, void *data); extern GtkWidget *config_dialog; static void _e2_option_tree_add_default (E2_OptionSet *set, GtkTreeIter *iter, GtkTreeIter *parent, gboolean sibling); static void _e2_option_tree_move (E2_OptionSet *set, GtkTreeIter *iter, GtkTreePath *path_dest, gboolean sibling, gboolean before); static gboolean _e2_option_tree_add_below_cb (GtkWidget *widget, E2_OptionSet *set); static gboolean _e2_option_tree_add_child_cb (GtkWidget *widget, E2_OptionSet *set); static gboolean _e2_option_tree_move_up_cb (GtkWidget *widget, E2_OptionSet *set); static gboolean _e2_option_tree_move_down_cb (GtkWidget *widget, E2_OptionSet *set); static gboolean _e2_option_tree_button_press_cb (GtkWidget *treeview, GdkEventButton *event, E2_OptionSet *set); static void *_e2_option_tree_get_void_simple (E2_OptionTreeColumn *col, gchar *option); extern GtkTreeStore *actions_store; /************************/ /***** context menu *****/ /************************/ #define INCLUDED_IN_PARENT #include "e2_option_tree_context_menu.c" #undef INCLUDED_IN_PARENT /** @brief create flag for the config dialog, to signal that the set's tree data has been modified @param set pointer to data struct for the option that the dialog configures @return */ void e2_option_tree_flag_change (E2_OptionSet *set) { set->ex.tree.flags |= E2_OPTION_TREE_SET_EDITED; } /**************************/ /***** data functions *****/ /**************************/ /*These functions set some properties of a treeview cell instead of using the straight mapping between the cell and the treemodel. They are called whenever the cell is rendered, and at the end of (not during) cell-editing */ static void _e2_option_tree_bool_data_func (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *model, GtkTreeIter *iter, E2_OptionTreeColumn *col) { gint column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (cell), "column")); gboolean b; gtk_tree_model_get (model, iter, column, &b, -1); gboolean visible = TRUE; if (col->visible_check_func != NULL) { gboolean (*fun) (GtkTreeModel *, GtkTreeIter *, GtkCellRenderer *, gpointer) = col->visible_check_func; visible = fun (model, iter, cell, col->visible_check_data); } g_object_set (G_OBJECT (cell), "visible", visible, "active", b, NULL); } static void _e2_option_tree_str_data_func (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *model, GtkTreeIter *iter, E2_OptionTreeColumn *col) { gint column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (cell), "column")); gchar *str; gtk_tree_model_get (model, iter, column, &str, -1); gboolean visible = TRUE; if (col->visible_check_func != NULL) { gboolean (*fun) (GtkTreeModel *, GtkTreeIter *, GtkCellRenderer *, gpointer) = col->visible_check_func; visible = fun (model, iter, cell, col->visible_check_data); } g_object_set (G_OBJECT (cell), "visible", visible, "text", str, NULL); g_free (str); } /*********************/ /***** callbacks *****/ /*********************/ /* the relevant one of funcs is called whenever an edited cell is re-rendered, and when the edit is finished, and when Esc is pressed (in the latter case, as part of the widget_destroy process for the config dialog They set treestore data in accord with the renderer */ /** @brief save edited text value in the underlying treestore @param renderer the renderer for the cell @param path_string string form of gtk tree path to the row to be amended @param new_text replacement text string for the cell @param set pointer to set data struct @return */ static void _e2_option_tree_string_edited_cb (GtkCellRendererText *cell, gchar *path_string, gchar *new_text, E2_OptionSet *set) { if (new_text == NULL) //this probably can't happen return; printd (DEBUG, "tree-option edited cb, new text is %s", new_text); if (g_str_equal (set->name, "keybindings") && g_str_equal (new_text, _("Press key"))) return; GtkTreeIter iter; if (gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (set->ex.tree.model), &iter, path_string)) { gint column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (cell), "column")); _e2_option_tree_set (set, &iter, column, (gchar *)new_text); } //clean up //revert focus to edited row //CHECKME when editing done, focus the cancel button ?? gtk_widget_grab_focus (set->widget); } /** @brief save toggled boolean value in the underlying treestore @param renderer the renderer for the cell @param path_string string form of gtk tree path to the row to be amended @param set pointer to set data struct @return */ static void _e2_option_tree_toggle_cb (GtkCellRendererToggle *cell, const gchar *path_str, E2_OptionSet *set) { //find out where GtkTreePath *path = gtk_tree_path_new_from_string (path_str); GtkTreeIter iter; if (gtk_tree_model_get_iter (set->ex.tree.model, &iter, path)) { gint column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (cell), "column")); //invert current value gboolean toggle_item; gtk_tree_model_get (set->ex.tree.model, &iter, column, &toggle_item, -1); toggle_item ^= 1; //change model gtk_tree_store_set (GTK_TREE_STORE (set->ex.tree.model), &iter, column, toggle_item, -1); e2_option_tree_flag_change (set); } //clean up gtk_tree_path_free (path); } static void _e2_option_tree_cell_pixbuf_data_func (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *model, GtkTreeIter *iter, gpointer data) { gint column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column), "column")); gchar *icon; gtk_tree_model_get (model, iter, column, &icon, -1); #ifdef E2_IMAGECACHE GdkPixbuf *pb; #endif if (icon != NULL && *icon != '\0') { #ifdef E2_IMAGECACHE // FIXME = adjust img->refcount ?? GtkIconSize sz = GPOINTER_TO_INT (data); E2_Image *img = e2_cache_image_get (icon, sz); pb = img->pixbuf; #else if (e2_utils_check_stock_icon (icon)) { g_object_set (G_OBJECT (cell), "stock-id", icon, NULL); g_object_set (G_OBJECT (cell), "pixbuf", NULL, NULL); } else //not a stock item { GdkPixbuf *pixbuf; GdkPixbuf *pixbuf2; if ((pixbuf = gdk_pixbuf_new_from_file (icon, NULL)) != NULL) { pixbuf2 = pixbuf; pixbuf = gdk_pixbuf_scale_simple (pixbuf2, 16, 16, GDK_INTERP_BILINEAR); g_object_unref (pixbuf2); } g_object_set (G_OBJECT (cell), "stock-id", NULL, NULL); g_object_set (G_OBJECT (cell), "pixbuf", pixbuf, NULL); if (pixbuf != NULL) g_object_unref (pixbuf); } #endif } else #ifdef E2_IMAGECACHE pb = NULL; g_object_set (G_OBJECT (cell), "pixbuf", pb, NULL); #else { g_object_set (G_OBJECT (cell), "stock-id", NULL, NULL); g_object_set (G_OBJECT (cell), "pixbuf", NULL, NULL); } #endif g_free (icon); } /** @brief set key name determined from key press event @a event This is a callback after a key cell edit begins @param entry the entry widget associated with the cell renderer @param event pointer to event data struct for the pressed key @param data pointer to data needed to update the cell the renderer @return TRUE unless the key value is invalid */ static gboolean _e2_option_tree_keychange_cb (GtkEntry *entry, GdkEventKey *event, E2_KeyChangeData *data) { // printd (DEBUG, "key change cb"); //one-time only g_signal_handlers_disconnect_by_func ( entry, _e2_option_tree_keychange_cb, data->set); guint keyval = event->keyval; GdkModifierType mask = event->state; if (!gtk_accelerator_valid (keyval, mask)) return TRUE; mask &= ~(GDK_MOD2_MASK); //ignore Mod2 (numlock) // always cancels (so unmodified, it can't be set as a binding here) if (keyval == GDK_Escape && !(mask & E2_OVERRIDE_MASK)) { gtk_widget_grab_focus (data->set->widget); g_free (data->path_string); DEALLOCATE (KeyChangeData, data); return TRUE; } gchar *keyname; if (keyval < 0xF000 || keyval > 0xFFFF) { if ((mask & E2_OVERRIDE_MASK) == GDK_SHIFT_MASK) { //control and alt not pressed if (mask & GDK_LOCK_MASK) //lcase already, we don't want keyname = gdk_keyval_name (keyval); else /* { //toggle case if (gdk_keyval_is_lower (keyval)) keyname = gdk_keyval_name (gdk_keyval_to_upper (keyval)); else keyname = gdk_keyval_name (gdk_keyval_to_lower (keyval)); } */ { if (gdk_keyval_is_lower (keyval)) keyval = gdk_keyval_to_upper (keyval); keyname = gdk_keyval_name (keyval); } keyname = g_strdup (keyname); } else if ((mask & GDK_LOCK_MASK) && !(mask & E2_OVERRIDE_MASK)) //only lock is active keyname = g_strdup (gdk_keyval_name (gdk_keyval_to_upper (keyval))); else keyname = gtk_accelerator_name (keyval, mask); } else keyname = gtk_accelerator_name (keyval, mask); /* THE DIALOG CAUSES CRASH //check for and warn about duplicate GtkTreeView *treeview = GTK_TREE_VIEW (data->set->widget); GtkTreeSelection *sel = gtk_tree_view_get_selection (treeview); GtkTreeIter iter; if (!gtk_tree_selection_get_selected (sel, NULL, &iter)) return TRUE; DialogButtons result = OK; GtkTreeIter parent, srch_iter; GtkTreeModel *mdl = data->set->ex.tree.model; gtk_tree_model_iter_parent (mdl, &parent, &iter); gtk_tree_model_iter_children (mdl, &srch_iter, &parent); do { gchar *storedkey, *action, *arg; gtk_tree_model_get (mdl, &srch_iter, 1, &storedkey, 3, &action, 4, &arg, -1); if (g_str_equal (storedkey, keyname) && (srch_iter.stamp != iter.stamp || srch_iter.user_data != iter.user_data || srch_iter.user_data2 != iter.user_data2 || srch_iter.user_data3 != iter.user_data3 )) { gchar *s, *prompt, *fmt = _("%s is assigned to '%s'. Proceed ?"); if (*action != '\0' && *arg != '\0') { s = g_strdup_printf (fmt, keyname, "%s %s"); prompt = g_strdup_printf (s, action, arg); g_free (s); } else prompt = g_strdup_printf (fmt, keyname, action); result = e2_dialog_warning (prompt); g_free (prompt); g_free (storedkey); g_free (action); g_free (arg); break; } g_free (storedkey); g_free (action); g_free (arg); } while (gtk_tree_model_iter_next (mdl, &srch_iter)); if (result == OK) { */ // g_signal_handlers_block_by_func (data->renderer, // _e2_option_tree_key_edit_start_cb, data->set); //this causes _e2_option_tree_key_edit_start_cb () SMALL YUK g_signal_emit_by_name (data->renderer, "edited", data->path_string, keyname, data->set); // g_signal_handlers_unblock_by_func (data->renderer, // _e2_option_tree_key_edit_start_cb, data->set); // } g_free (data->path_string); // g_free (data->oldtext); DEALLOCATE (KeyChangeData, data); g_free (keyname); return TRUE; } /** @brief setup to amend the key name in a treeview cell @param renderer the renderer for the cell @param editable the interface to @a cell @param path_string string form of gtk tree path to the row to be amended @param set pointer to set data struct @return */ static void _e2_option_tree_key_edit_start_cb (GtkCellRenderer *renderer, GtkCellEditable *editable, gchar *path_string, E2_OptionSet *set) { // printd (DEBUG, "START KEY EDIT CB"); if (GTK_IS_ENTRY (editable)) { E2_KeyChangeData *data = ALLOCATE (E2_KeyChangeData); CHECKALLOCATEDWARN (data, return;) data->set = set; data->path_string = g_strdup (path_string); data->renderer = renderer; GtkEntry *entry = GTK_ENTRY (editable); // data->oldtext = g_strdup (gtk_entry_get_text (entry)); gtk_entry_set_text (entry, _("Press key")); g_signal_connect (G_OBJECT (entry), "key-press-event", G_CALLBACK (_e2_option_tree_keychange_cb), data); } } /** @brief change key handling when cell editing starts @param renderer the renderer for the cell @param editable the interface to @a renderer @param path_string string form of gtk tree path to the row to be amended @param user_data UNUSED pointer to data specified when callback was connected @return */ static void _e2_option_tree_cell_edit_start_cb (GtkCellRenderer *renderer, GtkCellEditable *editable, gchar *path_string, gpointer user_data) { // printd (DEBUG, "start cell edit cb"); g_signal_handlers_block_by_func (G_OBJECT (config_dialog), e2_dialog_key_neg_cb, config_dialog); /* if (GTK_IS_ENTRY (editable)) { GtkEntry *entry = GTK_ENTRY (editable); //do stuff } */ } /** @brief revert key handling when cell editing is finished @param renderer the renderer for the cell @param user_data UNUSED pointer to data specified when callback was connected @return */ static void _e2_option_tree_cell_edit_stop_cb (GtkCellRenderer *renderer, gpointer user_data) { // printd (DEBUG, "stop cell edit cb"); g_signal_handlers_unblock_by_func (G_OBJECT (config_dialog), e2_dialog_key_neg_cb, config_dialog); } static void _e2_option_tree_page_opened_cb (GtkWidget *widget, E2_OptionSet *set) { //backup set data when the page is opened (if it's not done already) e2_option_tree_backup (set); } //this is the row_draggable function for the treeview's GtkTreeDragSourceIface static gboolean _e2_option_tree_draggable_check_cb (GtkTreeDragSource *drag_source, GtkTreePath *path) { if (!GTK_IS_TREE_MODEL (drag_source)) return TRUE; GtkTreeModel *model = GTK_TREE_MODEL (drag_source); E2_OptionSet *set = g_object_get_data (G_OBJECT (model), "e2-option-set"); if (set == NULL) return TRUE; if (set->ex.tree.draggable_check_func != NULL) { gboolean (*fun) (GtkTreeDragSource *, GtkTreePath *) = set->ex.tree.draggable_check_func; return fun (drag_source, path); } else return TRUE; } /** @brief delete the row at @a path, because it was moved somewhere else via drag-and-drop. @param drag_source drag source data struct @param path gtk tree path to the row to be deleted @return FALSE if the deletion fails because path no longer exists, or for some model-specific reason */ static gboolean _e2_option_tree_drag_delete_cb (GtkTreeDragSource *drag_source, GtkTreePath *path) { if (!GTK_IS_TREE_MODEL (drag_source)) return FALSE; GtkTreeModel *model = GTK_TREE_MODEL (drag_source); GtkTreeIter iter; if (gtk_tree_model_get_iter (model, &iter, path)) { gtk_tree_store_remove (GTK_TREE_STORE (model), &iter); E2_OptionSet *set = g_object_get_data (G_OBJECT (model), "e2-option-set"); e2_option_tree_flag_change (set); return TRUE; } else return FALSE; } static gboolean _e2_option_tree_move_up_cb (GtkWidget *widget, E2_OptionSet *set) { GtkTreeModel *model; GtkTreeIter iter; if (gtk_tree_selection_get_selected (gtk_tree_view_get_selection (GTK_TREE_VIEW (set->widget)), &model, &iter)) { GtkTreePath *path; path = gtk_tree_model_get_path (model, &iter); if (gtk_tree_path_prev (path)) { _e2_option_tree_move (set, &iter, path, TRUE, TRUE); //this sets the dirty-flag } if (path) gtk_tree_path_free (path); } return TRUE; } static gboolean _e2_option_tree_move_down_cb (GtkWidget *widgget, E2_OptionSet *set) { GtkTreeModel *model; GtkTreeIter iter; if (gtk_tree_selection_get_selected (gtk_tree_view_get_selection (GTK_TREE_VIEW (set->widget)), &model, &iter)) { GtkTreePath *path; path = gtk_tree_model_get_path (model, &iter); gtk_tree_path_next (path); _e2_option_tree_move (set, &iter, path, TRUE, FALSE); //this sets the dirty-flag e2_option_tree_flag_change (set); if (path) gtk_tree_path_free (path); } return TRUE; } static void _e2_option_tree_icon_column_set_cb (GtkDialog *dialog, gint response, E2_OptionSet *set) { if ((response == GTK_RESPONSE_APPLY) || (response == GTK_RESPONSE_OK) || (response == E2_RESPONSE_REMOVE)) { GtkTreeIter iter; if (gtk_tree_selection_get_selected (gtk_tree_view_get_selection (GTK_TREE_VIEW (set->widget)), NULL, &iter)) { gpointer icon = g_object_get_data (G_OBJECT (dialog), "image"); if (icon != NULL) //user didn't cancel or do something silly { gint column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (dialog), "column")); _e2_option_tree_set (set, &iter, column, (gchar *)icon); //this sets the dirty-flag } } } if ((response != GTK_RESPONSE_APPLY) && (response != E2_RESPONSE_REMOVE)) gtk_widget_destroy (GTK_WIDGET (dialog)); } static gboolean _e2_option_tree_popup_menu_cb (GtkWidget *treeview, GtkWidget *menu) { //set appropriate item sensitivities e2_option_tree_menu_set_sensitive (menu, treeview); gint event_time = gtk_get_current_event_time (); gtk_menu_popup (GTK_MENU (menu), NULL, NULL, (GtkMenuPositionFunc) _e2_option_tree_menu_set_position, treeview, 0, event_time); //button code = 0 as this was a menu-button press return TRUE; } /** @brief create context menu Context menu is created when mouse button-3 is pressed @param treeview widget where the button was pressed @param event gdk event data @param menu the context-menu widget @return TRUE (to prevent further handlers) for a button=3 press, else FALSE */ static gboolean _e2_option_tree_button_press_cb2 (GtkWidget *treeview, GdkEventButton *event, GtkWidget *menu) { if (event->button != 3) return FALSE; //set appropriate item sensitivities e2_option_tree_menu_set_sensitive (menu, treeview); gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, event->button, event->time); return TRUE; } static gboolean _e2_option_tree_button_press_cb (GtkWidget *treeview, GdkEventButton *event, E2_OptionSet *set) { if (event->button != 1) return FALSE; GtkTreePath *path; GtkTreeViewColumn *tree_column; if (!gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (treeview), event->x, event->y, &path, &tree_column, NULL, NULL)) return FALSE; GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview)); if (!gtk_tree_selection_path_is_selected (selection, path)) return FALSE; gint column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column), "column")); E2_OptionTreeColumn *opt = g_list_nth_data (set->ex.tree.columns, column); if (opt->type == E2_OPTION_TREE_TYPE_ICON) { //get the current value gchar *icon; GtkTreeIter iter; gtk_tree_model_get_iter (set->ex.tree.model, &iter, path); gtk_tree_model_get (set->ex.tree.model, &iter, column, &icon, -1); gchar *dialog_name = g_strdup_printf (_("select icon for %s"), set->name); GtkWidget *dialog = e2_sid_create (config_dialog, dialog_name, icon, _e2_option_tree_icon_column_set_cb, set); g_free (dialog_name); g_object_set_data (G_OBJECT (dialog), "column", GINT_TO_POINTER (column)); return TRUE; } return FALSE; } static gboolean _e2_option_tree_help_cb (GtkWidget *button, E2_OptionSet *set) { //get the config dialog page label, from set->group // (after a parent separator '.' if any) gchar *page, *realpage; page = set->group; //FIXME this is translated, but the help doc is not (yet?) if ((realpage = strchr (page, '.')) != NULL) //if always ascii ',', don't need g_utf8_strchr() { NCHR(realpage); page = realpage; } //relevant section label is "[page]" gchar *command = g_strconcat(_A(5),".",_A(103)," ", e2_option_str_get ("config-help-doc")," [",page,"]",NULL); //file.view_at; #ifdef E2_COMMANDQ e2_command_run (command, E2_COMMAND_RANGE_DEFAULT, FALSE); #else e2_command_run (command, E2_COMMAND_RANGE_DEFAULT); #endif g_free (command); return TRUE; } static gboolean e2_option_tree_del_direct_cb (GtkWidget *button, E2_OptionSet *set) { GtkTreeIter iter; if (gtk_tree_selection_get_selected (gtk_tree_view_get_selection (GTK_TREE_VIEW (set->widget)), NULL, &iter)) { GtkTreePath *path = gtk_tree_model_get_path (set->ex.tree.model, &iter); gint response = GTK_RESPONSE_YES; gint children = gtk_tree_model_iter_n_children (set->ex.tree.model, &iter); if (children > 0) { gchar *question = g_strdup_printf ( _("Are you sure that you want to delete this row and %d %s?"), children, children == 1 ? _("child") : _("children")); GtkWidget *dialog = e2_dialog_create (GTK_STOCK_DIALOG_QUESTION, question, _("confirm row delete"), NULL, NULL); e2_dialog_set_negative_response (dialog, GTK_RESPONSE_NO); response = e2_dialog_show (dialog, config_dialog, E2_DIALOG_RUNMODAL | E2_DIALOG_FREE, &E2_BUTTON_NO, &E2_BUTTON_YES, NULL); g_free (question); } if (response == GTK_RESPONSE_YES) e2_option_tree_del_direct (set, &iter); if (iter.user_data == NULL) { if (!gtk_tree_path_prev (path)) if (!gtk_tree_path_up (path)) path = NULL; } else path = gtk_tree_model_get_path (GTK_TREE_MODEL (set->ex.tree.model), &iter); if ((path != NULL) && (gtk_tree_model_get_iter_first (set->ex.tree.model, &iter))) { gtk_tree_view_set_cursor (GTK_TREE_VIEW (set->widget), path, gtk_tree_view_get_column (GTK_TREE_VIEW (set->widget), 0), FALSE); } } return TRUE; } static gboolean _e2_option_tree_add_child_cb (GtkWidget *wid, E2_OptionSet *set) { GtkTreeIter iter; if (gtk_tree_selection_get_selected (gtk_tree_view_get_selection (GTK_TREE_VIEW (set->widget)), NULL, &iter)) { if (iter.user_data != NULL) { GtkTreeIter iter2; _e2_option_tree_add_default (set, &iter2, &iter, FALSE); } } else { _e2_option_tree_add_default (set, &iter, NULL, FALSE); } return TRUE; } static gboolean _e2_option_tree_add_below_cb (GtkWidget *wid, E2_OptionSet *set) { GtkTreeIter iter; if (gtk_tree_selection_get_selected (gtk_tree_view_get_selection (GTK_TREE_VIEW (set->widget)), NULL, &iter)) { if (iter.user_data != NULL) { GtkTreeIter iter2; _e2_option_tree_add_default (set, &iter2, &iter, TRUE); } } else { _e2_option_tree_add_default (set, &iter, NULL, TRUE); } return TRUE; } /******************/ /***** public *****/ /******************/ /** @brief (de)sensitize config dialog buttons that are not relevant to 'category' lines @param view UNUSED the treeview to which the buttons relate @param value TRUE to senstitize the buttons, FALSE to desensitize @return */ void e2_option_tree_adjust_buttons (GtkTreeView *view, gboolean value) { E2_Sextet *q = g_object_get_data (G_OBJECT (view), "e2-config-buttons"); if (q == NULL) return; if (q->b != NULL) gtk_widget_set_sensitive (q->b, value); if (q->c != NULL) gtk_widget_set_sensitive (q->c, value); if (q->d != NULL) gtk_widget_set_sensitive (q->d, value); if (q->e != NULL) gtk_widget_set_sensitive (q->e, value); } /** @brief create, and add to @a box, a config-data treeview for tree-option @a set This also backs up the current option data @param parent UNUSED the parent config-dialog widget @param box the widget into which the treeview and related things will be packed @param set set data structure @return */ void e2_option_tree_add_widget (GtkWidget *parent, GtkWidget *box, E2_OptionSet *set) { // config_dialog = parent; //keep a static reference GtkWidget *vbox = gtk_vbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (box), vbox, TRUE, TRUE, 0); GtkWidget *sw = e2_widget_add_sw (vbox, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, TRUE, E2_PADDING); gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN); //setup treeview GtkTreeView *treeview = GTK_TREE_VIEW (gtk_tree_view_new_with_model (set->ex.tree.model)); gtk_container_add (GTK_CONTAINER (sw), GTK_WIDGET (treeview)); gtk_tree_view_set_reorderable (treeview, TRUE); // gtk_tree_view_set_headers_clickable (treeview, FALSE); =default // gtk_tree_view_set_rules_hint (treeview, FALSE); =default #ifdef USE_GTK2_10 gtk_tree_view_set_enable_tree_lines (treeview, TRUE); #endif set->widget = GTK_WIDGET (treeview); #ifdef E2_IMAGECACHE //setup for appropriate icon size gint phigh; e2_widget_get_font_pixels (set->widget, NULL, &phigh); GtkIconSize iconsizes = e2_utils_get_best_icon_size (phigh + 2); #endif GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview); gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE); // gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE); g_object_set_data (G_OBJECT (set->ex.tree.model), "e2-option-set", set); // if (set->ex.tree.draggable_check_func != NULL) // { // printd (INFO, "drag func: %d", set->ex.tree.draggable_check_func); GtkTreeDragSourceIface *iface = GTK_TREE_DRAG_SOURCE_GET_IFACE (set->ex.tree.model); // iface->row_draggable = set->ex.tree.draggable_check_func; iface->row_draggable = _e2_option_tree_draggable_check_cb; // } //we need a way to set the 'dirty-flag' when any row is dragged iface->drag_data_delete = _e2_option_tree_drag_delete_cb; if (set->ex.tree.selection_check_func != NULL) gtk_tree_selection_set_select_function (selection, (GtkTreeSelectionFunc) set->ex.tree.selection_check_func, set, NULL); //add columns int j = 0; GList *member; for (member = set->ex.tree.columns; member != NULL; member = member->next) { GtkCellRenderer *renderer; GtkTreeViewColumn *column; E2_OptionTreeColumn *opt = member->data; gboolean editable = !(opt->flags & E2_OPTION_TREE_COL_NOT_EDITABLE); switch (opt->type) { case E2_OPTION_TREE_TYPE_BOOL: renderer = gtk_cell_renderer_toggle_new (); g_object_set (G_OBJECT (renderer), "activatable", TRUE, NULL); g_signal_connect (G_OBJECT (renderer), "toggled", G_CALLBACK (_e2_option_tree_toggle_cb), set); column = gtk_tree_view_column_new_with_attributes (opt->label, renderer, "active", j, NULL); g_object_set (G_OBJECT (column), "alignment", 0.5, NULL); gtk_tree_view_column_set_resizable (GTK_TREE_VIEW_COLUMN (column), TRUE); gtk_tree_view_column_set_cell_data_func (column, renderer, (GtkTreeCellDataFunc) _e2_option_tree_bool_data_func, opt, NULL); gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column); break; case E2_OPTION_TREE_TYPE_STR: case E2_OPTION_TREE_TYPE_KEY: renderer = gtk_cell_renderer_text_new (); g_object_set (G_OBJECT (renderer), "editable", editable, NULL); // g_signal_connect (G_OBJECT (?), "key-press-event", // G_CALLBACK (_e2_option_tree_cell_keypress_cb), renderer); g_signal_connect (G_OBJECT (renderer), "editing-started", G_CALLBACK (_e2_option_tree_cell_edit_start_cb), NULL); if (opt->type == E2_OPTION_TREE_TYPE_KEY) g_signal_connect (G_OBJECT (renderer), "editing-started", G_CALLBACK (_e2_option_tree_key_edit_start_cb), set); g_signal_connect (G_OBJECT (renderer), "editing-canceled", G_CALLBACK (_e2_option_tree_cell_edit_stop_cb), NULL); g_signal_connect (G_OBJECT (renderer), "edited", G_CALLBACK (_e2_option_tree_string_edited_cb), set); column = gtk_tree_view_column_new_with_attributes (opt->label, renderer, "text", j, NULL); gtk_tree_view_column_set_sizing (GTK_TREE_VIEW_COLUMN (column), GTK_TREE_VIEW_COLUMN_GROW_ONLY); gtk_tree_view_column_set_resizable (GTK_TREE_VIEW_COLUMN (column), TRUE); gtk_tree_view_column_set_cell_data_func (column, renderer, (GtkTreeCellDataFunc) _e2_option_tree_str_data_func, opt, NULL); gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column); break; case E2_OPTION_TREE_TYPE_ICON: renderer = gtk_cell_renderer_pixbuf_new (); g_object_set (G_OBJECT (renderer), "stock-size", GTK_ICON_SIZE_MENU, NULL); column = gtk_tree_view_column_new_with_attributes (opt->label, renderer, NULL); gtk_tree_view_column_set_cell_data_func (column, renderer, #ifdef E2_IMAGECACHE _e2_option_tree_cell_pixbuf_data_func, GINT_TO_POINTER (iconsizes), NULL); #else _e2_option_tree_cell_pixbuf_data_func, NULL, NULL); #endif gtk_tree_view_column_set_resizable (GTK_TREE_VIEW_COLUMN (column), TRUE); gtk_tree_view_column_set_sizing (GTK_TREE_VIEW_COLUMN (column), GTK_TREE_VIEW_COLUMN_GROW_ONLY); gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column); break; case E2_OPTION_TREE_TYPE_SEL: renderer = gtk_cell_renderer_combo_new (); GtkTreeModel *mdl = (opt->visible_check_data == NULL) ? GTK_TREE_MODEL (actions_store): e2_action_filter_store (opt->visible_check_data); g_object_set (G_OBJECT (renderer), "model", mdl, "text-column", 0, "editable", editable, NULL); // g_signal_connect (G_OBJECT (?), "key-press-event", // G_CALLBACK (_e2_option_tree_cell_keypress_cb), renderer); g_signal_connect (G_OBJECT (renderer), "editing-started", G_CALLBACK (_e2_option_tree_cell_edit_start_cb), NULL); g_signal_connect (G_OBJECT (renderer), "editing-canceled", G_CALLBACK (_e2_option_tree_cell_edit_stop_cb), NULL); g_signal_connect (G_OBJECT (renderer), "edited", G_CALLBACK (_e2_option_tree_string_edited_cb), set); //CHECKME is it possible to open the combo at the current spot? column = gtk_tree_view_column_new_with_attributes (opt->label, renderer, NULL); gtk_tree_view_column_set_cell_data_func (column, renderer, (GtkTreeCellDataFunc) _e2_option_tree_str_data_func, opt, NULL); /* does not work gint colwidth; gtk_tree_view_column_cell_get_size (column, NULL, NULL, NULL, &colwidth, NULL); g_object_set (G_OBJECT (column), "min-width", colwidth+20, NULL); */ gtk_tree_view_column_set_resizable (GTK_TREE_VIEW_COLUMN (column), TRUE); gtk_tree_view_column_set_sizing (GTK_TREE_VIEW_COLUMN (column), GTK_TREE_VIEW_COLUMN_AUTOSIZE); //GROW_ONLY); gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column); break; default: renderer = NULL; column = NULL; //warning prevention printd (WARN, "don't know how to render column %d of %s", j, set->name); break; } if (renderer != NULL) { g_object_set_data (G_OBJECT (renderer), "column", GINT_TO_POINTER (j)); g_object_set_data (G_OBJECT (column), "column", GINT_TO_POINTER (j)); } j++; } //spacer column if ((int) set->ex.tree.flags & E2_OPTION_TREE_LAST_COL_EMPTY) { GtkCellRenderer *renderer = gtk_cell_renderer_text_new (); GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes ("", renderer, NULL); gtk_tree_view_append_column (treeview, column); } //now expand everything gtk_tree_view_expand_all (treeview); // gtk_tree_view_set_hover_expand (treeview, TRUE); //annoying ? //add control buttons sw = e2_widget_get_sw_plain (GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER); gtk_box_pack_start (GTK_BOX (vbox), sw, FALSE, FALSE, 0); GtkWidget *bbox = gtk_hbox_new (FALSE, E2_PADDING); e2_widget_sw_add_with_viewport (sw, bbox); E2_Sextet *q = (E2_Sextet *) e2_utils_sextet_new (); g_object_set_data_full (G_OBJECT (treeview), "e2-config-buttons", q, (GDestroyNotify) e2_utils_sextet_destroy); if (q != NULL) { q->a = e2_button_add (bbox, FALSE, 0, _("_Help"), GTK_STOCK_HELP, _("Get help on this option"), _e2_option_tree_help_cb, set); //insert extra button on specific pages if (e2_option_get_simple ("plugins") == set) e2_button_add (bbox, FALSE, 0, _("_Select"), GTK_STOCK_OPEN, _("Show plugin-selection dialog"), e2_confdlg_choose_plugin_cb, set); #ifdef E2_RAINBOW else if (e2_option_get_simple ("filetypes") == set) e2_button_add (bbox, FALSE, 0, _("Co_lor"), GTK_STOCK_SELECT_COLOR, _("Show color-selection dialog"), e2_confdlg_extcolorpick_cb, set); #endif if (set->ex.tree.flags & E2_OPTION_TREE_UP_DOWN) { q->b = e2_button_add (bbox, FALSE, 0, _("_Up"), GTK_STOCK_GO_UP, _("Move the selected row one place up"), _e2_option_tree_move_up_cb, set); q->c = e2_button_add (bbox, FALSE, 0, _("_Down"), GTK_STOCK_GO_DOWN, _("Move the selected row one place down"), _e2_option_tree_move_down_cb, set); } if (set->ex.tree.flags & E2_OPTION_TREE_ADD_DEL) { q->d = e2_button_add (bbox, FALSE, 0, _("_Remove"), GTK_STOCK_REMOVE, _("Remove the selected row"), e2_option_tree_del_direct_cb, set); q->e = e2_button_add (bbox, FALSE, 0, _("Add a_fter"), GTK_STOCK_ADD, _("Add a row after the currently selected row in the tree"), _e2_option_tree_add_below_cb, set); if (! (set->ex.tree.flags & E2_OPTION_TREE_LIST)) q->f = e2_button_add (bbox, FALSE, 0, _("Add ch_ild"), GTK_STOCK_INDENT, _("Add a child row to the currently selected one"), _e2_option_tree_add_child_cb, set); } } //add context-menu E2_TreeContextMenuFlags context_flags = E2_TREE_CONTEXT_DEFAULT; if (!((gboolean) set->ex.tree.flags & E2_OPTION_TREE_LIST)) context_flags |= E2_TREE_CONTEXT_EXP_COL; GtkWidget *context_menu = gtk_menu_new (); _e2_option_tree_menu_items_add (treeview, context_menu, context_flags, set); //connect callbacks //no longer needed it seems // g_signal_connect (treeview, "drag-drop", G_CALLBACK (drop_cb), set); // g_signal_connect (treeview, "drag-end", G_CALLBACK (drag_end_cb), set); //arrange for data backup if/when the page is opened g_signal_connect (treeview, "realize", G_CALLBACK (_e2_option_tree_page_opened_cb), set); g_signal_connect (treeview, "popup_menu", G_CALLBACK (_e2_option_tree_popup_menu_cb), context_menu); /* the dialog's "negative response" handler takes care of this g_signal_connect_after (treeview, "key-press-event", G_CALLBACK (e2_confdlg_key_press_cb), NULL); */ //this one handles left-button clicks g_signal_connect (treeview, "button-press-event", G_CALLBACK (_e2_option_tree_button_press_cb), set); //this one handles right-button clicks g_signal_connect (treeview, "button-press-event", G_CALLBACK (_e2_option_tree_button_press_cb2), context_menu); //goto first row & column GtkTreeIter iter; if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (set->ex.tree.model), &iter)) { GtkTreePath *path = gtk_tree_model_get_path (GTK_TREE_MODEL (set->ex.tree.model), &iter); gtk_tree_view_set_cursor (treeview, path, gtk_tree_view_get_column (treeview, 0), FALSE); gtk_tree_path_free (path); } } /** @brief begin registration of a tree option Complete registraion involves this func, then e2_option_tree_add_column() for each column in the treestore, tnen e2_option_tree_create_store() to finish things @param name name of the option, generally a constant string but sometimes runtime-created @param group group the option belongs to, used in config dialog, can be a r-t string FREEME or a _() @param desc textual description of the option used in config dialog, a r-t _() string @param depends name of another option this one depends on @param selection_check_func function to check whether row is selectable @param draggable_check_func function to check whether selected row is draggable @param flags flags set when registering a tree option @param flags2 bitflags determining how the option data is to be handled @return E2_OptionSet data struct for the option */ E2_OptionSet *e2_option_tree_register (gchar *name, gchar *group, gchar *desc, gchar *depends, gpointer selection_check_func, gpointer draggable_check_func, E2_OptionTreeTypeFlags flags, E2_OptionFlags flags2) { E2_OptionSet *set = e2_option_register (E2_OPTION_TYPE_TREE, name, group, desc, NULL, depends, flags2); set->ival = -1; set->sval = NULL; set->ex.tree.synced = FALSE; set->ex.tree.def = NULL; // set->ex.tree.columns_num = 0; //set when all cols processed set->ex.tree.columns = NULL; set->ex.tree.flags = flags; // set->ex.tree.unknown = NULL; set->ex.tree.selection_check_func = selection_check_func; set->ex.tree.draggable_check_func = draggable_check_func; return set; } /** @brief setup data for a column of a tree-set This establishes some parameters for regular use, and others for config-dialog use. @param set pointer to data struct for the tree-set @param name column label string @param type indicator of the type of data in the column @param idef default value if the column has a bool value @param sdef default value if the column has a non-bool value @param flags flags relating to data editability, cleanup @param visible_check_func function to check whether a cell in the column is displayed in a config dialog @param visible_check_data data to provide to @a visible_check_func @return */ void e2_option_tree_add_column (E2_OptionSet *set, const gchar *name, E2_OptionTreeType type, gint idef, const gchar *sdef, E2_OptionTreeColFlags flags, gpointer visible_check_func, gpointer visible_check_data) { E2_OptionTreeColumn *col = ALLOCATE (E2_OptionTreeColumn); //FIXME no deallocation CHECKALLOCATEDFATAL (col); set->ex.tree.columns = g_list_append (set->ex.tree.columns, col); col->label = name; col->type = type; col->idef = idef; col->sdef = sdef; col->flags = flags; col->visible_check_func = visible_check_func; col->visible_check_data = visible_check_data; } /** @brief create a treestore for the data of a tree-set This works from listed columns' data at set->ex.tree.columns @param set pointer to data struct for the tree-set @return */ void e2_option_tree_create_store (E2_OptionSet *set) { set->ex.tree.columns_num = g_list_length (set->ex.tree.columns); GType t[set->ex.tree.columns_num + 1]; //space for registered columns + 1 //the extra trailing column is for visibility flag t[set->ex.tree.columns_num] = G_TYPE_BOOLEAN; GList *member; gint i; for (member = set->ex.tree.columns, i = 0; member != NULL; member = member->next, i++) { E2_OptionTreeColumn *tcol = member->data; switch (tcol->type) { case E2_OPTION_TREE_TYPE_BOOL: t[i] = G_TYPE_INT; break; case E2_OPTION_TREE_TYPE_STR: case E2_OPTION_TREE_TYPE_ICON: case E2_OPTION_TREE_TYPE_SEL: case E2_OPTION_TREE_TYPE_KEY: t[i] = G_TYPE_STRING; break; default: printd (ERROR, "internal error, tree options may only have bool or string values"); break; } } set->ex.tree.model = gtk_tree_store_new (1, t[0]); gtk_tree_store_set_column_types (set->ex.tree.model, set->ex.tree.columns_num + 1, t); } /** @brief create a string representing a tree row This is a helper fn for backup and config file writing Depth is represented by leading tabs Columns are separated by '|' Any other embedded '|', or an initial '>', is escaped with '\' c.f. e2_tree_row_to_string() which does not store bools as "true" or "false" @param set set data structure @param iter treeiter used for scanning the treemodel @param level tree path-depth of the processed line @return the constructed string */ gchar *e2_option_tree_row_write_to_string (E2_OptionSet *set, GtkTreeIter *iter, gint level) { GString *treerow = g_string_sized_new (128); gint j; for (j = 0; j < level; j++) treerow = g_string_append_c (treerow, '\t'); GList *column; j = 0; for (column = set->ex.tree.columns; column != NULL; column = g_list_next (column)) { E2_OptionTreeColumn *opt = column->data; switch (opt->type) { case E2_OPTION_TREE_TYPE_BOOL: { gboolean int_data; gtk_tree_model_get (set->ex.tree.model, iter, j, &int_data, -1); if (int_data) treerow = g_string_append (treerow, "true"); else treerow = g_string_append (treerow, "false"); } break; case E2_OPTION_TREE_TYPE_STR: case E2_OPTION_TREE_TYPE_ICON: case E2_OPTION_TREE_TYPE_SEL: case E2_OPTION_TREE_TYPE_KEY: { gchar *str_data; gtk_tree_model_get (set->ex.tree.model, iter, j, &str_data, -1); if (str_data != NULL) { if (j == 0 && str_data[0] == '>') treerow = g_string_append (treerow, "\\"); if ((str_data[0] != '\0') && ((strchr (str_data, '|')) != NULL)) //if always ascii |, don't need g_utf8_strchr() { gchar **split = g_strsplit (str_data, "|", -1); g_free (str_data); str_data = g_strjoinv ("\\|", split); g_strfreev (split); } treerow = g_string_append (treerow, str_data); g_free (str_data); } } break; default: break; } treerow = g_string_append_c (treerow, '|'); j++; } //get rid of superfluous trailing '|' treerow = g_string_truncate (treerow, treerow->len -1); gchar *result = g_string_free (treerow, FALSE); return result; } /** @brief write a string-representation of tree set @a set to @a file This is a helper fn for wrting config file data There is no error checking It is recursive, to deal with descendants @param f pointer to file descriptor @param set the set data structure @param iter treeiter used for scanning the treemodel @param level the current path depth in the tree @return */ void e2_option_tree_write_to_file (E2_FILE *f, E2_OptionSet *set, GtkTreeIter *iter, gint level) { do { gchar *rowstring = e2_option_tree_row_write_to_string (set, iter, level); gchar *writestring = g_strconcat (rowstring, "\n", NULL); // if ( e2_fs_put_stream (f, writestring, "config" E2_ERR_NONE()); // == EOF) // { FIXME handle error } g_free (rowstring); g_free (writestring); GtkTreeIter iter2; if (gtk_tree_model_iter_children (set->ex.tree.model, &iter2, iter)) e2_option_tree_write_to_file (f, set, &iter2, level + 1); } while (gtk_tree_model_iter_next (set->ex.tree.model, iter)); } /** @brief create a string array of option-tree row data for a specified optionset This is a helper fn for backup It is recursive, to deal with descendants @param lines ptr-array where the strings are recorded @param set the set data structure @param iter treeiter used for scanning the treemodel @param level the current path depth in the tree @return */ static void e2_option_tree_write_to_storage (GPtrArray *lines, E2_OptionSet *set, GtkTreeIter *iter, gint level) { do { gchar *rowstring = e2_option_tree_row_write_to_string (set, iter, level); g_ptr_array_add (lines, (gpointer) rowstring); GtkTreeIter iter2; if (gtk_tree_model_iter_children (set->ex.tree.model, &iter2, iter)) e2_option_tree_write_to_storage (lines, set, &iter2, level + 1); } while (gtk_tree_model_iter_next (set->ex.tree.model, iter)); } /** @brief create backup data for a specified treeoption set Data are stored as strings, one for each tree row, in the same format as used for the config file Each row is referred to in a NULL-terminated pointer array This fn does nothing if there is already backup data for the set @param set the set data structure @return */ void e2_option_tree_backup (E2_OptionSet *set) { if (set->ex.tree.def == NULL) { //backup data does not exist now, so ok to create it GPtrArray *lines = g_ptr_array_sized_new (21); g_ptr_array_add (lines, (gpointer) g_strdup_printf ("%s=<", set->name)); GtkTreeIter iter; if (gtk_tree_model_get_iter_first (set->ex.tree.model, &iter)) e2_option_tree_write_to_storage (lines, set, &iter, 0); g_ptr_array_add (lines, (gpointer) g_strdup (">")); // set->ex.tree.def_num = (gint) lines->len; g_ptr_array_add (lines, NULL); set->ex.tree.def = lines->pdata; g_ptr_array_free (lines, FALSE); } } /** @brief revert tree option backup data @param set pointer to set data structure @param empty_check TRUE to check for backup data only if loaded config data is missing @return */ static void _e2_option_tree_revert (E2_OptionSet *set, gboolean empty_check) { if ((!set->ex.tree.synced || !empty_check) && set->ex.tree.def != NULL) //CHECKME this test { g_object_unref (set->ex.tree.model); e2_option_tree_create_store (set); //option needs default configuration which is stored in //array at ex.tree.def of the OptionSet e2_option_tree_set_from_array (set->name, (gchar **) set->ex.tree.def, NULL, NULL); //make sure the set is recognised as clean set->ex.tree.flags &= ~E2_OPTION_TREE_SET_EDITED; } } /** @brief revert tree option backup data @param option_name the name of the set @return */ void e2_option_tree_revert (gchar *option_name) { E2_OptionSet *set = e2_option_tree_get (option_name); _e2_option_tree_revert (set, FALSE); } /** @brief optionally revert, then clean, tree option backup data @param set the set data structure @param revert TRUE to restore the backup data to the tree, FALSE to cleanup only @return */ void e2_option_tree_unbackup (E2_OptionSet *set, gboolean revert) { if (set->ex.tree.def != NULL) { //backup data exists if (revert) _e2_option_tree_revert (set, FALSE); //cleanup g_strfreev ((gchar **)set->ex.tree.def); set->ex.tree.def = NULL; } } /** @brief iterate over all option trees, reverting any with backup data @return */ void e2_option_tree_restore_all (void) { guint i; gpointer *walker; E2_OptionSet *set; for (i = 0, walker = options_array->pdata; i < options_array->len; i++, walker++) { set = *walker; if (set->type == E2_OPTION_TYPE_TREE) e2_option_tree_unbackup (set, TRUE); } } /** @brief log a function to be called to setup tree-option defaults if needed @a func needs to be of the form void (*func) (E2_Optionset *) and to call e2_option_tree_setup_defaults () @param set the set data structure @param func pointer to function which will install default tree data @return */ void e2_option_tree_prepare_defaults (E2_OptionSet *set, gpointer func) { set->ex.tree.def = func; } /** @brief setup default config tree contents, ready for installation This creates a NULL-terminated vector of tree-row data strings, which in aggregate (are expected to) have the same format as the config file data for the set being processed. @param set the set data structure @param first start of a null-terminated series of arguments, each a newly-allocated string which is to be converted to a tree row @return */ void e2_option_tree_setup_defaults (E2_OptionSet *set, gchar *first, ...) { if (first == NULL) return; // printd (DEBUG, "preparing default option tree for %s>", first); va_list args; va_start (args, first); gchar *cur = first; GPtrArray *lines = g_ptr_array_sized_new (32); while (cur) { // cur = g_strdup (cur); //store ptr to constant string (do not free, later) g_ptr_array_add (lines, (gpointer) cur); cur = va_arg (args, gchar *); } va_end (args); // set->ex.tree.def_num = (gint) lines->len; UNUSED g_ptr_array_add (lines, NULL); //null signals end, to parser set->ex.tree.def = lines->pdata; g_ptr_array_free (lines, FALSE); } /** @brief install default config tree data where needed after loading the config file (if any) at session start, check if any tree option needs a default config, (stored in arrays) and install it if so then free the default tree 'branches' @return */ void e2_option_tree_install_defaults (void) { guint i; gpointer *walker; E2_OptionSet *set; for (i = 0, walker = options_array->pdata; i < options_array->len; i++, walker++) { set = *walker; if (set->type == E2_OPTION_TYPE_TREE) { if (!set->ex.tree.synced //option needs default data && set->ex.tree.def != NULL) //setup func has been logged { // printd (DEBUG, "installing default option tree %s>", // VPSTR(set->ex.tree.def)); void (*install_func) (E2_OptionSet *) = set->ex.tree.def; //call the defaults-install function, which in turn calls // e2_option_tree_setup_defaults () which sets up a // pointer array at set->ex.tree.def again (*install_func) (set); //parse the data e2_option_tree_set_from_array (set->name, (gchar **) set->ex.tree.def, NULL, NULL); //cleanup g_strfreev ((gchar **) set->ex.tree.def); } set->ex.tree.def = NULL; //always zap the function/vector pointer } } } /* * @brief Append top-level tree iter for @a name, and populate if from @a options @param name name of option @param iter pointer to iter which stores the result @param n_options number of treestore columns @param options array of values to store in the new iter @return */ /* UNUSED void e2_option_tree_add_simple (gchar *name, GtkTreeIter *iter, gint n_options, void *options[]) { E2_OptionSet *set = e2_option_tree_get (name); if (set != NULL) e2_option_tree_add (set, iter, NULL, FALSE, TRUE, n_options, options); } */ /** @brief Add tree iter to @a set, and populate if from @a options Any NULL value in @a options is ignored, its stored value is undefined @param set data struct for the set @param iter pointer to iter which stores the result @param parent pointer the iter which is the "base" for relative insertion @param sibling TRUE to add new iter at same level as @a parent, FALSE to add a child @param before TRUE to add iter before @a parent @param n_options number of treestore columns @param options array of values to store in the new iter @return */ void e2_option_tree_add (E2_OptionSet *set, GtkTreeIter *iter, GtkTreeIter *parent, gboolean sibling, gboolean before, gint n_options, void *options[]) { if (sibling) { //new iter at same level if (before) //before parent, or appended to toplevel if parent == NULL gtk_tree_store_insert_before (set->ex.tree.model, iter, NULL, parent); else //after parent, or prepended to toplevel if parent == NULL gtk_tree_store_insert_after (set->ex.tree.model, iter, NULL, parent); } else { //new child iter if (before) //append to parent's children gtk_tree_store_insert_before (set->ex.tree.model, iter, parent, NULL); else //prepend to parent's children gtk_tree_store_insert_after (set->ex.tree.model, iter, parent, NULL); } gint i; for (i = 0; i < n_options; i++) { if (options[i] != NULL) gtk_tree_store_set (set->ex.tree.model, iter, i, options[i], -1); } gtk_tree_store_set (set->ex.tree.model, iter, set->ex.tree.columns_num, TRUE, -1); e2_option_tree_flag_change (set); } void e2_option_tree_add_default (gchar *option, GtkTreeIter *iter, GtkTreeIter *parent, gboolean sibling) { E2_OptionSet *set = e2_option_tree_get (option); if (set != NULL) _e2_option_tree_add_default (set, iter, parent, sibling); } static void _e2_option_tree_add_default (E2_OptionSet *set, GtkTreeIter *iter, GtkTreeIter *parent, gboolean sibling) { void *arg[set->ex.tree.columns_num]; GList *column; gint i = 0; for (column = set->ex.tree.columns; column != NULL; column = g_list_next (column)) { E2_OptionTreeColumn *opt = column->data; switch (opt->type) { case E2_OPTION_TREE_TYPE_BOOL: arg[i] = GINT_TO_POINTER (opt->idef); break; case E2_OPTION_TREE_TYPE_SEL: case E2_OPTION_TREE_TYPE_STR: case E2_OPTION_TREE_TYPE_ICON: case E2_OPTION_TREE_TYPE_KEY: arg[i] = (void *)opt->sdef; break; default: break; } i++; } e2_option_tree_add (set, iter, parent, sibling, FALSE, set->ex.tree.columns_num, arg); if ((parent != NULL) && (sibling == FALSE)) gtk_tree_view_expand_all (GTK_TREE_VIEW (set->widget)); gtk_tree_view_set_cursor (GTK_TREE_VIEW (set->widget), gtk_tree_model_get_path (GTK_TREE_MODEL (set->ex.tree.model), iter), gtk_tree_view_get_column (GTK_TREE_VIEW (set->widget), 0), FALSE); if (set->widget) { gtk_widget_grab_focus (set->widget); } } void e2_option_tree_del (gchar *option, GtkTreeIter *iter) { E2_OptionSet *set = e2_option_tree_get (option); if (set != NULL) e2_option_tree_del_direct (set, iter); } void e2_option_tree_del_direct (E2_OptionSet *set, GtkTreeIter *iter) { gtk_tree_store_remove (set->ex.tree.model, iter); e2_option_tree_flag_change (set); } static void _e2_option_tree_move (E2_OptionSet *set, GtkTreeIter *iter, GtkTreePath *path_dest, gboolean sibling, gboolean before) { //find out real number of columns, we also want to move the meta data gint n = gtk_tree_model_get_n_columns (set->ex.tree.model); gpointer values[n]; gint i; //save old row contents for (i = 0; i < n; i++) { gtk_tree_model_get (set->ex.tree.model, iter, i, values + i, -1); } GtkTreePath *path_old = gtk_tree_model_get_path (set->ex.tree.model, iter); GtkTreeRowReference *ref_old = gtk_tree_row_reference_new (set->ex.tree.model, path_old); GtkTreeIter iter2; if (gtk_tree_model_get_iter (set->ex.tree.model, &iter2, path_dest)) { GtkTreeIter iter_new; e2_option_tree_add (set, &iter_new, &iter2, sibling, before, n, values); gtk_tree_path_free (path_old); path_old = gtk_tree_row_reference_get_path (ref_old); if (gtk_tree_model_get_iter (set->ex.tree.model, &iter2, path_old)) { if (gtk_tree_model_iter_has_child (set->ex.tree.model, &iter2)) { gboolean expand = FALSE; if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (set->widget), path_old)) { expand = TRUE; gtk_tree_view_collapse_row (GTK_TREE_VIEW (set->widget), path_old); } GtkTreePath *path_new = gtk_tree_model_get_path (set->ex.tree.model, &iter_new); GtkTreeIter iter_child; while (gtk_tree_model_iter_children (set->ex.tree.model, &iter_child, &iter2)) _e2_option_tree_move (set, &iter_child, path_new, FALSE, TRUE); if (expand) gtk_tree_view_expand_row (GTK_TREE_VIEW (set->widget), path_new, FALSE); gtk_tree_path_free (path_new); } e2_option_tree_del_direct (set, &iter2); } gtk_tree_view_set_cursor (GTK_TREE_VIEW (set->widget), path_dest, gtk_tree_view_get_column (GTK_TREE_VIEW (set->widget), 0), FALSE); } gtk_tree_path_free (path_old); gtk_tree_row_reference_free (ref_old); } static void _e2_option_tree_set (E2_OptionSet *set, GtkTreeIter *iter, gint col, void *data) { gtk_tree_store_set (GTK_TREE_STORE (set->ex.tree.model), iter, col, data, -1); e2_option_tree_flag_change (set); } /** @brief Read tree config data from @a f and setup treestore and model If @a index is non-NULL, the value initially stored there index is the index of the start of the set data, or of the data corresponding to @a root_iter if that is not at the start of the set. The fist line is skipped (unless we need to log the lot as an unknown set) Critical characters in @a f are assumed to be ascii Unknown options are logged as appropriate If @a root_iter is NULL, the root node is used, of course. This does not cope with badly-formed trees e.g. with missing level @param name set name string @param f NULL-terminated array of strings in config file format @param index pointer to store for index of @a f, or NULL @param root_iter pointer to treestore iter under which the data are to be added @return TRUE if the option was installed properly */ gboolean e2_option_tree_set_from_array (gchar *name, gchar *f[], gint *index, GtkTreeIter *root_iter) { gchar *line; //pointer to the current line gint idx = (index == NULL) ? 0 : *index; //array index E2_OptionSet *set = e2_option_get_simple (name); if (set == NULL) { //we've found an unknown tree option // printd (DEBUG, "found config tree for unknown option %s", name); //accumulate the lines GPtrArray *unknown_lines = g_ptr_array_sized_new (11); if (strstr (f[idx], "=<") == NULL) { //incomplete tree, from transient store-updates line = g_strconcat (name, "-part=<", NULL); g_ptr_array_add (unknown_lines, line); } while ((line = f[idx]) != NULL) { idx++; //now we know array's not finished g_strchomp (line); g_ptr_array_add (unknown_lines, line); if (line[0] == '>') { g_ptr_array_add (unknown_lines, NULL); gchar *value = g_strjoinv ("\n", (gchar **)unknown_lines->pdata); e2_option_unknown_record (g_strdup (name), value); break; } } g_ptr_array_free (unknown_lines, TRUE); if (index != NULL) *index = idx; return FALSE; } //process known tree option idx++; //pass the "name=<" line //flag for whether the tree data is still valid E2_TreeStatus tree_mode = E2_TREE_STARTED; //list of iters for managing parent-child dependencies GList *parents = g_list_append (NULL, root_iter); GList *member; gboolean firstline = TRUE; gint firstdepth = 0; while ((line = f[idx]) != NULL) { idx++; //now we know array's not finished g_strchomp (line); if (line[0] == '>') break; //ignore empty lines and comments if (*line == '\0') continue; if (line[0] == '#') continue; if (tree_mode == E2_TREE_ABORTED) continue; //path-depth of the current tree option row //determined by tabs at the start of the line gint level = 0; while (line[level] == '\t') level++; line += level; if (firstline) { //1st valid line corresponds to 1st child of root_iter, //not necessarily at depth 0 of the tree firstdepth = level; firstline = FALSE; } //"unescape" the first character if need be if (line[0] == '\\' && line[1] == '>') line++; //strsplit() isn't a great approach because it makes //unescaping complicated, but ... gchar **split = g_strsplit (line, "|", -1); void *tmp[set->ex.tree.columns_num]; //split counter, increased when stepping through **split gint i = 0; //tree column counter gint j = 0; //tree column pointer GList *columns; //unescape helper for concatting, later on GList *freecats = NULL; //transform splitted values to void pointers and //add default values in case of a missing value/field for (columns = set->ex.tree.columns; columns != NULL; columns = columns->next) { //not enough separators in the line = bad config data if (split[i] == NULL) { tree_mode = E2_TREE_ABORTED; break; } //current tree column-data pointer E2_OptionTreeColumn *coldata = columns->data; if ( *(split[i]) != '\0') { gchar *value = split[i]; //unescape | and concat values while (split[i + 1] != NULL) { gint len = strlen (value); if ((len > 0) && (value[len - 1] == '\\')) { value[len - 1] = '\0'; value = g_strconcat (value, "|", split[i + 1], NULL); //save pointer to free it after //knowing how long this really gets //(there could be several escaped | in one line) freecats = g_list_append (freecats, value); i++; } else break; } //get void pointer to add it to the model tmp[j] = _e2_option_tree_get_void_simple (coldata, value); } else //empty string found found, use default value tmp[j] = _e2_option_tree_get_void_simple (coldata, (void *)coldata->sdef); j++; i++; } if (tree_mode != E2_TREE_ABORTED) { //append row to children of parent, and populate it GtkTreeIter iter; i = level - firstdepth; e2_option_tree_add (set, &iter, g_list_nth_data (parents, i), FALSE, TRUE, set->ex.tree.columns_num, tmp); //add iter pointer to be able to add a child to this row later on GList *member = g_list_nth (parents, i+1); if (member == NULL) parents = g_list_append (parents, gtk_tree_iter_copy (&iter)); else { gtk_tree_iter_free (member->data); member->data = gtk_tree_iter_copy (&iter); } } else { //clear any data recorded already gtk_tree_store_clear (set->ex.tree.model); // set->ex.tree.synced = FALSE; redundant printd (WARN, "bad config data for %s, using default", set->name); } //cleanup g_strfreev (split); for (member = freecats; member != NULL; member = member->next) g_free (member->data); g_list_free (freecats); } if (line != NULL && tree_mode != E2_TREE_ABORTED) { //found config for this tree option, so don't init it //with the default config later on set->ex.tree.synced = TRUE; // set->ex.tree.def = NULL; //make sure the set is recognised as clean set->ex.tree.flags &= ~E2_OPTION_TREE_SET_EDITED; } //cleanup // parents = g_list_remove (parents, NULL); // g_list_foreach (parents, (GFunc) gtk_tree_iter_free, NULL); for (member = parents->next; member != NULL; member = member->next) gtk_tree_iter_free (member->data); g_list_free (parents); if (index != NULL) *index = idx; return (tree_mode != E2_TREE_ABORTED); } E2_OptionSet *e2_option_tree_get (gchar *option) { E2_OptionSet *set; set = (E2_OptionSet *) g_hash_table_lookup (options_hash, option); if (set == NULL) { printd (WARN, "trying to access option '%s' which doesn't exist", option); return NULL; } if (set->type != E2_OPTION_TYPE_TREE) { printd (WARN, "trying to access tree option '%s' which isn't a tree", set->name); return NULL; } return set; } void *_e2_option_tree_get_void_simple (E2_OptionTreeColumn *col, gchar *option) { switch (col->type) { case E2_OPTION_TREE_TYPE_BOOL: if (g_str_equal (option, "true")) return (void *) TRUE; else return (void *) FALSE; break; case E2_OPTION_TREE_TYPE_STR: case E2_OPTION_TREE_TYPE_ICON: case E2_OPTION_TREE_TYPE_SEL: case E2_OPTION_TREE_TYPE_KEY: return (void *) option; default: return NULL; break; } return NULL; } emelfm2-0.4.1/src/config/e2_option_tree.h0000600000175000017500000001140511010340377017126 0ustar cairocairo/* $Id: e2_option_tree.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_OPTION_TREE_H__ #define __E2_OPTION_TREE_H__ #include "emelfm2.h" #include "e2_option.h" typedef enum { E2_OPTION_TREE_TYPE_BOOL, E2_OPTION_TREE_TYPE_STR, E2_OPTION_TREE_TYPE_ICON, E2_OPTION_TREE_TYPE_SEL, E2_OPTION_TREE_TYPE_KEY } E2_OptionTreeType; //these are flags that may be set for a tree-option //column when that column is added to the tree option //stored in glist at respective ((set->ex.tree.columns)->data)->flags typedef enum { E2_OPTION_TREE_COL_NOT_EDITABLE = 1<< 0, //flags for things to free when the config data is dumped E2_OPTION_TREE_COL_FREENAME = 1 << 8, //UNUSED E2_OPTION_TREE_COL_FREESTRING = 1 << 9, //UNUSED E2_OPTION_TREE_COL_FREEEXTRA = 1 << 10, //UNUSED E2_OPTION_TREE_COL_FREEXTRADATA = 1 << 11, //UNUSED } E2_OptionTreeColFlags; //data struct for each tree-option column. typedef struct _E2_OptionTreeColumn { const gchar *label; //config-dialog column header label E2_OptionTreeType type; //enumerator of type of data in the column gint idef; //default boolean value for the column, if relevant const gchar *sdef; //default string for the column, if relevant //flags that may be set when a column is added to a tree option E2_OptionTreeColFlags flags; //function to call for checking whether to display a cell in the column //gboolean (*fun) (GtkTreeModel *, GtkTreeIter *, GtkCellRenderer *, gpointer) //for sel-columns, NULL gpointer visible_check_func; //data to supply to visible_check_func() //OR for sel-columns, a set of flags to supply to the filtermodel visibility func gpointer visible_check_data; } E2_OptionTreeColumn; void e2_option_tree_flag_change (E2_OptionSet *set); void e2_option_tree_adjust_buttons (GtkTreeView *view, gboolean value); void e2_option_tree_add_widget (GtkWidget *parent, GtkWidget *box, E2_OptionSet *set); E2_OptionSet *e2_option_tree_register (gchar *name, gchar *group, gchar *desc, gchar *depends, gpointer selection_check_func, gpointer draggable_check_func, E2_OptionTreeTypeFlags flags, E2_OptionFlags flags2); void e2_option_tree_add_column (E2_OptionSet *set, const gchar *name, E2_OptionTreeType type, gint idef, const gchar *sdef, E2_OptionTreeColFlags flags, gpointer visible_check_func, gpointer visible_check_data); void e2_option_tree_create_store (E2_OptionSet *set); void e2_option_tree_revert (gchar *name); E2_OptionSet *e2_option_tree_get (gchar *option); void e2_option_tree_add_simple (gchar *option, GtkTreeIter *iter, gint n_options, void *options[]); void e2_option_tree_add (E2_OptionSet *set, GtkTreeIter *iter, GtkTreeIter *parent, gboolean sibling, gboolean before, gint n_options, void *options[]); void e2_option_tree_add_default (gchar *option, GtkTreeIter *iter, GtkTreeIter *parent, gboolean sibling); void e2_option_tree_del (gchar *option, GtkTreeIter *iter); void e2_option_tree_del_direct (E2_OptionSet *set, GtkTreeIter *iter); void e2_option_tree_set (gchar *option, GtkTreeIter *iter, gint col, void *data); void e2_option_tree_backup (E2_OptionSet *set); void e2_option_tree_unbackup (E2_OptionSet *set, gboolean revert); void e2_option_tree_restore_all (void); void e2_option_tree_prepare_defaults (E2_OptionSet *set, void *func); void e2_option_tree_setup_defaults (E2_OptionSet *set, gchar *first, ...); void e2_option_tree_install_defaults (void); gboolean e2_option_tree_set_from_array (gchar *name, gchar *f[], gint *index, GtkTreeIter *root_iter); gchar *e2_option_tree_row_write_to_string (E2_OptionSet *set, GtkTreeIter *iter, gint level); //this needs to be in header e2_fs.h, to prevent build problems //related to E2_FILE //void e2_option_tree_write_to_file (E2_FILE *f, E2_OptionSet *set, // GtkTreeIter *iter, gint level); // context menu things typedef enum { E2_TREE_CONTEXT_DEFAULT=1<<0, E2_TREE_CONTEXT_CP_PST=1<<1, E2_TREE_CONTEXT_EXP_COL=1<<2, } E2_TreeContextMenuFlags; GHashTable *tree_view_buffer_hash; void e2_option_tree_menu_hash_clean (GList *list); void e2_option_tree_menu_set_sensitive (GtkWidget *menu, GtkWidget *treeview); #endif //ndef __E2_OPTION_TREE_H__ emelfm2-0.4.1/src/config/e2_cache.h0000600000175000017500000000601311010340377015641 0ustar cairocairo/* $Id: e2_cache.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004 Florian Zaehringer (flo.zaehringer@web.de) Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_CACHE_H__ #define __E2_CACHE_H__ // #define DEFAULT_CACHE_FILE "cache" << stuffs up gettext, replaced #include "emelfm2.h" typedef enum { //simple types E2_CACHE_TYPE_BOOL, E2_CACHE_TYPE_INT, E2_CACHE_TYPE_LONG, E2_CACHE_TYPE_TIME, //could be int or long E2_CACHE_TYPE_FLOAT, E2_CACHE_TYPE_DOUBLE, E2_CACHE_TYPE_STR, E2_CACHE_TYPE_LIST, E2_CACHE_TYPE_TREE, E2_CACHE_TYPE_ARRAY, //treated as longs E2_CACHE_TYPE_STORE, /* //these are for things that don't like being set directly ... E2_CACHE_TYPE_BOOLPTR, E2_CACHE_TYPE_INTPTR, E2_CACHE_TYPE_DBLPTR, */ } E2_CacheType; typedef struct _E2_Cache { E2_CacheType type; gchar *name; gpointer *data; gpointer sync_func; gpointer sync_data; } E2_Cache; #ifdef E2_IMAGECACHE typedef enum { E2_IMAGE_BMP, E2_IMAGE_SVG } E2Imagetype; typedef struct _E2_Image { // E2Imagetype type; //not used gint size; //pixel size GdkPixbuf *pixbuf; // gint refcount; //not used } E2_Image; #endif #ifdef E2_IMAGECACHE void e2_cache_icons_init (void); E2_Image *e2_cache_image_get (const gchar *name, GtkIconSize size); void e2_cache_image_clearall (void); #endif //cache management gboolean e2_cache_check (gchar *name); #define e2_cache_bool_register(name, val, def) e2_cache_int_register (name, val, def) void e2_cache_int_register (gchar *name, gint *value, gint def); gint e2_cache_ROint_register (gchar *name, gint *value, gint def); //#w = gint **intwatch, for debugging void e2_cache_time_register (gchar *name, time_t *value, time_t def); void e2_cache_double_register (gchar *name, gdouble *value, gdouble def); void e2_cache_str_register (gchar *name, gchar **str, gchar *def); void e2_cache_store_register (gchar *name, gpointer *store, // gpointer fillfunc, gpointer syncfunc); void (*fillfunc) (gpointer*, GList*), GList*(*syncfunc) (gpointer, gpointer), gpointer syncdata); E2_Cache *e2_cache_list_register (gchar *name, GList **list); void e2_cache_array_register (gchar *name, guint size, gint *values, gint *defs); void e2_cache_unregister (gchar *name); void e2_cache_file_write (void); void e2_cache_init (gboolean config_dir_ready); void e2_cache_clean (void); void e2_cache_clean1 (const gchar *name); #endif //ndef __E2_CACHE_H__ emelfm2-0.4.1/src/config/e2_cl_option.c0000600000175000017500000002443410715150533016572 0ustar cairocairo/* $Id: e2_cl_option.c 708 2007-11-09 21:30:03Z tpgww $ Copyright (C) 2003-2007 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /* @file src/config/e2_cl_option.c @brief session-start command-line option processing, and help messages display Glib's option parsing mechanism - GOptionContext and friends - ia not used because several emelFM2 options may have whitespace inside an argument string - and that is not supported by GOptionContext (nor is -h for help) */ #include "emelfm2.h" #include #include #include #include "e2_cl_option.h" /** @brief print minimal usage message on console @return */ static void _e2_cl_option_print_usage (void) { g_printf (_("usage: %s [option]\n"), BINNAME); } /** @brief print full usage message on console @return */ static void _e2_cl_option_print_help (void) { _e2_cl_option_print_usage (); /*NOTE for translators: do not translate the actual option strings (-h, --help etc) but it's ok to translate any other part of this string */ g_printf ( _("Program options:\n" "-1,--one=DIR set 1st pane's start directory to DIR\n" "-2,--two=DIR set 2nd pane's start directory to DIR\n" "-c,--config=DIR set config directory to DIR (default: ~/.config/emelfm2)\n" "-e,--encoding=TYPE set filesystem character encoding to TYPE\n" "-f,--fallback-encoding set fallback encoding (default: ISO-8859-1)\n" "-i,--ignore-problems ignore encoding/locale problems (at your own risk!)\n" "-l,--log-all maximise scope of error logging\n" "-m,--daemon run program as daemon\n" "-r,--run-at-start=CMD run command CMD at session start\n" "-s,--set-option=OPT set one-line gui option using config-file formatted OPT\n" "-t,--trash=DIR set trash directory to DIR (default: ~/.local/share/Trash/files)\n" "\n" "Help options:\n" "-h,--help show this help message\n" "-u,--usage display brief usage messsage\n" "-v,--version display version and build info\n") ); #ifdef DEBUG_MESSAGES /*need separate message to allow the main message to work regardless of debug mode when the .po file is created and when the program is run */ g_printf ( _("-d,--debug=[1-5] set debug level from 1 (low) to 5 (high)\n" "-x,--verbose display time/location info on debug messages\n") ); #endif } /** @brief print build-related message on console @return */ static void _e2_cl_option_print_version (void) { g_printf ( _("%s v. %s\n" "Licensed under the GPL\n" "Copyright (C) %s\n" //© doesn't work "Build date: %s\n" "Build platform: GTK+ %d.%d.%d %s\n"), PROGNAME, VERSION RELEASE, COPYRIGHT, BUILDDATE, //BUILDDATE may be non-utf-8 GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION, BUILDINFO ); } /** @brief parse commandline options and store results for later use @param argc no. of commandline arguments, as supplied to main() @param argv array of commanline arguments, as supplied to main() @return */ void e2_cl_option_process (gint argc, gchar *argv[]) { //non-string non-zero-default values need to be set before the options are parsed #ifdef DEBUG_MESSAGES e2_cl_options.debug_level = E2_DEBUG_LEVEL; #endif //default to no logging when detailed messaging happening e2_cl_options.suppress_gtk_log = TRUE; //(E2_DEBUG_LEVEL > 2); gchar *freeme; gint c; #ifdef DEBUG_MESSAGES gint d = 0; #endif while (TRUE) { gint option_index = 0; static struct option long_options[] = { {"one", required_argument, NULL, '1'}, {"two", required_argument, NULL, '2'}, {"config", required_argument, NULL, 'c'}, #ifdef DEBUG_MESSAGES {"debug", required_argument, NULL, 'd'}, #endif {"encoding", required_argument, NULL, 'e'}, {"fallback-encoding", required_argument, NULL, 'f'}, {"ignore-problems", no_argument, NULL, 'i'}, {"log-all", no_argument, NULL, 'l'}, {"daemon", no_argument, NULL, 'm'}, {"run-at-start", required_argument, NULL, 'r'}, {"set-option", required_argument, NULL, 's'}, {"trash", required_argument, NULL, 't'}, #ifdef DEBUG_MESSAGES {"verbose", no_argument, NULL, 'x'}, #endif {"help", no_argument, NULL, 'h'}, {"usage", no_argument, NULL, 'u'}, {"version", no_argument, NULL, 'v'}, {NULL, no_argument, NULL, 0} }; #ifdef DEBUG_MESSAGES c = getopt_long (argc, argv, "1:2:c:d:e:f:t:ilmr:s:xhuv", long_options, &option_index); #else c = getopt_long (argc, argv, "1:2:c:e:f:t:ilmr:s:huv", long_options, &option_index); #endif if (c == -1) break; #ifdef DEBUG_MESSAGES d++; #endif if (optarg != NULL && *optarg != '\0') { gchar *convert; switch (c) { case '1': printd (DEBUG, "setting pane 1 startup path '%s'", optarg); //no encoding conversion yet, the arrangements are not yet in place e2_cl_options.pane1_path = g_strdup (optarg); break; case '2': printd (DEBUG, "setting pane 2 startup path '%s'", optarg); //no encoding conversion yet, the arrangements are not yet in place e2_cl_options.pane2_path = g_strdup (optarg); break; case 'c': g_free (e2_cl_options.config_dir); if (g_path_is_absolute (optarg)) #ifdef E2_FILES_UTF8ONLY convert = g_strdup (optarg); #else convert = optarg; #endif else { freeme = g_get_current_dir (); convert = g_build_filename (freeme, optarg, NULL); g_free (freeme); } #ifdef E2_FILES_UTF8ONLY e2_cl_options.config_dir = convert; #else e2_cl_options.config_dir = e2_utf8_filename_from_locale (convert); if (convert != optarg) g_free (convert); #endif printd (DEBUG, "setting config directory '%s'", e2_cl_options.config_dir); break; #ifdef DEBUG_MESSAGES case 'd': printd (DEBUG, "setting debug level '%s'", optarg); { gchar *end; e2_cl_options.debug_level = g_ascii_strtoull (optarg, &end, 10); if (end == optarg) { e2_cl_options.debug_level = E2_DEBUG_LEVEL; printd (WARN, "failed setting debug level '%s'", optarg); } // e2_cl_options.suppress_gtk_log = // (e2_cl_options.debug_level > 2); } break; #endif case 'e': g_free (e2_cl_options.encoding); e2_cl_options.encoding = g_strdup (optarg); break; case 'f': g_free (e2_cl_options.fallback_encoding); e2_cl_options.fallback_encoding = g_strdup (optarg); break; case 'r': e2_cl_options.startup_commands = g_list_append (e2_cl_options.startup_commands, g_strdup (optarg)); break; case 's': e2_cl_options.option_overrides = g_list_append (e2_cl_options.option_overrides, g_strdup (optarg)); break; case 't': g_free (e2_cl_options.trash_dir); if (g_path_is_absolute (optarg)) #ifdef E2_FILES_UTF8ONLY convert = g_strdup (optarg); #else convert = optarg; #endif else { freeme = g_get_current_dir (); convert = g_build_filename (freeme, optarg, NULL); g_free (freeme); } #ifdef E2_FILES_UTF8ONLY e2_cl_options.trash_dir = convert; #else e2_cl_options.trash_dir = e2_utf8_filename_from_locale (convert); if (convert != optarg) g_free (convert); #endif printd (DEBUG, "setting trash directory '%s'", e2_cl_options.trash_dir); break; default: printd (ERROR, "unknown option with code: 0%o and value: %s", c, optarg); break; } } else //no argument, or blank { switch (c) { case 'i': e2_cl_options.ignore_problems = TRUE; break; case 'l': e2_cl_options.suppress_gtk_log = FALSE; case 'm': e2_cl_options.detached = TRUE; break; #ifdef DEBUG_MESSAGES case 'x': e2_cl_options.verbose = TRUE; break; #endif case 'u': _e2_cl_option_print_usage (); exit (0); break; case 'h': _e2_cl_option_print_help (); exit (0); break; case 'v': _e2_cl_option_print_version (); exit (0); break; case '?': printd (ERROR, "unknown option"); //ignore exit (0); break; default: printd (ERROR, "unknown option with code: 0%o", c); break; } } } if (optind < argc) { g_printf (_("Startup options must begin with \"-\" or \"--\"\n")); exit (0); //FIXME do a clean exit } printd (DEBUG, "parsed %d command line options", d); //now we setup any missing default strings const gchar *usedir; if (e2_cl_options.config_dir == NULL) { usedir = g_getenv ("XDG_CONFIG_HOME"); if (usedir == NULL || *usedir == '\0') usedir = g_get_user_config_dir (); #ifdef E2_FILES_UTF8ONLY e2_cl_options.config_dir = g_build_filename (usedir, BINNAME, NULL); #else freeme = g_build_filename (usedir, BINNAME, NULL); e2_cl_options.config_dir = e2_utf8_filename_from_locale (freeme); g_free (freeme); #endif } //setup local default trash dir (any others done elsewhere) if (e2_cl_options.trash_dir == NULL) { usedir = g_getenv ("XDG_DATA_HOME"); if (usedir == NULL || *usedir == '\0') usedir = g_get_user_data_dir (); #ifdef E2_FILES_UTF8ONLY e2_cl_options.trash_dir = g_build_filename (usedir, "Trash", "files", NULL); #else freeme = g_build_filename (usedir, "Trash", "files", NULL); e2_cl_options.trash_dir = e2_utf8_filename_from_locale (freeme); g_free (freeme); #endif } const gchar **encodings; g_get_filename_charsets (&encodings); if (e2_cl_options.encoding == NULL) { if (encodings[0] != NULL && *encodings[0] != '\0') e2_cl_options.encoding = g_strdup (encodings[0]); else e2_cl_options.encoding = g_strdup ("ISO-8859-1"); } if (e2_cl_options.fallback_encoding == NULL) { if (encodings[1] != NULL && *encodings[1] != '\0') e2_cl_options.fallback_encoding = g_strdup (encodings[1]); else e2_cl_options.fallback_encoding = g_strdup ("ISO-8859-1"); } } emelfm2-0.4.1/src/config/e2_option_color.h0000600000175000017500000000250211010340377017303 0ustar cairocairo/* $Id: e2_option_color.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_OPTION_COLOR_H__ #define __E2_OPTION_COLOR_H__ #include "e2_option.h" #include "e2_option_color.h" #ifdef E2_RAINBOW void e2_option_color_filetypes_sync (void); #endif E2_OptionSet *e2_option_color_register (gchar *name, gchar *group, gchar *desc, gchar *tip, gchar *depends, gchar *def, E2_OptionFlags flags); GdkColor *e2_option_color_get (gchar *name); gboolean e2_option_color_set_str (gchar *name, gchar *value); gboolean e2_option_color_set_str_direct (E2_OptionSet *set, gchar *value); #endif //ndef __E2_OPTION_COLOR_H__ emelfm2-0.4.1/src/config/e2_option_bool.h0000600000175000017500000000275211010340377017127 0ustar cairocairo/* $Id: e2_option_bool.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_OPTION_BOOL_H__ #define __E2_OPTION_BOOL_H__ GtkWidget *e2_option_bool_add_menu_widget (GtkWidget *controller, GtkWidget *menu, E2_OptionSet *set, gpointer func, gpointer data); E2_OptionSet *e2_option_bool_register (gchar *name, gchar *group, gchar *desc, gchar *tip, gchar *depends, gboolean def, E2_OptionFlags flags); gboolean e2_option_bool_get (gchar *option); gboolean e2_option_bool_get_direct (E2_OptionSet *set); gboolean e2_option_bool_toggle (gchar *option); gboolean e2_option_bool_toggle_direct (E2_OptionSet *set); gboolean e2_option_bool_set (gchar *option, gboolean state); gboolean e2_option_bool_set_direct (E2_OptionSet *set, gboolean value); #endif //ndef __E2_OPTION_BOOL_H__ emelfm2-0.4.1/src/config/e2_option_unknown.h0000600000175000017500000000202611010340377017665 0ustar cairocairo/* $Id: e2_option_unknown.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_OPTION_UNKNOWN_H__ #define __E2_OPTION_UNKNOWN_H__ #include "emelfm2.h" void e2_option_unknown_record (gchar *option, gchar *value); void e2_option_transient_value_get (E2_OptionSet *set); #endif // ndef __E2_OPTION_UNKNOWN_H__ emelfm2-0.4.1/src/e2_dnd.h0000600000175000017500000000353311010340377014102 0ustar cairocairo/* $Id: e2_dnd.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2006-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_DND_H__ #define __E2_DND_H__ #include "emelfm2.h" enum { TARGET_URI, TARGET_STRING, TARGET_XDS }; //GdkAtom atom_XdndDirectSave0; //GdkAtom atom_text_plain; //GdkAtom atom_text_uri_list; void e2_dnd_drag_begin_cb (GtkWidget *treeview, GdkDragContext *context, ViewInfo *view); gboolean e2_dnd_drag_motion_cb (GtkWidget *treeview, GdkDragContext *context, gint x, gint y, guint time, ViewInfo *view); gboolean e2_dnd_drag_leave_cb (GtkWidget *treeview, GdkDragContext *context, guint time, ViewInfo *view); /*void e2_dnd_drag_delete_cb (GtkWidget *treeview, GdkDragContext *context, ViewInfo *view); */ //gboolean e2_dnd_drag_drop_cb (GtkWidget *treeview, GdkDragContext *context, // gint x, gint y, guint time, ViewInfo *view); void e2_dnd_drag_data_get_cb (GtkWidget *treeview, GdkDragContext *context, GtkSelectionData *data, guint info_arg, guint time, ViewInfo *view); void e2_dnd_drag_data_received_cb (GtkWidget *treeview, GdkDragContext *context, gint x, gint y, GtkSelectionData *data, guint drag_info, guint time, ViewInfo *view); #endif //ndef __E2_DND_H__ emelfm2-0.4.1/src/e2_pane.c0000600000175000017500000014631411014640776014272 0ustar cairocairo/* $Id: e2_pane.c 894 2008-05-20 21:26:22Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/e2_pane.c @brief pane creation and action functions includes actions on pane contents, but not on panes per se */ /** \page panes the filelist panes ToDo \section treeviews */ #include "emelfm2.h" //#include #include #include #include "e2_pane.h" #include "e2_action.h" #include "e2_dialog.h" #ifdef E2_TREEDIALOG #include "e2_tree_dialog.h" #endif #include "e2_context_menu.h" #include "e2_filelist.h" static void _e2_pane_change_dir (E2_PaneRuntime *rt, gchar *path, gboolean history, gboolean hook); extern guint last_selected_rows; /*****************/ /***** utils *****/ /*****************/ #ifdef E2_VFS /** @brief setup the pane to which @a rt applies to show data for a specified namespace Any error messages downstream expect BGL closed here @a spacedescriptor can have various forms, from full-URI to "-1" - see plugin for details @param rt pointer to pane data struct, NULL for active pane @param spacedescriptor utf8 string to be interpreted into existing or new namespace @return TRUE on successful completion or no change needed */ gboolean e2_pane_change_space_byuri (E2_PaneRuntime *rt, gchar *spacedescriptor) { if (e2_fs_vfsfunc_ready ((gpointer *)&vfs.set_namedspace)) { ViewInfo *view; if (rt == NULL) view = curr_view; else view = (rt == &app.pane1) ? &app.pane1_view : &app.pane2_view; return (vfs.set_namedspace (view, spacedescriptor)); //CHECKME last-used dir is opened when we change space ? } else { printd (WARN, "Cannot find vfs plugin"); return FALSE; } } /** @brief setup the pane to which @a rt applies to show data for a specified namespace If @a utfpath has a NULL localpath, then the relevant history or vtab path will be used @param rt pointer to pane data struct @param localpath pointer to path and namespace data struct @return TRUE on successful change or no change needed */ gboolean e2_pane_change_space (E2_PaneRuntime *rt, VPATH *utfpath) { PlaceInfo *current = rt->view->spacedata; if (current == utfpath->spacedata) return TRUE; //nothing to change //E2_VFSTMP may want to change dir anyway if (e2_fs_vfsfunc_ready ((gpointer *)&vfs.set_space)) { ViewInfo *view; if (rt == NULL) view = curr_view; else view = (rt == &app.pane1) ? &app.pane1_view : &app.pane2_view; if (!vfs.set_space (view, utfpath->spacedata, TRUE)) return FALSE; //E2_VFSTMP CHECKME last-used dir is opened when we change space ? if (utfpath->localpath == NULL) _e2_pane_change_dir (rt, "FIXME-HISTORY-ITEM", FALSE, TRUE); else _e2_pane_change_dir (rt, (gchar *)utfpath->localpath, TRUE, TRUE); return TRUE; } else { printd (WARN, "Cannot find vfs plugin"); return FALSE; } } #endif //fdef E2_VFS /** @brief set filepanes' properties to indicate which one is active The "active-pane-signal" config option is checked, and: the column headers will be re-coloured, or the filename column title will be [un]bolded, or the treeviews' sensitivity will be toggled @return */ void e2_pane_flag_active (void) { E2_OptionSet *set = e2_option_get ("active-pane-signal"); gint htype = e2_option_int_get_direct (set); switch (htype) { case 1: { //set bold Filename-column header title gchar *new_title = gettext (e2_all_columns[FILENAME].title); gchar *new_title2 = g_strconcat ("", new_title,"", NULL); gtk_label_set_markup (curr_view->name_label, new_title2); g_free (new_title2); //set normal Filename-column header title gtk_label_set_text (other_view->name_label, new_title); break; } case 2: { //make active pane sensitive gtk_widget_set_sensitive (curr_view->treeview, TRUE); //make inactive pane insensitive gtk_widget_set_sensitive (other_view->treeview, FALSE); break; } default: { //set all active columns' header color GdkColor *active_btncolor = e2_option_color_get ("color-active-pane"); GList *base = gtk_tree_view_get_columns (GTK_TREE_VIEW (curr_view->treeview)); GList *columns; for (columns = base; columns !=NULL; columns = columns->next) gtk_widget_modify_bg (((GtkTreeViewColumn *)columns->data)->button, GTK_STATE_NORMAL, active_btncolor); g_list_free (base); //revert all inactive columns' header color to default base = gtk_tree_view_get_columns (GTK_TREE_VIEW (other_view->treeview)); for (columns = base; columns != NULL; columns = columns->next) gtk_widget_modify_bg (((GtkTreeViewColumn *)columns->data)->button, GTK_STATE_NORMAL, NULL); g_list_free (base); break; } } } /** @brief change active pane from the current one to the other one Assumes BGL is closed on arrival here @return */ void e2_pane_activate_other (void) { // printd (DEBUG, "e2_pane_activate_other ()"); E2_PaneRuntime *tmp = other_pane; other_pane = curr_pane; curr_pane = tmp; //force a status-line update last_selected_rows = -1; #ifdef E2_STATUS_DEMAND BGL e2_window_update_status_bar (NULL); #endif e2_fileview_switch_views (); // gtk_widget_grab_focus (tmp->focus_widget); if (GTK_WIDGET_HAS_FOCUS (other_view->treeview)) gtk_widget_grab_focus (curr_view->treeview); gdk_threads_leave (); e2_hook_list_run (&app.hook_pane_focus_changed, tmp); gdk_threads_enter (); } /* * @brief change-directory completion-watch timer-function @param completed_flag store for flag to watch @return FALSE when ready to shutdown timer */ /*static gboolean _e2_pane_cd_watch (E2_CDType *completed_flag) { return (*completed_flag == CD_NOTFINISHED); //FIXME signal that this timer is being shutdown } */ /* * @brief change the directory for a specified pane and setup completion-watch This is a wrapper for _e2_pane_change_dir (), with update of pane history list and hooklist It must be called with gtk's thread-lock closed @param rt pointer to pane data struct, or NULL for current pane @param path utf8 string containing actual or virtual path to directory to be opened @param completed_flag store for flag to set to CD_SUCCESS if/when cd is completed @return */ /* void e2_pane_change_dir_watch (E2_PaneRuntime *rt, gchar *path, E2_CDType *completed_flag) { *completed_flag = CD_NOTFINISHED; _e2_pane_change_dir (rt, path, completed_flag, TRUE, TRUE); //FIXME wait for completion, without blocking the cd process // app.timers[?] = g_timeout_add (100, (GSourceFunc) _e2_pane_cd_watch, &completed_flag); //FIXME nicely block while the timer is still running } */ /** @brief change the directory for a specified pane This is a wrapper for _e2_pane_change_dir (), with update of pane history list and hooklist gtk's thread-lock (BGL) may be open or closed @param rt pointer to pane data struct, or NULL for current pane @param path utf8 string containing actual or virtual path to directory to be opened @return */ void e2_pane_change_dir (E2_PaneRuntime *rt, gchar *path) { _e2_pane_change_dir (rt, path, TRUE, TRUE); } /** @brief change directory displayed in a specified pane Makes no assumption about whether BGL is active, but uses timer to force-open BGL for downstream processing where needed ~ at start of path is interpreted to $HOME Surrounding single- or double-quotes are removed Redundant separators are removed, and one is added to the end, if not already there Any ".." at start or later in the path is corrected if possible (or if not, ignored) Does nothing if the path string is empty, shows error msg if path is unreachable Session-start check for valid path done in main(), not here The supplied path string is unchanged. New path is stored in heap @ rt->path New path is also stored in array @ rt->view->dir @param rt pointer to pane data struct, or NULL for current pane @param path utf8 string containing space-relative path of directory to be opened @param history TRUE to update pane history list @param hook TRUE to run change-dir hook functions (if any, normally has at least update dirline) @return */ static void _e2_pane_change_dir (E2_PaneRuntime *rt, gchar *path, gboolean history, gboolean hook) { gchar *freeme, *currpath; printd (DEBUG, "_e2_pane_change_dir (rt:,path:\"%s\",update:%d,hook:%d)", path, history, hook); if (path == NULL) return; path = g_strdup (path); g_strchug (path); //trailing whitespace may be deliberate if (*path == '\0') { //quick exit g_free (path); return; } else if (path[0] == '"' || path[0] == '\'') { gint len = strlen (path); gchar *s = path + len - sizeof (gchar); if (*s == path[0]) { path[0] = ' '; *s = '\0'; g_strchug (path); //trailing whitespace may be deliberate } } //work on currently active file pane if argument is NULL if (rt == NULL) rt = curr_pane; //work with a copy of path string which might be in transition by a prior cd LISTS_LOCK currpath = g_strdup (rt->path); LISTS_UNLOCK if (g_str_equal (path, "..") && g_str_equal (currpath, G_DIR_SEPARATOR_S) #ifdef E2_VFSTMP //CHECKME how does updir from archive-root work here ? #endif ) { //ignore impossible change g_free (path); g_free (currpath); return; } //CHECKME do a full interpretation here ? //this fails in the event of trailing whitespace else if (path[0] == '.' && (path[1] == '\0' || (path[1] == G_DIR_SEPARATOR && path[2] == '\0'))) { g_free (path); #ifdef E2_VFSTMP freeme = ?; #else freeme = g_get_current_dir (); #endif path = F_FILENAME_FROM_LOCALE (freeme); if (path != freeme) g_free (freeme); } else if (path[0] == '~') //home dir check { freeme = path; if (path[1] == '\0') { path = g_strdup (g_get_home_dir ()); g_free (freeme); } else if (path[1] == G_DIR_SEPARATOR) { path = g_strconcat (g_get_home_dir (), path + 1, NULL); g_free (freeme); } } /* #ifndef E2_FILES_UTF8ONLY if (!app.utf8_filenames) { //this test not needed with only-utf8 file coding if (! g_utf8_validate (path, -1, NULL)) { freeme = path; path = e2_utf8_filename_from_locale (path); g_free (freeme); } } #endif */ #ifdef E2_VFSTMP //FIXME for changes up from a virtual root ... if ( !g_str_equal (path, "..") || !g_str_equal (currpath, G_DIR_SEPARATOR_S)) { #endif //cleanup the new path string //these funcs both ensure a trailing /, which gets into rt->path //(see below) and onto dir lines if (e2_option_bool_get_direct (rt->opt_transparent) || g_str_equal (path, "..")) //always handle cd .. properly { //relative path segment(s) conversion //clean and convert the path string, if needed freeme = path; #ifdef E2_VFSTMP //CHECKME confirm this is ok for non-local work-dir #endif path = e2_utils_translate_relative_path (currpath, path); g_free (freeme); } else { if (!g_path_is_absolute (path)) { freeme = path; path = g_strconcat (currpath, path, NULL); g_free (freeme); } //make sure the user didn't enter a 'dirty' path string #ifdef E2_VFSTMP //CHECKME confirm this is ok for non-local work-dir path = e2_utils_path_clean (path); #else path = e2_utils_path_clean (path); #endif } #ifdef E2_VFSTMP } #endif g_free (currpath); E2_Listman *data = (rt == &app.pane1) ? &app.pane1_view.listcontrols : &app.pane2_view.listcontrols; LISTS_LOCK data->view = rt->view; data->history = history; data->hook = hook; //now signal we're ready to do a new cd // any prior data->newpath is copied and cleared downstream, when ready data->newpath = path; //this is a copy LISTS_UNLOCK /*we use a timer, not a thread directly, to control the cd process cuz: . we're happy to do the cd at a reasonably non-busy time . if gtk's "native" BGL mutex is in play, _must_ ensure that BGL is off, for the downstream code that performs the cd (BUT this does make it tough to wait for the end of any specific cd) we check in the timer callback, not here, for any duplicated timer for this same pane (or the other one), so as to eliminate any race and missed cd request */ gint interval = (data->cd_working) ? 90 : //check about every 90 mS for completion of prior cd 4; //short initial delay in case there's a following request for other pane // data->timer = with possible repeated timers for the same pane, the id is useless //#ifdef DEBUG_MESSAGES // guint tid = //#endif g_timeout_add_full (G_PRIORITY_HIGH, interval, (GSourceFunc) e2_fileview_cd_manage, data, NULL); // printd (DEBUG, "initiated cd timer = %d", tid); // LISTS_UNLOCK /* //setup for completion-watching if requested if (completed_flag != NULL) { E2_CDwatch *data2 = ALLOCATE (E2_CDwatch); CHECKALLOCATEDWARN (data2, return;) data2->view = rt->view; data2->newpath = g_strdup (path); data2->repeats = 0; data2->completed_flag = completed_flag; //this timer stops after 100 callbacks = 10 seconds //CHECKME make this cancellable at session-end ? //data->watchtimer_id = g_timeout_add (100, (GSourceFunc) e2_fileview_cd_watch, data2); } */ } /** @brief show a chosen directory in a pane @param rt pointer to pane data struct, or NULL to determine the pane @param entry pointer to widget where the action was initiated, or NULL @return TRUE if the directory was changed successfully */ gboolean e2_pane_goto_choice (E2_PaneRuntime *rt, GtkWidget *entry) { #ifdef E2_VFSTMP //FIXME ensure this works with non-mounted dirs #endif E2_PaneRuntime *prt; E2_CommandLineRuntime *clrt; if (entry != NULL && g_str_equal (IS_A (entry), "GtkEntry")) { if (rt != NULL) prt = rt; else { clrt = g_object_get_data (G_OBJECT (entry), "command-line-runtime"); prt = clrt->pane; } } else { //try to find relevant pane and/or entry GList *line; for (line = app.command_lines; line != NULL ; line = g_list_next (line)) { clrt = line->data; if (! clrt->original //this is a dir line && (GTK_BIN (clrt->cl)->child == entry//processing a keybinding, entry != NULL || clrt->pane == rt) //processing a mouse-click, rt != NULL ) { prt = clrt->pane; entry = GTK_BIN (clrt->cl)->child; break; } } if (line == NULL) //OOPS !! return FALSE; //can't do anything } //create relevant path string const gchar *choice = gtk_entry_get_text (GTK_ENTRY (entry)); gchar *dir; if (g_str_has_prefix (choice, G_DIR_SEPARATOR_S)) dir = g_strdup (choice); else { #ifdef E2_VFSTMP //FIXME path for non-mounted dirs #else //path string might be involved in a current cd LISTS_LOCK dir = g_strconcat (prt->path, choice, NULL); LISTS_UNLOCK #endif } #ifdef E2_VFSTMP //FIXME handle vfs error reasonably #endif e2_fs_get_valid_path (&dir, FALSE E2_ERR_NONE()); gchar *newdir = NULL; e2_opendir_dialog_create (prt->view, dir, &newdir); g_free (dir); if (newdir != NULL) { //something was selected _e2_pane_change_dir (prt, newdir, TRUE, TRUE); g_free (newdir); return TRUE; } //user cancelled gtk_widget_grab_focus (entry); gtk_editable_select_region (GTK_EDITABLE (entry), -1, -1); //unselect the entry contents return FALSE; } /* * @brief timeout function for checking completion of fileview creation @param userdata data specified when the timer was initialised, here, always NULL, unused @return TRUE until update is completed, then FALSE */ /*gboolean _e2_pane_views_complete (gpointer userdata) { if (!(curr_view->listing || other_view->listing)) { printd (DEBUG, "finished waiting for fileview completion"); gdk_threads_enter (); e2_fileview_switch_views (); gtk_widget_grab_focus (curr_view->treeview); gdk_threads_leave (); //run this with BGL open/off e2_hook_list_run (&app.hook_pane_focus_changed, curr_pane); return FALSE; } return TRUE; //continue waiting } */ /** @brief pane history goto-menu callback @param menuitem the selected item @param rt pointer to pane data struct @return */ static void _e2_pane_history_selected_cb (GtkMenuItem *menuitem, E2_PaneRuntime *rt) { #ifdef E2_VFSTMP //FIXME ensure this works with non-mounted dirs ie history is specific to the fs type/place #endif gpointer curr = g_object_get_data (G_OBJECT (menuitem), "e2-history-pick"); rt->opendir_cur = GPOINTER_TO_UINT (curr); GtkWidget* label = gtk_bin_get_child (GTK_BIN (menuitem)); const gchar* dir = gtk_label_get_label (GTK_LABEL (label)); _e2_pane_change_dir (rt, (gchar *)dir, TRUE, TRUE); } /*******************/ /***** actions *****/ /*******************/ /** @brief switch panes if a specified pane is not the active one, or focus current pane @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if the focus was changed */ static gboolean _e2_pane_focus_action (gpointer from, E2_ActionRuntime *art) { E2_PaneRuntime *rt = (E2_PaneRuntime *)art->action->data; if (rt != NULL && rt != curr_pane) { e2_pane_activate_other (); return TRUE; } else if (rt == NULL) { gtk_widget_grab_focus (curr_view->treeview); return TRUE; } return FALSE; } /** @brief change active pane from the current one to the other one This also toggles the visible filepane, if only 1 is shown @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ gboolean e2_pane_activate_other_action (gpointer from, E2_ActionRuntime *art) { //if showing only 1 filepane, show the other one now if (app.window.panes_paned_ratio < 0.001) e2_window_adjust_pane_ratio ("1"); else if (app.window.panes_paned_ratio > 0.999) e2_window_adjust_pane_ratio ("0"); e2_pane_activate_other (); return TRUE; } /** @brief change the directory shown in a specified pane or the current one The cd is asynchronous, so probably not done immediately Downstream function expects BGL to be closed @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ gboolean e2_pane_change_dir_action (gpointer from, E2_ActionRuntime *art) { E2_PaneRuntime *rt = (E2_PaneRuntime *) art->action->data; if (rt == NULL) rt = curr_pane; _e2_pane_change_dir (rt, (gchar *)art->data, TRUE, TRUE); return TRUE; } /* * @brief for a specified pane, open the directory @a number places forward in the history list The last entry in the list will be used if we try to go forward too far @param rt pointer to pane data struct @param number integer no.of places in the history list to go forward @return */ /*static void _e2_pane_go_forward_ (E2_PaneRuntime *rt, gint number) { NOTE - IF USED, THIS MAY NEED A BASE-INDEX CORRECTION (1 TO 0) AND LIST-DIRECTION SWAP printd (DEBUG, "_e2_pane_go_forward (rt:,number:%d", number); guint old = rt->opendir_cur; guint len = g_list_length (rt->history); rt->opendir_cur += number; if (rt->opendir_cur > g_list_length (rt->history)) rt->opendir_cur = len; if (old != rt->opendir_cur) { // gchar *dir = F_FILENAME_TO_LOCALE ( gchar *dir = g_list_nth_data (rt->history, rt->opendir_cur - 1); _e2_pane_change_dir (rt, dir, NULL, FALSE, TRUE); // F_FREE (dir); } } */ /** @brief goto the next dir in the history list for specified or current pane @param from the button, menu item etc which was activated @param art action runtime data @return TRUE unless there's nowhere to goto */ static gboolean _e2_pane_go_forward (gpointer from, E2_ActionRuntime *art) { // printd (DEBUG, "_e2_pane_go_forward action"); E2_PaneRuntime *rt = (E2_PaneRuntime *)art->action->data; //action data is pointer to pane data struct, or NULL to use current pane if (rt == NULL) rt = curr_pane; //history entries are prepended to list, forward in history = back in list HISTORY_LOCK if (rt->opendirs == NULL || rt->opendir_cur == 0) { HISTORY_UNLOCK return FALSE; //nowhere to go to } if (g_str_equal (IS_A ((GtkWidget *)from), "GtkButton")) { GdkModifierType mask = e2_utils_get_modifiers (); if (mask & GDK_CONTROL_MASK) { GtkWidget *menu = gtk_menu_new (); GtkWidget *item; guint curr = (rt->opendir_cur > 1) ? rt->opendir_cur - 1 : 0; GList *member = g_list_nth (rt->opendirs, curr); HISTORY_UNLOCK while (member != NULL) { item = gtk_menu_item_new_with_label ((gchar *)member->data); gtk_widget_show_all (item); gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); g_object_set_data (G_OBJECT (item), "e2-history-pick", GUINT_TO_POINTER (curr)); g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (_e2_pane_history_selected_cb), rt); HISTORY_LOCK member = member->prev; HISTORY_UNLOCK curr--; } g_signal_connect (G_OBJECT (menu), "selection-done", G_CALLBACK (e2_menu_destroy_cb), NULL); gtk_menu_popup (GTK_MENU (menu), NULL, NULL, (GtkMenuPositionFunc) e2_toolbar_set_menu_position, from, 1, gtk_get_current_event_time ()); return TRUE; } } rt->opendir_cur--; // gchar *dir = F_FILENAME_TO_LOCALE ( gchar *dir = g_list_nth_data (rt->opendirs, rt->opendir_cur); HISTORY_UNLOCK _e2_pane_change_dir (rt, dir, FALSE, TRUE); // F_FREE (dir); return TRUE; } /* * @brief for a specified pane, open the directory @a number places back in the history list The first entry in the list will be used if we try to go back too far @param rt pointer to pane data struct @param number integer no.of places in the history list to go back @return */ /*static void _e2_pane_go_back_ (E2_PaneRuntime *rt, gint number) { NOTE - IF USED, THIS MAY NEED A BASE-INDEX CORRECTION (1 TO 0) AND LIST-DIRECTION SWAP printd (DEBUG, "_e2_pane_go_back (rt:,number:%d", number); guint old = rt->opendir_cur; rt->opendir_cur -= number; if (rt->opendir_cur < 1) rt->opendir_cur = 1; if (old != rt->opendir_cur) { gchar *dir = g_list_nth_data (rt->history, rt->opendir_cur - 1); _e2_pane_change_dir (rt, dir, FALSE, TRUE); } }*/ /** @brief goto the previous dir in the history list for specified or current pane @param from the button, menu item etc which was activated @param art action runtime data @return TRUE unless there's nowhere to goto */ static gboolean _e2_pane_go_back (gpointer from, E2_ActionRuntime *art) { // printd (DEBUG, "_e2_pane_go_back action"); E2_PaneRuntime *rt = (E2_PaneRuntime *)art->action->data; if (rt == NULL) rt = curr_pane; //history entries are prepended to list, so back in history = forward in list HISTORY_LOCK if (rt->opendirs == NULL || rt->opendir_cur == g_list_length (rt->opendirs) - 1) { HISTORY_UNLOCK return FALSE; } if (g_str_equal (IS_A ((GtkWidget *)from), "GtkButton")) { GdkModifierType mask = e2_utils_get_modifiers (); if (mask & GDK_CONTROL_MASK) { GtkWidget *menu = gtk_menu_new (); GtkWidget *item; guint curr = rt->opendir_cur + 1; GList *member = g_list_nth (rt->opendirs, curr); HISTORY_UNLOCK while (member != NULL) { item = gtk_menu_item_new_with_label ((gchar *)member->data); gtk_widget_show_all (item); gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); g_object_set_data (G_OBJECT (item), "e2-history-pick", GUINT_TO_POINTER (curr)); g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (_e2_pane_history_selected_cb), rt); HISTORY_LOCK member = member->next; HISTORY_UNLOCK curr++; } g_signal_connect (G_OBJECT (menu), "selection-done", G_CALLBACK (e2_menu_destroy_cb), NULL); gtk_menu_popup (GTK_MENU (menu), NULL, NULL, (GtkMenuPositionFunc) e2_toolbar_set_menu_position, from, 1, gtk_get_current_event_time ()); return TRUE; } } rt->opendir_cur++; gchar *dir = g_list_nth_data (rt->opendirs, rt->opendir_cur); HISTORY_UNLOCK _e2_pane_change_dir (rt, dir, FALSE, TRUE); return TRUE; } /** @brief in a specified pane or current pane, open the parent of the current directory The helper function prevents any change if at root dir already @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_pane_go_up (gpointer from, E2_ActionRuntime *art) { E2_PaneRuntime *rt = (E2_PaneRuntime *)art->action->data; if (rt == NULL) rt = curr_pane; _e2_pane_change_dir (rt, "..", TRUE, TRUE); return TRUE; } /** @brief open pane other pane directory in this pane @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_pane_mirror (gpointer from, E2_ActionRuntime *art) { E2_PaneRuntime *rt = (E2_PaneRuntime *)art->action->data; E2_PaneRuntime *ort = (rt == &app.pane1) ? &app.pane2 : &app.pane1; LISTS_LOCK gchar *pathcopy = g_strdup (ort->path); LISTS_UNLOCK _e2_pane_change_dir (rt, pathcopy, TRUE, TRUE); g_free (pathcopy); return TRUE; } /** @brief show a chosen directory in a pane @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if directory was changed */ static gboolean _e2_pane_goto_choice (gpointer from, E2_ActionRuntime *art) { E2_PaneRuntime *rt = (E2_PaneRuntime *)art->action->data; //can be NULL if from != NULL GtkWidget *entry = (from == NULL) ? NULL : GTK_WIDGET (from); return (e2_pane_goto_choice (rt, entry)); } /** @brief open the trash directory in the inactive pane There is no hook update for the pane @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if trash directory is recorded */ static gboolean _e2_pane_goto_trash (gpointer from, E2_ActionRuntime *art) { const gchar *tp = e2_utils_get_trash_path (); if (tp != NULL) { //change the dir line entry, if possible E2_CommandLineRuntime *clrt; GList *line; for (line = app.command_lines; line != NULL ; line = g_list_next (line)) { clrt = line->data; if (! clrt->original //this is a dir line && clrt->pane == other_pane) { GtkWidget *entry = GTK_BIN (clrt->cl)->child; gtk_entry_set_text (GTK_ENTRY (entry), tp); break; } } _e2_pane_change_dir (other_pane, (gchar *) tp, TRUE, FALSE); //NOTE no hook so no change of dirline ! return TRUE; } e2_output_print_error (_("No trash directory is available"), FALSE); return FALSE; } /** @brief create and pop up a filters menu for a specified pane, adjacent to a toolbar button @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_pane_filter_menu_create (gpointer from, E2_ActionRuntime *art) { E2_PaneRuntime *rt = (E2_PaneRuntime *)art->action->data; ViewInfo *view; if (rt == NULL) view = curr_view; else view = (rt == &app.pane1) ? &app.pane1_view : &app.pane2_view; GtkWidget *menu = e2_menu_create_filter_menu (view); guint32 time = gtk_get_current_event_time (); gtk_menu_popup (GTK_MENU (menu), NULL, NULL, (GtkMenuPositionFunc) e2_toolbar_set_menu_position, from, 1, time); return TRUE; } /** @brief create and show filters menu in active pane @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_pane_filters_show (gpointer from, E2_ActionRuntime *art) { GtkWidget *menu = e2_menu_create_filter_menu (curr_view); guint32 time = gtk_get_current_event_time (); gtk_menu_popup (GTK_MENU (menu), NULL, NULL, (GtkMenuPositionFunc) e2_fileview_set_menu_position, curr_view->treeview, 0, time); return TRUE; } #ifdef E2_VFS /** @brief select-namespace action, where a string argument is supplied @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if the change was successful */ static gboolean _e2_pane_select_space (gpointer from, E2_ActionRuntime *art) { return (e2_pane_change_space_byuri ((E2_PaneRuntime *)art->action->data, (gchar *)art->data)); } /** @brief vfs menu-item callback This must be here, not in vfs plugin, as it is one mechanism to load that plugin Other callbacks are in the plugin @param menu_item the selected menu item widget @param view pointer to data struct for view to be changed @return */ static void _e2_pane_vfs_dialog_cb (GtkWidget *menu_item, ViewInfo *view) { if (e2_fs_vfsfunc_ready ((gpointer *)&vfs.show_historydialog)) vfs.show_historydialog (view); else printd (WARN, "Cannot find vfs plugin"); } /** @brief respond to pane vfs-button click @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_pane_vfs_menu_show (gpointer from, E2_ActionRuntime *art) { E2_PaneRuntime *rt = (E2_PaneRuntime *)art->action->data; //will be NULL for active-pane action ViewInfo *view; if (rt == NULL) view = curr_view; else view = (rt == &app.pane1) ? &app.pane1_view : &app.pane2_view; GtkWidget *item, *menu = gtk_menu_new (); if (e2_fs_vfsfunc_ready ((gpointer *)&vfs.create_placesmenu)) //note no cursor change in event of slow loading { gboolean history = FALSE; if (g_str_equal (IS_A ((GtkWidget *)from), "GtkButton")) { GdkModifierType mask = e2_utils_get_modifiers (); if (mask & GDK_CONTROL_MASK) history = TRUE; } vfs.create_placesmenu (menu, view, history); //add plugin-specific items to menu } item = e2_menu_add (menu, _("_Edit places"), NULL, NULL, _e2_pane_vfs_dialog_cb, view); g_signal_connect (G_OBJECT (menu), "selection-done", G_CALLBACK (e2_menu_destroy_cb), NULL); guint32 time = gtk_get_current_event_time (); if (rt == NULL) gtk_menu_popup (GTK_MENU (menu), NULL, NULL, (GtkMenuPositionFunc) e2_fileview_set_menu_position, curr_view->treeview, 0, time); else gtk_menu_popup (GTK_MENU (menu), NULL, NULL, (GtkMenuPositionFunc) e2_toolbar_set_menu_position, from, 1, time); return TRUE; } #endif /** @brief select all items in specified pane The pane is made active, if it wasn't already @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_pane_select_all (gpointer from, E2_ActionRuntime *art) { E2_PaneRuntime *rt = (E2_PaneRuntime *)art->action->data; if (rt != curr_pane) e2_pane_activate_other (); e2_fileview_select_all (NULL, curr_view); #ifdef E2_STATUS_DEMAND use a selection-change cb ? BGL management e2_window_update_status_bar (NULL); #endif return TRUE; } /** @brief select all items in active pane @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_pane_current_select_all (gpointer from, E2_ActionRuntime *art) { e2_fileview_select_all (NULL, curr_view); #ifdef E2_STATUS_DEMAND use a selection-change cb ? BGL management e2_window_update_status_bar (NULL); #endif return TRUE; } /** @brief invert selection state of all items in active pane @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if there is any item in the pane */ static gboolean _e2_pane_current_invert_all (gpointer from, E2_ActionRuntime *art) { GtkTreeIter iter; GtkTreeModel *mdl = curr_view->model; if (gtk_tree_model_get_iter_first (mdl, &iter)) { GtkTreeSelection *sel = curr_view->selection; do { if (gtk_tree_selection_iter_is_selected (sel, &iter)) gtk_tree_selection_unselect_iter (sel, &iter); else gtk_tree_selection_select_iter (sel, &iter); } while (gtk_tree_model_iter_next (mdl, &iter)); #ifdef E2_STATUS_DEMAND use a selection-change cb ? BGL management e2_window_update_status_bar (NULL); #endif return TRUE; } return FALSE; } /** @brief invert selection state of focused item in active pane @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if there is any item in the pane */ static gboolean _e2_pane_current_invert_current (gpointer from, E2_ActionRuntime *art) { GtkTreeIter iter; if (gtk_tree_model_iter_nth_child (curr_view->model, &iter, NULL, curr_view->row)) { GtkTreeSelection *sel = curr_view->selection; if (gtk_tree_selection_iter_is_selected (sel, &iter)) gtk_tree_selection_unselect_iter (sel, &iter); else gtk_tree_selection_select_iter (sel, &iter); #ifdef E2_STATUS_DEMAND use a selection-change cb ? BGL management e2_window_update_status_bar (NULL); #endif return TRUE; } return FALSE; } static gboolean _e2_pane_toggle_hidden_ (ViewInfo *view) { view->show_hidden = !view->show_hidden; e2_fileview_refilter_list (view); #ifdef E2_STATUS_DEMAND if (view == curr_view) { use a selection-change cb ? BGL management e2_window_update_status_bar (NULL); } #endif //toggle the button E2_ToggleType num = (view == &app.pane1_view) ? E2_TOGGLE_PANE1HIDDEN : E2_TOGGLE_PANE2HIDDEN; e2_toolbar_button_toggle (toggles_array[num]); return TRUE; } /** @brief toggle display of hidden items in a specified file list Downstream functions require BGL closed @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_pane_toggle_hidden (gpointer from, E2_ActionRuntime *art) { return (_e2_pane_toggle_hidden_ ((ViewInfo *) art->action->data)); } /** @brief toggle display of hidden items in active pane Downstream functions require BGL closed @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_pane_current_toggle_hidden (gpointer from, E2_ActionRuntime *art) { return (_e2_pane_toggle_hidden_ (curr_view)); } /** @brief toggle focus between a dir line and the corresponding file pane @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if the focus was changed */ static gboolean _e2_pane_focus_dirline_action (gpointer from, E2_ActionRuntime *art) { //string with "1" (or anything else) which specifies the dir line to focus gchar *arg = (gchar *) art->data; gint choice = 1; if (arg != NULL) { g_strstrip (arg); if (!g_str_equal (arg, "1")) //CHECKME no translation ?? choice = 2; } E2_PaneRuntime *pane = (choice == 1) ? &app.pane1 : &app.pane2; //find the first-registered dir-line for the pane E2_CommandLineRuntime *cl_rt; GList *list; for (list = app.command_lines; list != NULL ; list = g_list_next (list)) { cl_rt = (E2_CommandLineRuntime *) list->data; if (!cl_rt->original && cl_rt->pane == pane) break; } if (list == NULL) return FALSE; //no dirline found for the pane if (GTK_WIDGET_HAS_FOCUS (GTK_BIN (cl_rt->cl)->child)) gtk_widget_grab_focus (curr_view->treeview); else { gtk_editable_set_position (GTK_EDITABLE (GTK_BIN (cl_rt->cl)->child), -1); gtk_widget_grab_focus (GTK_BIN (cl_rt->cl)->child); } return TRUE; } /** @brief focus active pane file list This is primarily intended for the start of a chained a key-binding @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_pane_current_focus (gpointer from, E2_ActionRuntime *art) { gtk_widget_grab_focus (curr_view->treeview); return TRUE; } /** @brief action to go to top of active pane file list This is primarily intended for a custom key-binding Downstream code expects BGL to be closed @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if there is any item in the pane */ static gboolean _e2_pane_current_focus_top (gpointer from, E2_ActionRuntime *art) { if (gtk_tree_model_iter_n_children (curr_view->model, NULL) > 0) { e2_fileview_focus_row (curr_view, 0, TRUE, TRUE, FALSE, TRUE); return TRUE; } return FALSE; } /** @brief action to go to bottom of active pane file list This is primarily intended for a custom key-binding Downstream code expects BGL to be closed @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if there is any item in the pane */ static gboolean _e2_pane_current_focus_bottom (gpointer from, E2_ActionRuntime *art) { gint n = gtk_tree_model_iter_n_children (curr_view->model, NULL); if (n > 0) { e2_fileview_focus_row (curr_view, n-1, TRUE, TRUE, FALSE, TRUE); return TRUE; } return FALSE; } /** @brief action to sort a visible column in active pane This is primarily intended for a custom key-binding @param from the button, menu item etc which was activated @param art action runtime data @return TRUE always */ static gboolean _e2_pane_sort_active (gpointer from, E2_ActionRuntime *art) { return (e2_fileview_sort_column (GPOINTER_TO_INT (art->action->data), curr_view)); } /******************/ /***** public *****/ /******************/ /** @brief grab pointers to options previously registered @param rt pointer to pane data struct @return */ void e2_pane_create_option_data (E2_PaneRuntime *rt) { /* gchar *option_name = (rt == &app.pane1) ? "panebar1" : "panebar2" ; //do not translate rt->set = e2_option_get (option_name); option_name = g_strconcat (rt->name,"-use-startup-dir",NULL); rt->opt_use_startup = e2_option_get (option_name); g_free (option_name); option_name = g_strconcat(rt->name,"-use-startup-dir-startup-dir",NULL); rt->opt_startup = e2_option_get (option_name); g_free (option_name); //create dependency rt->opt_startup->depends = rt->opt_use_startup->name; */ rt->opt_transparent = e2_option_get ("transparent-dir-links"); } /** @brief create file pane and all its contents This is used only at session-start @param rt pointer to pane data struct @return */ void e2_pane_create (E2_PaneRuntime *rt) { gint num = (rt == &app.pane1) ? 1 : 2; rt->name = g_strdup_printf ("pane%d", num); rt->pane_sw = NULL; e2_pane_create_option_data (rt); //waiiting until first-registration of a hook func may be too late g_hook_list_init (&rt->hook_change_dir, sizeof (GHook)); // g_hook_list_init (&rt->view->hook_refresh, sizeof (GHook)); #ifdef E2_VFSTMP //CHECKME path when non-mounted dir cached at session end #endif e2_cache_str_register (rt->name, &rt->path, G_DIR_SEPARATOR_S); gchar *freeme = NULL; //do we need to over-ride the cached startup dir by a runtime option ? gchar *startdir = (num == 1) ? e2_cl_options.pane1_path : e2_cl_options.pane2_path ; if (startdir != NULL) freeme = startdir = F_FILENAME_FROM_LOCALE (startdir); //don't worry about trailer #ifdef E2_VFSTMP //CHECKME startup dir always local? set other paths accordingly #endif else //or by a config option ? { gchar *option_name = g_strconcat (rt->name,"-use-startup-dir", NULL); if (e2_option_bool_get (option_name)) { g_free (option_name); option_name = g_strconcat (rt->name,"-use-startup-dir-startup-dir", NULL); startdir = e2_option_str_get (option_name); // freeme = startdir = F_FILENAME_FROM_LOCALE (startdir); } g_free (option_name); } if (startdir != NULL) { g_free (rt->path); rt->path = g_strdup (startdir); } if (freeme != NULL) F_FREE (freeme); E2_BarType thisbar = (num == 1) ? E2_BAR_PANE1 : E2_BAR_PANE2; e2_toolbar_initialise (thisbar); /* no need to "go back" to places visited in a prior session //CHECKME do this again after post-config window recreation ? gchar *history_name = g_strconcat (rt->name, "-history", NULL); e2_cache_list_register (history_name, &rt->history); if (rt->history != NULL) //if history list found in cache rt->opendir_cur = 0; //g_list_length (rt->history); g_free (history_name); */ //register pane-specific actions (all name strings freed in action creation function) gchar *pstr = (num == 1) ? _A(11) : _A(12); gchar *action_name = g_strconcat (pstr,".",_A(23), NULL); e2_action_register (action_name, E2_ACTION_TYPE_COMMAND_LINE, e2_command_line_cd_activated_cb, "dir line", TRUE, //name not translated E2_ACTION_EXCLUDE_MENU | E2_ACTION_EXCLUDE_ACCEL, rt); action_name = g_strconcat (pstr,".",_A(42), NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_focus_action, rt, FALSE); action_name = g_strconcat (pstr,".",_A(46), NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_go_forward, rt, FALSE); action_name = g_strconcat (pstr,".",_A(45), NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_go_back, rt, FALSE); action_name = g_strconcat (pstr,".",_A(47), NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_go_up, rt, FALSE); action_name = g_strconcat (pstr,".",_A(60), NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_goto_choice, rt, FALSE); action_name = g_strconcat (pstr,".",_A(55),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_mirror, rt, FALSE); action_name = g_strdup ((num == 1) ? toggles_array [E2_TOGGLE_PANE1HIDDEN] : toggles_array [E2_TOGGLE_PANE2HIDDEN]); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_toggle_hidden, rt->view, FALSE); action_name = g_strconcat (pstr,".",_A(95),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_select_all, rt, FALSE); action_name = g_strconcat (pstr,".",_A(21), NULL); e2_action_register (action_name, E2_ACTION_TYPE_BOOKMARKS, e2_bookmark_open, GINT_TO_POINTER (num), TRUE, E2_ACTION_EXCLUDE_ACCEL | E2_ACTION_EXCLUDE_MENU, NULL); action_name = g_strdup ((num == 1) ? toggles_array [E2_TOGGLE_PANE1FILTERS] : toggles_array [E2_TOGGLE_PANE2FILTERS]); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_filter_menu_create, rt, FALSE); #ifdef E2_VFS action_name = g_strconcat (pstr,".",_A(117),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_select_space, rt, TRUE); action_name = g_strdup ((num == 1) ? toggles_array [E2_TOGGLE_PANE1SPACE] : toggles_array [E2_TOGGLE_PANE2SPACE]); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_vfs_menu_show, rt, FALSE); #endif //after the actions are in place, fill in the pane contents e2_pane_create_part (rt); } /** @brief part of pane creation, used also in re-creation @param rt pointer to pane data struct @return */ void e2_pane_create_part (E2_PaneRuntime *rt) { //existing hooklists are cleared if appropriate in e2_window_recreate() rt->pane_sw = e2_fileview_create_list (rt->view); rt->outer_box = gtk_hbox_new (FALSE, 0); rt->inner_box = gtk_vbox_new (FALSE, 0); gtk_box_pack_start_defaults (GTK_BOX (rt->inner_box), rt->pane_sw); gtk_box_pack_start_defaults (GTK_BOX (rt->outer_box), rt->inner_box); gtk_widget_show (rt->inner_box); gtk_widget_show (rt->outer_box); e2_toolbar_create (&rt->toolbar); if (e2_option_bool_get_direct (rt->toolbar.show)) { //show full/split window button according to cached pane ratio if (rt == &app.pane1 && app.window.panes_paned_ratio > 0.999) e2_toolbar_toggle_button_set_state (toggles_array [E2_TOGGLE_PANE1FULL], TRUE); else if (rt == &app.pane2 && app.window.panes_paned_ratio < 0.001) e2_toolbar_toggle_button_set_state (toggles_array [E2_TOGGLE_PANE2FULL], TRUE); //show (un)hide toggle buttons which match corresponding cached state E2_ToggleType num = (rt == &app.pane1) ? E2_TOGGLE_PANE1HIDDEN : E2_TOGGLE_PANE2HIDDEN; e2_toolbar_toggle_button_set_state (toggles_array [num], rt->view->show_hidden); num = (rt == &app.pane1) ? E2_TOGGLE_PANE1FILTERS : E2_TOGGLE_PANE2FILTERS; gboolean filtered = ( rt->view->name_filter.active || rt->view->size_filter.active || rt->view->date_filter.active); //this needs to be inverted to show correct button e2_toolbar_toggle_button_set_state (toggles_array [num], !filtered); //set initial filter tooltip if (filtered) e2_toolbar_toggle_filter_button (rt->view); #ifdef E2_VFSTMP //FIXME gchar *tip = "faketip"; e2_toolbar_update_vfs_toggle_button (rt->view, tip); #endif } } /* * @brief destroy pane data @param rt pointer to pane data struct @return */ /*static void _e2_pane_destroy (E2_PaneRuntime *rt) { gchar *history_name = g_strconcat (rt->name, "-history", NULL); e2_cache_unregister (history_name); e2_list_free_with_data (&); g_free (history_name); // e2_toolbar_destroy (&rt->toolbar); //pane toolbar may be outside rt->outer_box, so destroy it independently gtk_widget_destroy (rt->toolbar.toolbar_container); // gtk_widget_destroy (rt->pane_sw); gtk_widget_destroy (rt->outer_box); g_hook_list_clear (&rt->hook_change_dir); }*/ /* * @brief destroy and re-create specified pane @param rt pointer to pane data struct @return */ /*void e2_pane_recreate (E2_PaneRuntime *rt) { _e2_pane_destroy (rt); e2_pane_create_part (rt); }*/ /** @brief register pane-related actions that don't have a pane-specifc action Actions with pane-specific function are registered in e2_pane_create() @return */ void e2_pane_actions_register (void) { //these strings must be freeable gchar *action_name = g_strconcat(_A(10),".",_A(46),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_go_forward, NULL, FALSE); action_name = g_strconcat(_A(10),".",_A(45),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_go_back, NULL, FALSE); action_name = g_strconcat(_A(10),".",_A(47),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_go_up, NULL, FALSE); action_name = g_strconcat(_A(13),".",_A(60),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_goto_choice, NULL, FALSE); action_name = g_strconcat(_A(6),".",_A(97),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_goto_trash, NULL, FALSE); action_name = g_strconcat(_A(10),".",_A(80),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_current_toggle_hidden, NULL, FALSE); action_name = g_strconcat(_A(10),".",_A(54),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_current_invert_all, NULL, FALSE); action_name = g_strconcat(_A(10),".",_A(96),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_current_invert_current, NULL, FALSE); action_name = g_strconcat(_A(10),".",_A(95),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_current_select_all, NULL, FALSE); action_name = g_strconcat(_A(10),".",_A(81),NULL); e2_action_register (action_name, E2_ACTION_TYPE_ITEM, e2_context_menu_show_menu_action, NULL, TRUE, E2_ACTION_EXCLUDE_MENU | E2_ACTION_EXCLUDE_TOOLBAR, NULL); #ifdef E2_TREEDIALOG action_name = g_strconcat(_A(10),".",_A(99),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, e2_tree_dialog_show_action, NULL, FALSE); #endif action_name = g_strconcat(_A(4),".",_A(43),NULL); e2_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_focus_dirline_action, NULL, TRUE, E2_ACTION_EXCLUDE_MENU | E2_ACTION_EXCLUDE_TOOLBAR, NULL); action_name = g_strconcat(_A(10),".",_A(42),NULL); e2_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_current_focus, NULL, FALSE, E2_ACTION_EXCLUDE_MENU | E2_ACTION_EXCLUDE_TOOLBAR, NULL); action_name = g_strconcat(_A(10),".",_A(49),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_current_focus_top, NULL, FALSE); action_name = g_strconcat(_A(10),".",_A(48),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_current_focus_bottom, NULL, FALSE); action_name = g_strconcat(_A(10),".",_A(22),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_filters_show, NULL, FALSE); //CHECKME which exclusions for these sort-actions ? action_name = g_strconcat(_A(10),".",_A(86),NULL); e2_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_sort_active, GINT_TO_POINTER (FILENAME), FALSE, 0, NULL); action_name = g_strconcat(_A(10),".",_A(88),NULL); e2_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_sort_active, GINT_TO_POINTER (SIZE), FALSE, 0, NULL); action_name = g_strconcat(_A(10),".",_A(87),NULL); e2_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_sort_active, GINT_TO_POINTER (PERM), FALSE, 0, NULL); action_name = g_strconcat(_A(10),".",_A(89),NULL); e2_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_sort_active, GINT_TO_POINTER (OWNER), FALSE, 0, NULL); action_name = g_strconcat(_A(10),".",_A(84),NULL); e2_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_sort_active, GINT_TO_POINTER (GROUP), FALSE, 0, NULL); action_name = g_strconcat(_A(10),".",_A(85),NULL); e2_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_sort_active, GINT_TO_POINTER (MODIFIED), FALSE, 0, NULL); action_name = g_strconcat(_A(10),".",_A(82),NULL); e2_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_sort_active, GINT_TO_POINTER (ACCESSED), FALSE, 0, NULL); action_name = g_strconcat(_A(10),".",_A(83),NULL); e2_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_sort_active, GINT_TO_POINTER (CHANGED), FALSE, 0, NULL); #ifdef E2_VFS action_name = g_strconcat (_A(10),".",_A(117),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_pane_select_space, NULL, TRUE); #endif } /** @brief register pane-related config options Panebar default contents are set here, too @param num integer pane number (1 or 2) @return */ void e2_pane_options_register (gint num) { gint i; gchar *option_name, *desc, *tip, *grey_me; E2_OptionFlags flags; desc = (num == 1) ? _A(11) : _A(12); // group_name = g_strconcat (parent, ".", _C(3), ":", desc, NULL); // _("panes.columns:pane N" gchar *group_name = g_strconcat ((num == 1) ? _C(28) : _C(30), ":", _C(3), NULL); // _("paneN:columns" for (i = 0; i < MAX_COLUMNS; i++) { option_name = g_strdup_printf ("pane%d-show-column%d", num, i); desc = g_strdup_printf (_("show %s column"), gettext(e2_all_columns[i].title)); tip = g_strdup_printf (_("This causes the the named column to be displayed in pane %d (duh ...)"), num); flags = E2_OPTION_FLAG_BASIC | E2_OPTION_FLAG_FREENAME | E2_OPTION_FLAG_FREEDESC | E2_OPTION_FLAG_FREETIP | E2_OPTION_FLAG_BUILDPANES; if (i > 0) grey_me = (num == 1) ? "!pane1-uses-other" : "!pane2-uses-other"; else { flags |= E2_OPTION_FLAG_FREEGROUP; //disable hiding of col 0 = filename grey_me = g_strconcat ("!",option_name,NULL); //a 'depends' entry = ! disables any change } e2_option_bool_register (option_name, group_name, desc, tip, grey_me, TRUE, flags); } //this needs to be after at least one non-tree options, so that the //first-registered option is non-tree and dialog scrolling is setup properly e2_toolbar_panebar_register (num); //setup the pane bar if (num == 1) e2_toolbar_options_register (E2_BAR_PANE1); //set general toolbar options else e2_toolbar_options_register (E2_BAR_PANE2); //hack to make general options show after the panes, in basic mode if (num == 2) { for (num = 1; num < 3; num++) { option_name = g_strdup_printf ("pane%d-use-startup-dir", num); gchar *dep = g_strdup (option_name); group_name = g_strconcat (_C(17), ":", _C(36), NULL); // _("general:startup" desc = g_strdup_printf (_("At startup, show a specific directory in pane %d"), num); e2_option_bool_register (option_name, group_name, desc, _("This causes the directory named below, instead of the last-opened directory, to be shown at session start"), NULL, FALSE, E2_OPTION_FLAG_BASIC | E2_OPTION_FLAG_FREEGROUP | E2_OPTION_FLAG_FREENAME | E2_OPTION_FLAG_FREEDESC ); option_name = g_strconcat (option_name,"-startup-dir", NULL); desc = g_strdup_printf (_("pane %d startup directory:"), num); e2_option_str_register (option_name, group_name, desc, _("This is the directory to show in this pane, at session start"), dep, (num == 1) ? "~" : G_DIR_SEPARATOR_S, //pane1 default = home, pane2 default = root directory E2_OPTION_FLAG_BASIC | E2_OPTION_FLAG_FREENAME | E2_OPTION_FLAG_FREEDESC | E2_OPTION_FLAG_FREEDEPENDS); } } } emelfm2-0.4.1/src/e2_keybinding.c0000600000175000017500000012267611007771043015471 0ustar cairocairo/* $Id: e2_keybinding.c 870 2008-05-06 05:52:35Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelfm2. emelfm2 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 3, or (at your option) any later version. emelfm2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/e2_keybinding.c @brief functions for handling key bindings */ /** \page bindings key bindings The primary data for key-bindings are stored in a treestore established at session-start. Its hierarchy currently provides for 2 levels of "category" (more levels can be defined at compile-time). One or more categories can be bound to any widget. Any level can be bound to the widget. Any key can be assigned to any level. Keys assigned to a category apply to all widgets in that category, and to all widgets in any descendant category. Keys assiged to a lowest-level category apply to widgets to which that category only are bound. After the last level of categories in any tree branch, the next level of the hierarchy has key names and related data. Any such key may have child(ren), grand-child(ren) etc. Descendant-keys are treated as chained, i.e key-sequences can be created. Any key in a sequence, or more than one of them (i.e. not just the last one) can have its own action. Categories can be added to the keys treestore at any time. If E2_TREEINCREMENT is defined, they can be added from strings like "1.2.3", without any keys. A category must, and its keys may, be added to the treestore before the category is bound to any widget. When a category is first bound to any widget, the keys in the category (if any) are prepared for use - see below. Keys can be added to a category later, provided they are specifically converted to runtime format. Treestore data are converted to a list (keys) for ready access when used. Each entry in keys corresponds to a binding category. Among other things, the entry has a list of bound widgets, and a list of key-data structs. The latter each has specific key data and the bound command. A category can be "registered" (converted to runtime list) at any time, and any prior rt data for the category will then be destroyed. Category data for "core" widgets are destroyed and rebuilt as appropriate during a config dialog. As of now, there are no "transient" bindings. They probably don't need to be rebuilt, anyhow. Category names must be set at compile time, as the relevant widgets need to know the names to which to bind. In theory at least, category names could be made configurable. If so, it would be reasonable to also allow creation and deletion of categories in a config dialog. Binding a category to a widget means that a key-press on the widget will callback to scan the keys in the bound group, initiate or continue a chaining process if appropriate, or otherwise act on the key if matched. Look for code tagged with #ifdef E2_TRANSIENTKEYS, and in particular a dummy set of bindings and their initialization and cancellation, in e2_view_dialog.c Keypresses may be 'aliased' by binding them to the action 'key.alias'. The argument for that action must be a string containing one or more key-names in a form gtk can parse, like m, or just a letter. If more than one key is in the replacement string, they must be separated by a " " char (the name of a substituted space is ) */ /** \page bindings_ui key bindings interface ToDo - describe how this works */ #include "e2_keybinding.h" #include "e2_option_tree.h" #include //enable data conversion at idle-time, instead of when a binding is registered //don't do that, it interferes with config dialog edit-cancellation #define E2_IDLE_KEYSYNC //keybindings is a list of E2_KeybindingRuntime structs, each for a 'category' of bindings GList *keybindings = NULL; #ifdef E2_IDLE_KEYSYNC //keys is a pointer for use with the keybindings GList //GList *binding_cur = NULL; #endif /*****************/ /***** utils *****/ /*****************/ /** @brief cleanup bound-keys data in list @a keys Recurses to clean chained keys, if any @param keys pointer to a bound-keys list @return */ static void _e2_keybinding_free_keyslist (GList *usedkeys) { GList *list; for (list = usedkeys; list != NULL; list = list->next) { E2_KeyRuntime *k = list->data; if (k != NULL) { if (k->chained != NULL) _e2_keybinding_free_keyslist (k->chained); //strings can't be NULL g_free (k->action); g_free (k->action_data); DEALLOCATE (E2_KeyRuntime, k); } } g_list_free (usedkeys); } /** @brief cleanup data for the bound keys for the category associated with @a rt @param rt pointer to keybinding category data struct @return */ static void _e2_keybinding_free_keys (E2_KeybindingRuntime *rt) { if (rt->keys != NULL) { _e2_keybinding_free_keyslist (rt->keys); rt->keys = NULL; } } /** @brief recursively translate a keybinding treestore branch into a listed struct Some cute stuff is done to conform keys-cell-renderer behaviour with gtk's @param model model for the bindings treestore @param iter pointer to iter to be used to interrogage @a model @param keys pointer to list to which the created struct(s) will be appended @return */ static void _e2_keybinding_sync_one (GtkTreeModel *model, GtkTreeIter *iter, GList **keys) { gchar *cat, *key, *action, *action_data; gboolean cont; gtk_tree_model_get (model, iter, 0, &cat, 1, &key, 2, &cont, 3 , &action, 4, &action_data, -1); gboolean valid = (*cat == '\0'//for a key row, the category should be empty && *key != '\0'); if (valid) { E2_Action *act = e2_action_check (action); valid = (act == NULL || !(act->exclude & E2_ACTION_EXCLUDE_ACCEL)); } if (!valid) { g_free (cat); g_free (key); g_free (action); g_free (action_data); return; } GdkModifierType mask; guint keyval; //this converts ucase to lcase, when there is no modifier gtk_accelerator_parse (key, &keyval, &mask); if (keyval != 0) { /*for letters, when a is pressed, or after , gtk actually returns the upper-case code, = lower-case - 0x20 if , there will also be GDK_SHIFT_MASK but e2's config widget always provides the lower-case pair FIXED we need to work around this mismatch */ // printd (DEBUG, "key - %s - %d - %d", key, keyval, mask); // if (keyval >= GDK_a && keyval <= GDK_z && mask == GDK_SHIFT_MASK) // keyval -= 0x20; if (mask == 0 && (keyval < 0xF000 || keyval > 0xFFFF)) { gchar *realkey = gdk_keyval_name (keyval); if (!g_str_equal (key, realkey)) keyval = gdk_keyval_to_upper (keyval); } E2_KeyRuntime *k = ALLOCATE (E2_KeyRuntime); CHECKALLOCATEDWARN (k, ) if (k == NULL) { g_free (cat); g_free (key); return; } k->keyval = keyval; k->mask = mask; k->action = action; //may be "" k->action_data = action_data; //may be "" k->cont = cont; k->chained = NULL; GtkTreeIter child_iter; if (gtk_tree_model_iter_children (model, &child_iter, iter)) { do { _e2_keybinding_sync_one (model, &child_iter, &k->chained); } while (gtk_tree_model_iter_next (model, &child_iter)); } *keys = g_list_append (*keys, k); } else { g_free (action); g_free (action_data); } g_free (cat); g_free (key); return; } /** @brief translate, from primary treestore to runtime glist, the data for a key-binding category This can be called at any time, including when there are no keys for the category in the treestore. Pre-existing data for the category are cleared, so this cannot be used to _add_ keys to the group Keys can be nested to any level i.e key-chains can be as long as desired @param rt pointer to data struct for the keybinding category to be processed @return if E2_IDLE_KEYSYNC applies, TRUE if the process completed properly */ static #ifdef E2_IDLE_KEYSYNC gboolean #else void #endif e2_keybinding_sync (E2_KeybindingRuntime *rt) { if (!gtk_tree_row_reference_valid (rt->ref)) { printd (WARN, "internal keybinding error, row reference not valid anymore"); #ifdef E2_IDLE_KEYSYNC return FALSE; #else return; #endif } GtkTreePath *path = gtk_tree_row_reference_get_path (rt->ref); if (path == NULL) { printd (WARN, "internal keybinding error, path is NULL"); #ifdef E2_IDLE_KEYSYNC return FALSE; #else return; #endif } printd (DEBUG, "%s e2_keybinding_sync", rt->name); _e2_keybinding_free_keys (rt); //get rid of any old key data for this category E2_OptionSet *opt_keys = e2_option_get ("keybindings"); GtkTreeIter iter; gtk_tree_path_down (path); //down to the keys level while (gtk_tree_model_get_iter (opt_keys->ex.tree.model, &iter, path)) { //convert current key and all descendants to runtime form _e2_keybinding_sync_one (opt_keys->ex.tree.model, &iter, &rt->keys); gtk_tree_path_next (path); } gtk_tree_path_free (path); #ifdef E2_IDLE_KEYSYNC // rt->synced = TRUE; return TRUE; #else return; #endif } /** @brief check if @a name is a registered keybinding category @param name the name of the binding category to search for @return list member that matches @a name, or NULL if no match */ static GList *_e2_keybinding_find_by_name (const gchar *name) { GList *member; for (member = keybindings; member != NULL; member = member->next) { if (g_str_equal (name, ((E2_KeybindingRuntime *) member->data)->name)) break; } return member; } /*********************/ /***** callbacks *****/ /*********************/ /** @brief (de)sensitize option tree buttons for selected option tree row Config dialog page buttons are de-sensitized if the row is a category. @param selection pointer to selection @param model @param path @param path_currently_selected @param set data struct for the keybings option @return TRUE always (the row is always selectable) */ static gboolean _e2_keybinding_tree_selection_check_cb (GtkTreeSelection *selection, GtkTreeModel *model, GtkTreePath *path, gboolean path_currently_selected, E2_OptionSet *set) { GtkTreeIter iter; if (gtk_tree_model_get_iter (set->ex.tree.model, &iter, path)) { gchar *cat; gtk_tree_model_get (set->ex.tree.model, &iter, 0, &cat, -1); gboolean retval = (cat[0] == '\0'); g_free (cat); GtkTreeView *view = gtk_tree_selection_get_tree_view (selection); e2_option_tree_adjust_buttons (view, retval); } return TRUE; } /** @brief ensure that cells in the same row as any category name are hidden Checks whether @a iter in @a model has an empty string in column 0. If not (i.e. it is a category row), the cell content is hidden, @param model treemodel for the keybindings option tree @param iter pointer to iter with data for the current model row @param cell cell renderer UNUSED @param data pointer to data UNUSED @return TRUE (cell is visible) if row is not a category name */ static gboolean _e2_keybinding_visible_check_cb (GtkTreeModel *model, GtkTreeIter *iter, GtkCellRenderer *cell, gpointer data) { gchar *cat; gtk_tree_model_get (model, iter, 0, &cat, -1); gboolean retval = (cat[0] == '\0'); g_free (cat); return retval; } /** @brief decide whether tree row is draggable Checks whether column 0 of the current row has a null string ie the row is not a category. If so, the row is draggable @param drag_source GtkTreeDragSource data struct @param path tree path to a row on which user is initiating a drag @return TRUE if the row can be dragged */ static gboolean _e2_keybinding_tree_draggable_check_cb (GtkTreeDragSource *drag_source, GtkTreePath *path) { if (!GTK_IS_TREE_MODEL (drag_source)) return TRUE; GtkTreeModel *model = GTK_TREE_MODEL (drag_source); GtkTreeIter iter; if (gtk_tree_model_get_iter (model, &iter, path)) { gchar *cat; gtk_tree_model_get (model, &iter, 0, &cat, -1); gboolean retval = (cat[0] == '\0'); g_free (cat); return retval; } return TRUE; } /** @brief cancel the chained keys specified in category binding detailed by @a rt This func is a mainloop idle callbacK @param rt pointer to keybinding data struct @return FALSE always to cancel the timer */ static gboolean _e2_keybinding_remove_chained_idle_cb (E2_KeybindingRuntime *rt) { rt->curr_chain = NULL; rt->timeout_id = 0; return FALSE; } /** @brief find and run the action that's bound to a key @param widget the widget to which the binding was bound @param event data struct for the event @param rt pointer to keybinding data struct @return TRUE (i.e. prevent other handlers) if any key has been acted upon */ static gboolean _e2_keybinding_key_press_cb (GtkWidget *widget, GdkEventKey *event, E2_KeybindingRuntime *rt) { #ifdef USE_GTK2_10 if (event->is_modifier) return FALSE; #endif // printd (DEBUG,"key binding cb begins"); gboolean retval = FALSE; guint modifiers = gtk_accelerator_get_default_mod_mask (); modifiers = event->state & modifiers; // printd (DEBUG, "key 1: %d - modifiers: %x", event->keyval, modifiers); /*workaround for matching our storage of -letter with gtk's approach to returning keycodes See comments in _e2_keybinding_sync_one () */ // if (event->state & GDK_LOCK_MASK) // modifiers |= GDK_SHIFT_MASK; //get rid of shift flag if that's the only one if ((event->keyval < 0xF000 || event->keyval > 0xFFFF) && !(modifiers & (GDK_CONTROL_MASK | GDK_MOD1_MASK))) modifiers &= ~(GDK_SHIFT_MASK); GList *list; //if there was a chain from the last key, check if this one is a valid //part of the chain. We need to update a pointer and adjust the timer for (list = rt->curr_chain; list != NULL; list = list->next) { E2_KeyRuntime *k = list->data; if ((k->keyval == event->keyval) && (k->mask == modifiers)) { if (k->chained != NULL) { //there is more chain left rt->curr_chain = k->chained; if (rt->timeout_id != 0) g_source_remove (rt->timeout_id); //FIXME make this timer cancellable at session end rt->timeout_id = g_timeout_add (e2_option_int_get ("keybindings-timeout"), (GSourceFunc) _e2_keybinding_remove_chained_idle_cb, rt); } else { //we've finished the chain if (rt->timeout_id != 0) g_source_remove (rt->timeout_id); _e2_keybinding_remove_chained_idle_cb (rt); } //CHECKME should we run the action before updating the timer ? if (*k->action != '\0') { //there is an action to run e2_action_run_simple_from (k->action, k->action_data, widget); if (!k->cont) { // printd (DEBUG,"key binding cb ends, returned value = TRUE"); return TRUE; } else retval = TRUE; } //keep looking through the chain and then the rest } } //then we check whether the key is in the category bound to the widget for (list = rt->keys; list != NULL; list = g_list_next (list)) { E2_KeyRuntime *k = list->data; if ((k->keyval == event->keyval) && (k->mask == modifiers)) { if (k->chained != NULL) { //setup for chaining rt->curr_chain = k->chained; if (rt->timeout_id != 0) g_source_remove (rt->timeout_id); //FIXME make this timer cancellable at session end rt->timeout_id = g_timeout_add (e2_option_int_get ("keybindings-timeout"), (GSourceFunc) _e2_keybinding_remove_chained_idle_cb, rt); } if (*k->action != '\0') { e2_action_run_simple_from (k->action, k->action_data, widget); retval = TRUE; if (!k->cont) break; } } } // printd (DEBUG,"key binding cb ends, returned value = %s", (retval) ? "TRUE":"FALSE"); return retval; } #ifdef E2_IDLE_KEYSYNC /** @brief convert data for one or all binding category(ies) from treestore to runtime list (if any category remains to be processed) This func is mainloop idle callback CHECKME why sync that way? @param data pointer to binding to be processed, or NULL to do whole list @return FALSE (so the idle is cancelled) */ static gboolean _e2_keybinding_idle_sync_cb (E2_KeybindingRuntime *data) { if (data != NULL) //sync a single KeybindingRuntime e2_keybinding_sync (data); else { /* if (binding_cur == NULL) //after all bindings (or none) have been synced ... binding_cur = keybindings; //start syncing from beginning of list if (binding_cur != NULL) { //sync a single KeybindingRuntime e2_keybinding_sync ((E2_KeybindingRuntime *) binding_cur->data); //setup pointer for next idle callback binding_cur = binding_cur->next; } */ GList *member; for (member = keybindings; member != NULL; member = member->next) { e2_keybinding_sync ((E2_KeybindingRuntime *) member->data); } } return FALSE; //remove this fn from event sources } /* * @brief setup for converting keybinding data, after the bindindgs treestore is altered @param model UNUSED @param arg1 UNUSED @param arg2 UNUSED @param data UNUSED @return */ /*static void _e2_keybinding_model_changed_cb (GtkTreeModel *model, GtkTreePath *arg1, GtkTreeIter *arg2, gpointer data) { keys_cur = NULL; FIXME new approach timer does one category only g_idle_add ((GSourceFunc) _e2_keybinding_idle_sync_cb, NULL); } */ #endif //def E2_IDLE_KEYSYNC /******************/ /***** public *****/ /******************/ /** @brief re-register all "core" widgets' key bindings @return */ void e2_keybinding_register_all (void) { e2_keybinding_register (_C(17), app.main_window); //_("general" gchar *category = g_strconcat(_C(17),".",_C(32),NULL); //_("general.panes e2_keybinding_register (category, app.pane1_view.treeview); e2_keybinding_register (category, app.pane2_view.treeview); g_free (category); gchar *category2 = g_strconcat (_C(17),".",_C(5),NULL); //_(general.command-line" gchar *category3 = g_strconcat (_C(17),".",_C(12),NULL); //_(general.dir-line" E2_CommandLineRuntime *rt; GList *line; for (line = app.command_lines; line != NULL ; line = g_list_next (line)) { rt = (E2_CommandLineRuntime *) line->data; category = (rt->original) ? category2 : category3 ; e2_keybinding_register (category, GTK_BIN (rt->cl)->child); } g_free (category2); g_free (category3); } /** @brief apply key-binding category @a category to @a widget If @a category doesn't exist in the keybindings treestore, it (any any missing ancestor(s) are added to the store If @a category hasn't been used before, its data are setup for runtime usage @param category name of the keybinding, structured like level1[.level2.level3 ...] @param widget the widget to which the keybinding will be attached @return */ void e2_keybinding_register (gchar *category, GtkWidget *widget) { GList *member = _e2_keybinding_find_by_name (category); E2_KeybindingRuntime *rt; if (member != NULL) { //the category is already used // printd (DEBUG, "register another widget for keybinding category '%s'", category); rt = member->data; if (rt->instances != NULL) { g_signal_connect (G_OBJECT (widget), "key-press-event", G_CALLBACK (_e2_keybinding_key_press_cb), rt); //remember that it's connected to the widget rt->instances = g_slist_append (rt->instances, widget); return; } } //first-time usage of category E2_OptionSet *opt_keys = e2_option_get ("keybindings"); GtkTreeIter iter; if (!e2_tree_find_lowest_iter_from_str (opt_keys->ex.tree.model, 0, category, &iter)) { //unknown category #ifdef E2_TREEINCREMENT //add it to the keybindings store e2_tree_create_lowest_iter_from_str (opt_keys->ex.tree.model, 0, category, &iter); printd (DEBUG, "added unknown keybinding category '%s'", category); #else printd (DEBUG, "ignored unknown keybinding category '%s'", category); return; #endif } printd (DEBUG, "first widget registered for keybinding category '%s'", category); //init runtime object if (member == NULL) { rt = ALLOCATE0 (E2_KeybindingRuntime); //FIXME never deallocated CHECKALLOCATEDWARN (rt, return;) keybindings = g_list_append (keybindings, rt); rt->name = g_strdup (category); } else rt = member->data; GtkTreePath *path = gtk_tree_model_get_path (opt_keys->ex.tree.model, &iter); rt->ref = gtk_tree_row_reference_new (opt_keys->ex.tree.model, path); gtk_tree_path_free (path); if (member == NULL || g_signal_handler_find (widget, G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL, _e2_keybinding_key_press_cb, rt) == 0) g_signal_connect (G_OBJECT (widget), "key-press-event", G_CALLBACK (_e2_keybinding_key_press_cb), rt); else printd (DEBUG, "key-press signal was already connected"); #ifdef E2_IDLE_KEYSYNC //FIXME one timer for all categories gboolean restart = TRUE; for (member = keybindings; member != NULL; member = member->next) { E2_KeybindingRuntime *thisrt = (E2_KeybindingRuntime *)member->data; if (g_str_equal (thisrt->name, category) && thisrt->instances != NULL) { restart = FALSE; break; } } #endif //remember what it's connected to rt->instances = g_slist_append (rt->instances, widget); #ifdef E2_IDLE_KEYSYNC if ( keybindings->next == NULL //first-ever registration of any category || restart //first re-registration // || binding_cur == g_list_last (keybindings) //nearly finished syncing ) { g_idle_add ((GSourceFunc) _e2_keybinding_idle_sync_cb, rt); printd (DEBUG, "arranged idle-sync for %s", category); } else printd (DEBUG, "NO idle-sync for %s", category); #else //translate the option tree data to rt form, now //CHECKME handle missing keys in a newly-created category e2_keybinding_sync (rt); #endif } #ifdef E2_TRANSIENTKEYS /** @brief update keybindings data for category @a category This does nothing if @a category is already in the keybindings treestore (from config file or a prior instance of a transient item that uses the category) @a defaults_func must have the same form as _e2_keybinding_tree_defaults () @param category name of binding to be processed @parmm widget the widget to which the category is to be bound @param defaults_func pointer to function which creates a tree-option array @return */ void e2_keybinding_register_transient (gchar *category, GtkWidget *widget, gpointer defaults_func) { printd (DEBUG, "register transient key-binding %s", category); E2_OptionSet *opt_keys = e2_option_get ("keybindings"); GtkTreeIter iter; if (!e2_tree_find_lowest_iter_from_str (opt_keys->ex.tree.model, 0, category, &iter)) { //the category is not already in the store //run the defaults-install function, which in turn calls // e2_option_tree_setup_defaults() which sets up a // pointer array at dummy_set.ex.tree.def void (*install_func) (E2_OptionSet *) = defaults_func; E2_OptionSet dummyset; (*install_func) (&dummyset); gchar **split = (gchar **) dummyset.ex.tree.def; //find a shared ancestor, if any gchar *work = g_strdup (category); gchar *p; while ((p = strrchr (work, '.')) != NULL) { *p = '\0'; if (e2_tree_find_lowest_iter_from_str (opt_keys->ex.tree.model, 0, work, &iter)) break; } GtkTreeIter *parent = (p == NULL) ? NULL : &iter; //adjust start-index of parsed array accordingly gint i = 0; if (p != NULL) { //find the array line which has the lowest segment of the parent p = strrchr (work, '.'); if (p == NULL) p = work; else p++; while (split[i] != NULL && strstr (split[i], p) == NULL) i++; if (split[i] == NULL) i = 0; } g_free (work); //parse the data & add it to the bindings store e2_option_tree_set_from_array (opt_keys->name, &split[i], NULL, parent); //cleanup g_strfreev (split); } //apply the category to the widget e2_keybinding_register (category, widget); } /** @brief cancel binding of @a category to @a widget @param category name of binding to be processed, or NULL for all categories @param widget the widget which is to be unbound, or NULL for all widgets @return */ void e2_keybinding_unregister (gchar *category, GtkWidget *widget) { printd (DEBUG, "e2_keybinding_unregister category: %s, widget: _", category); GList *categories; GSList *widgets; for (categories = keybindings; categories != NULL; categories = categories->next) { E2_KeybindingRuntime *rt = categories->data; if (category == NULL || g_str_equal (rt->name, category)) { for (widgets = rt->instances; widgets != NULL; widgets = widgets->next) { if (widgets->data == widget || widget == NULL) { printd (DEBUG, "UNregister transient key-binding %s", category); /* do this if widget is not to be destroyed gulong handler = g_signal_handler_find (widgets->data, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, _e2_keybinding_key_press_cb, NULL); if (handler > 0) g_signal_handler_disconnect (widgets->data, handler); */ widgets->data = NULL; //don't remove, list pointer cannot change } } } } } #endif //def E2_TRANSIENTKEYS /** @brief cleanup some keybindings runtime data The bindings data list is not affected Key lists are not cleared - that happens when the first widget is re-registered Callbacks are checked before re-connection @return */ void e2_keybinding_clean (void) { GList *member; for (member = keybindings; member != NULL; member = member->next) { E2_KeybindingRuntime *rt = member->data; if (rt->timeout_id != 0) { g_source_remove (rt->timeout_id); rt->timeout_id = 0; } gtk_tree_row_reference_free (rt->ref); // _e2_keybinding_free_keys (rt); //if bound widgets are not being destroyed, //must disconnect signals that have data = rt, before freeing rt // GSList *widgets; /* for (widgets = rt->instances; widgets != NULL; widgets = widgets->next) { if (GTK_IS_WIDGET (widgets->data)) //the bound widget may have been destroyed { gulong handler = g_signal_handler_find (widgets->data, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, _e2_keybinding_key_press_cb, NULL); if (handler > 0) g_signal_handler_disconnect (widgets->data, handler); } } */ g_slist_free (rt->instances); rt->instances = NULL; // g_free (rt->name); // g_free (rt); } // g_list_free (keybindings); #ifdef E2_IDLE_KEYSYNC // binding_cur = NULL; //force restart of bindings sync #endif //#endif } /** @brief display current key bindings, all or a nominated section Expects BGL on/closed @param parameters argument(s) to help command, starting with _(keys) @return */ void e2_keybinding_output_help (gchar *parameters) { //decide which section of the bindings to report //NULL reports all (general) gchar *section = e2_utils_find_whitespace (parameters); if (section != NULL) { section = g_strstrip (section); if (*section == '\0') section = NULL; } gchar *msg; // get treestore iter for start of bindings to report GtkTreeIter iter; E2_OptionSet *opt_keys = e2_option_get ("keybindings"); GtkTreeModel *mdl = opt_keys->ex.tree.model; if (section == NULL) gtk_tree_model_get_iter_first (mdl, &iter); else if (!e2_tree_find_iter_from_str (mdl, 0, section, &iter, FALSE)) { msg = g_strdup_printf (_("Cannot find a key binding named %s"), section); e2_output_print_error (msg, TRUE); return; //revert to showing all // gtk_tree_model_get_iter_first (mdl, &iter); } msg = g_strdup_printf ( "\t "PROGNAME" %s\n" "\t+------------------------+\n", _("key bindings")); e2_output_print (&app.tab, msg, NULL, FALSE, NULL); g_free (msg); gchar *cat, *key, *act, *arg; GtkTreeIter iter2, iter3; // get data from treestore //col 0 = category //col 1 = key //col 3 = action //col 4 = argument GString *str = g_string_new (""); do { if (gtk_tree_model_iter_children (mdl, &iter2, &iter)) { do { gtk_tree_model_get (mdl, &iter2, 0, &cat, 1, &key, 3, &act, 4, &arg, -1); if (*cat != '\0') { g_string_append_printf (str, "\t%s\n", cat); g_free (cat); if (gtk_tree_model_iter_children (mdl, &iter3, &iter2)) { do { gtk_tree_model_get (mdl, &iter3, 1, &key, 3, &act, 4, &arg, -1); g_string_append_printf (str, "\t\t%s\t%s\t%s\n", key, act, arg); g_free (key); g_free (act); g_free (arg); } while (gtk_tree_model_iter_next (mdl, &iter3)); } } else { g_string_append_printf (str, "\t%s\t%s\t%s\n", key, act, arg); g_free (cat); g_free (key); g_free (act); g_free (arg); } } while (gtk_tree_model_iter_next (mdl, &iter2)); } } while (gtk_tree_model_iter_next (mdl, &iter) && section == NULL); e2_output_print (&app.tab, str->str, NULL, FALSE, NULL); g_string_free (str, TRUE); } #ifdef E2_KEYALIAS /** @brief get a keypress alias and issue fake gdk event(s) with the substituted key(s) This expects as action-data a string indicating which key(s) are to be issued Each key name in the string must be in a form parsable by gtk e.g. m. Sequential keys must be separated by a space (actual spaces are ) @param from the widget where the action was initiated @param art action runtime data @return TRUE if timer is set up to issue fake event(s) */ static gboolean _e2_keybinding_substitute (gpointer from, E2_ActionRuntime *art) { printd (DEBUG, "_e2_keybinding_substitute from:_ art->data: %s", art->data); // printd (DEBUG, "from pointer at %x", (GtkWidget *)from); // GtkWidget *debug = gtk_get_event_widget (event); //what is this ?? (!= from, which is correct) gchar *s = (gchar *) art->data; if (s == NULL || *s == '\0') return FALSE; //user goofed the config data GdkEvent *event = gtk_get_current_event (); if (event == NULL) return FALSE; //should never happen if (event->type != GDK_KEY_PRESS) { gdk_event_free (event); return FALSE; //should never happen } // gdk_event_free (event); // g_signal_stop_emission_by_name (from, "key-press-event"); GdkEvent *event2 = gdk_event_new (GDK_KEY_PRESS); // printd (DEBUG, "fake event data at %x", event2); event2->any.window = event->any.window; //must be correct // event2->any.send_event = '\1'; //TRUE; //multi-key separator is space char (comma etc might be part of fake sequence) gchar **split = g_strsplit (s, " ", -1); guint fakekey, i, j = g_strv_length (split); GdkModifierType fakestate; for (i = 0; i < j; i++) { if (split[i] == NULL || *split[i] == '\0') continue; /* //handle instances of "\," which are not a separator s = split[i] + strlen (split[i]) - 1; if (*s == '\\' && s > split[i] && *(s-1) != '\\') *s = ','; */ printd (DEBUG, "constructed key name %s", split[i]); gtk_accelerator_parse (split[i], &fakekey, &fakestate); if (fakekey == 0 && fakestate == 0) continue; event2->key.keyval = fakekey; // event2->key.state = event2->key.state & ~GDK_MODIFIER_MASK; // event2->key.state |= fakestate; event2->key.state = fakestate; /* switch (fakestate) { case 0: event2->key.length = strlen (split[i]); event2->key.string = g_strdup (split[i]); break; case GDK_CONTROL_MASK: event2->key.length = 1; gchar fake[2] = { fakekey - 96, 0 }; event2->key.string = g_strdup (fake); break; default: event2->key.length = 0; event2->key.string = g_strdup (""); break; } */ //hardware_keycode must be correct for fake keycode gint n_keys; GdkKeymapKey *keys; if (!gdk_keymap_get_entries_for_keyval (NULL, fakekey, &keys, &n_keys) || n_keys == 0) continue; event2->key.hardware_keycode = (guint16) keys->keycode; g_free (keys); // event2->key.time = event->key.time + i; gtk_propagate_event ((GtkWidget *)from, event2); // gboolean success = FALSE; // g_signal_emit_by_name (G_OBJECT (from), "key-press-event", event2, &success); // printd (DEBUG, "signal handler cb returned %s", (success) ? "TRUE":"FALSE"); //if this is set above - g_free (event2->key.string); //CHECKME corresponding release-event(s) } gdk_event_free (event); //if this is set above - event2->key.string = NULL; //prevent crash when clearing event // gdk_event_free (event2); CHECKME cleaning this causes error(s) when dialog is closed g_strfreev (split); printd (DEBUG, "_e2_keybinding_substitute action ends"); return TRUE; } /** @brief register actions related to keybindings @return */ void e2_keybinding_actions_register (void) { gchar *action_name = g_strconcat(_A(119),".",_A(120),NULL); //_("key.alias" e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_keybinding_substitute, NULL, TRUE); } #endif //def E2_KEYALIAS /** @brief install default tree options for keybindings This function is called only if the default is missing from the config file @param set pointer to set data @return */ static void _e2_keybinding_tree_defaults (E2_OptionSet *set) { //the key name strings are parsed by gtk, and except for simple letters //(which might reasonably change for different languages) no translation //is possible e2_option_tree_setup_defaults (set, g_strdup("keybindings=<"), //internal name g_strconcat(_C(17),"||||",NULL), //_("general" g_strconcat("\t|F1||",_A(13),".",_A(70),"|",NULL), //"refresh" g_strconcat("\t|F1||",_A(7),".",_A(51),"|",NULL), //"list.history g_strconcat("\t|F1||",_A(7),".",_A(66),"|",NULL), //"list.pending g_strconcat("\t|F1||",_A(7),".",_A(28),"|",NULL), //"list.children g_strconcat("\t|F2||",_A(5),".",_A(73),"|",NULL), //"rename" g_strconcat("\t|F3||",_A(5),".",_A(101),"|",NULL), //"view" g_strconcat("\t|F3||",_A(5),".",_A(102),"|",NULL), //"view_again" g_strconcat("\t|F3||",_A(5),".",_A(6),"|",NULL), //"find" (was F4) g_strconcat("\t|F4||",_A(5),".",_A(39),"|",NULL), //"edit" g_strconcat("\t|F4||",_A(5),".",_A(40),"|",NULL), //"edit_again" g_strconcat("\t|F5||",_A(5),".",_A(33),"|",NULL), //"copy" g_strconcat("\t|F5||",_A(5),".",_A(34),"|",NULL), //"copy_as" g_strconcat("\t|F5||",_A(5),".",_A(36),"|",NULL), //"copy_with_time" g_strconcat("\t|F5||",_A(5),".",_A(35),"|",NULL), //"copy_merge" g_strconcat("\t|F6||",_A(5),".",_A(58),"|",NULL), //"move" g_strconcat("\t|F6||",_A(5),".",_A(59),"|",NULL), //"move_as" g_strconcat("\t|F7||",_A(1),".",_A(56),"|",NULL), //"mkdir" g_strconcat("\t|F8||",_A(5),".",_A(97),"|",NULL), //"trash" g_strconcat("\t|F8||",_A(6),".",_A(97),"|",NULL), //"goto trash" modifiers not translated, gtk needs english to parse g_strconcat("\t|F8||",_A(5),".",_A(38),"|",NULL), //"delete" g_strconcat("\t|F9||",_A(5),".",_A(52),"|",NULL), //"file.info" #ifdef E2_TREEDIALOG g_strconcat("\t|F9||",_A(10),".",_A(99),"|",NULL), //"pane_active.tree" popup directories tree dialog #endif g_strconcat("\t|F10||",_A(1),".",_A(69),"|",NULL), //"quit" g_strconcat("\t|F11||",_A(15),".",_A(44),"|",NULL), //"toggle.fullscreen" g_strconcat("\t|F11||",_A(13),".",_A(94),"|",NULL), //"panes.toggle direction" g_strconcat("\t|F11||",_A(9),".",_A(26),"|",NULL), //"output.add g_strconcat("\t|F11||",_A(9),".",_A(38),"|",NULL), //"output.delete g_strconcat("\t|F12||",_A(2),".",_A(32),"|",NULL), //"configure" #ifdef E2_VFS g_strconcat("\t|F12||",_A(10),".",_A(115),"|",NULL), //"pane_active." g_strconcat("\t|F12||",_A(3),".",_A(117),"|",NULL), //"dialog.namespace" #endif // _I( single letters in the following not worth translating ? //conform this help string to the following 'jump-keys' g_strconcat("\t|","g","||",_A(9),".",_A(68),"|",_("Now press one of h,m,d\\n"),NULL), //"print" //these 3 children are the so-called "directory jump keys" g_strconcat("\t\t|","h","||cd|$HOME",NULL), //_A(17) g_strconcat("\t\t|","m","||cd|/mnt",NULL), //_A(17) g_strconcat("\t\t|","d","||cd|$HOME/downloads",NULL), //_A(17) g_strconcat("\t|w","||",_A(9),".",_A(27),"|*,1",NULL), //"adjust ratio" no arg translation g_strconcat("\t|e","||",_A(9),".",_A(27),"|*,0",NULL), //"adjust ratio" no arg translation g_strconcat("\t|z","||",_A(1),".",_A(43),"|",NULL), //"focus toggle" g_strconcat("\t|r","||",_A(13),".",_A(70),"|",NULL), //"refresh" g_strconcat("\t|1","||",_A(4),".",_A(43),"|1",NULL), //"focus toggle" no arg translation g_strconcat("\t|2","||",_A(4),".",_A(43),"|2",NULL), //"focus toggle"no arg translation g_strconcat("\t",_C(32),"||||",NULL), //_("panes" //note "Space" not recognised, must be "space" // g_strconcat("\t\t|space||",_A(10),".",_A(90),"|",NULL), //"switch" something else makes this 'open' the selected item g_strconcat("\t\t|space||",_A(10),".",_A(96),"|",NULL), //"toggle selected" g_strconcat("\t\t|Tab||",_A(10),".",_A(90),"|",NULL), //"switch" g_strconcat("\t\t|Return||",_A(5),".",_A(60),"|",NULL), //"open" g_strconcat("\t\t|Return||",_A(5),".",_A(61),"|",NULL), //"open in other" g_strconcat("\t\t|Left||",_A(10),".",_A(45),"|",NULL), //"go back" g_strconcat("\t\t|Right||",_A(10),".",_A(46),"|",NULL), //"go forward" g_strconcat("\t\t|Up||cd|..",NULL), //_A(17) g_strconcat("\t\t|BackSpace||cd|..",NULL), //_A(17) g_strconcat("\t\t|Delete||",_A(5),".",_A(38),"|",NULL), //"delete" g_strconcat("\t\t|Delete||",_A(1),".",_A(98),"|",NULL), //"trashempty" // g_strconcat("\t\t|Home||cd|$HOME",NULL), //_A(17) g_strconcat("\t\t|a","||",_A(10),".",_A(95),"|",NULL), //"toggle select all" g_strconcat("\t\t|i","||",_A(10),".",_A(54),"|",NULL), //"invert selection" g_strconcat("\t\t|f","||",_A(5),".",_A(6),"|",NULL), //"find" g_strconcat("\t\t|x","||",_A(13),".",_A(93),"|",NULL), //"sync" g_strconcat("\t\t|h","||",_A(10),".",_A(80),"|",NULL), //"toggle hidden" g_strconcat("\t\t|m","||",_A(10),".",_A(21),"|",NULL), //"pane_active" g_strconcat("\t\t|t","||",_A(10),".",_A(22),"|",NULL), //"pane_active" // g_strconcat("\t\t|/||",_A(5),".",_A(6),"|",NULL), //"find" g_strconcat("\t\t|Left||",_A(13),".",_A(27),"|","*,1",NULL), //"adjust ratio" no arg translation g_strconcat("\t\t|Right||",_A(13),".",_A(27),"|","*,0",NULL), //"adjust ratio" no arg translation g_strconcat("\t\t|p","||",_A(10),".",_A(81),"|",NULL), //"show menu" g_strconcat("\t\t|p","||",_A(10),".",_A(81),"|",_A(113),NULL), //"show menu, shift" g_strconcat("\t\t|p","||",_A(10),".",_A(81),"|",_A(105),NULL), //"show menu, ctrl" g_strconcat("\t\t|n","||",_A(10),".",_A(86),"|",NULL), //"pane_active.sortname" g_strconcat("\t\t|m","||",_A(10),".",_A(85),"|",NULL), //"pane_active.sortmodified" g_strconcat("\t\t|s","||",_A(10),".",_A(88),"|",NULL), //"pane_active.sortsize" g_strconcat("\t",_C(12),"||||",NULL), //_"directory line" g_strconcat("\t\t|Tab||",_A(1),".",_A(31),"|",_A(106),NULL), //"complete dirs" g_strconcat("\t\t|Tab||",_A(13),".",_A(60),"|",NULL), //"open" g_strconcat("\t\t|Delete||",_A(1),".",_A(29),"|",NULL), //"clear" g_strconcat("\t\t|Delete||",_A(1),".",_A(30),"|",NULL), //"clear_history" g_strconcat("\t\t|Return||",_A(1),".",_A(53),"|",_A(107),NULL), //"insert selection, escape" g_strconcat("\t\t|Return||",_A(1),".",_A(53),"|",NULL), //"insert selection" g_strconcat("\t",_C(5),"||||",NULL), //_("command line" g_strconcat("\t\t|Return||",_A(1),".",_A(77),"|",NULL), //"command.send" g_strconcat("\t\t|Return||",_A(1),".",_A(53),"|",_A(107),NULL), //"insert selection, escape" g_strconcat("\t\t|Return||",_A(1),".",_A(53),"|",_A(112),NULL), //"insert selection, quote" g_strconcat("\t\t|Delete||",_A(1),".",_A(29),"|",NULL), //"clear" g_strconcat("\t\t|Delete||",_A(1),".",_A(30),"|",NULL), //"clear_history" g_strconcat("\t\t|Tab||",_A(1),".",_A(31),"|",NULL), //"complete" g_strconcat("\t\t|Insert||",_A(28),".",_A(24),"|",NULL), //"children" // g_strconcat("\t",_C(27),"||||",NULL), //_("output" //CHECKME these stay as command-line bindings ?? g_strconcat("\t\t|Page_Up||",_A(9),".",_A(65),"|",NULL), //"page up" g_strconcat("\t\t|Page_Down||",_A(9),".",_A(64),"|",NULL), //"page down" g_strconcat("\t\t|Page_Up||",_A(9),".",_A(49),"|",NULL), //"goto top" g_strconcat("\t\t|Page_Down||",_A(9),".",_A(48),"|",NULL), //"goto bottom" g_strconcat("\t\t|Up||",_A(9),".",_A(75),"|",NULL), //"scroll up" g_strconcat("\t\t|Down||",_A(9),".",_A(74),"|",NULL), //"scroll down" g_strdup(">"), NULL); } /** @brief initialize key-related options, and init keys pointer @return */ void e2_keybinding_options_register (void) { //no screen rebuilds needed after any change to these options gchar *group_name = g_strconcat(_C(20),".",_C(22),NULL); E2_OptionSet *set = e2_option_tree_register ("keybindings", group_name, _C(22), //_("keybindings" NULL, _e2_keybinding_tree_selection_check_cb, _e2_keybinding_tree_draggable_check_cb, E2_OPTION_TREE_UP_DOWN | E2_OPTION_TREE_ADD_DEL, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP | E2_OPTION_FLAG_BUILDKEYS); e2_option_tree_add_column (set, _("Category"), E2_OPTION_TREE_TYPE_STR, 0, "", E2_OPTION_TREE_COL_NOT_EDITABLE, NULL, NULL); e2_option_tree_add_column (set, _("Key"), E2_OPTION_TREE_TYPE_KEY, 0, "", 0, _e2_keybinding_visible_check_cb, NULL); e2_option_tree_add_column (set, _("Continue"), E2_OPTION_TREE_TYPE_BOOL, FALSE, "false", //no translation 0, _e2_keybinding_visible_check_cb, NULL); e2_option_tree_add_column (set, _("Action"), E2_OPTION_TREE_TYPE_SEL, 0, "", 0, NULL, GINT_TO_POINTER (E2_ACTION_EXCLUDE_GENERAL | E2_ACTION_EXCLUDE_ACCEL | E2_ACTION_EXCLUDE_LAYOUT)); e2_option_tree_add_column (set, _("Argument"), E2_OPTION_TREE_TYPE_SEL , 0, "", 0, NULL, GINT_TO_POINTER (E2_ACTION_EXCLUDE_GENERAL | E2_ACTION_EXCLUDE_ACCEL | E2_ACTION_EXCLUDE_LAYOUT | E2_ACTION_EXCLUDE_TOGGLE)); e2_option_tree_create_store (set); e2_option_tree_prepare_defaults (set, _e2_keybinding_tree_defaults); group_name = g_strconcat(_C(20),":",_C(25),NULL); //_("interface:miscellaneous" e2_option_int_register ("keybindings-timeout", group_name, _("chained keybindings timeout (ms)"), _("This sets the time limit (in milliseconds) for accepting 'chained' keybindings"), NULL, 2000, 1, 1000000, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP); } emelfm2-0.4.1/src/e2_task_backend.c0000600000175000017500000017501010751445056015754 0ustar cairocairo/* $Id: e2_task_backend.c 799 2008-02-03 23:11:42Z tpgww $ Copyright (C) 2005-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/e2_task_backend.c @brief task backend functions This file contains functions used to handle different file operations like copying, deleting, symlinking, etc. The 'public' functions are all named like e2_task_<...>_backend () They return TRUE on successful completion Private functions are named like _e2_task_backend_<...> () These functions are thread-safe, when E2_TREEWALK is defind */ #include "e2_task.h" #include #include #include "e2_filetype.h" #include "e2_filelist.h" #include "e2_dialog.h" //a (*plugin-func) (const gchar*, const struct stat *, const gchar *), for preserving acl's when copying //some fanciness-hacks used to avoid a mutex lock/unlock each time this func is used //only used when acl plugin is loaded, static variable is ok gpointer chaclfunc; /** @brief parse a symbolic permissions string The string @a tmp1 would be like 'o+x' or 'g-r' etc @param tmp1 permissions string to parse @param setmask ptr to store for bits to be set in the mode flags @param setmask ptr to store for bits to be cleared from the mode flags @return */ static void _e2_task_backend_parse_flags (gchar *tmp1, mode_t *setmask, mode_t *clearmask) { gint shifter; switch (tmp1[0]) { //get the scope case 'g': shifter=3; break; case 'u': shifter=6; break; case 'o': default: shifter=0; break; } gint mask=0; //default switch (tmp1[2]) { //get the mode case 'r': mask=S_IROTH; break; case 'w': mask=S_IWOTH; break; case 'x': mask=S_IXOTH; break; case 's': if (tmp1[0] == 'u') mask=S_IROTH; else if (tmp1[0] == 'g') mask=S_IWOTH; break; case 't': mask=S_IXOTH; shifter = 9; break; default: break; } if (tmp1[1] == '+') *setmask |= mask << shifter; else if (tmp1[1] == '-') *clearmask |= mask << shifter; } /** @brief parse octal or symbolic permissions string @a mode @param mode mode string @param data pointer to data struct @return nothing, the results are stored in global static variables */ static void _e2_task_backend_parse_mode (gchar *mode, E2_ChmodData *data) { data->setmask = 0; data->clearmask = 0; if (strchr (mode, ',') != NULL) //always ascii { //multiple adds or removes gchar **tmp1, **tmp2; tmp1 = tmp2 = g_strsplit (mode, ",", -1); while (*tmp1 != NULL) { _e2_task_backend_parse_flags (*tmp1, &(data->setmask), &(data->clearmask)); tmp1++; } g_strfreev (tmp2); } else if ((strchr (mode, '+') != NULL) || (strchr (mode, '-') != NULL)) //always ascii //single add or remove _e2_task_backend_parse_flags (mode, &(data->setmask), &(data->clearmask)); else { //octal string, 3 or 4 digits, no checking glong tmp = strtol (mode, NULL, 8); data->setmask = (mode_t) tmp; data->clearmask = ALLPERMS; } data->clearmask = ~data->clearmask; } /** @brief set permissions of item @a path to @a mode This is used when changing a single item, or recursively changing items. Possible errors: Name errors - see _e2_task_nftwfunc_delete documentation *ENOENT The named file doesn't exist. *EPERM This process does not have permission to change the access permissions of this file. Only the file's owner (as judged by the effective user ID of the process) or a privileged user can change them. *EROFS The file resides on a read-only file system. *EFTYPE mode has the S_ISVTX ("sticky") bit set, and the named file is not a directory. @param localpath localised string, absolute path of item to be processed @param mode mode_t to be set @return TRUE if succeeds */ static gboolean _e2_task_backend_chmod1 (VPATH *localpath, mode_t mode E2_ERR_ARG()) { E2_ERR_BACKUP (localerr); if (e2_fs_chmod (localpath, mode E2_ERR_SAMEARG()) && E2_ERR_PISNOT (ENOENT)) //CHECKME any other errors to ignore ?? { e2_fs_error_local (_("Cannot change permissions of %s"), localpath E2_ERR_MSGC()); E2_ERR_CLEARBACKUP (localerr); return FALSE; } E2_ERR_CLEARBACKUP (localerr); return TRUE; } /** @brief callback function for recursive directory chmods Tree is being walked breadth-first, not physical. Dirs are made accessible and writable if not already so and it's permitted, dirs are added to a list to be processed after all the tree has been traversed, other items are changed as requested (if possible) Error messages expect BGL open @param localpath absolute path of item to change, localised string @param statptr pointer to struct stat with info about @a localpath @param status code from the walker, indicating what type of report it is @param user_data pointer to user-specified data @return E2TW_CONTINUE if succeeds, others as appropriate */ static E2_TwResult _e2_task_twcb_chmod (VPATH *localpath, const struct stat *statptr, E2_TwStatus status, E2_ChmodData *user_data) { E2_TwResult retval = E2TW_CONTINUE; //default error code = none E2_ERR_DECLARE switch (status) { mode_t mode, newmode; E2_DirEnt *dirfix; GList *member; case E2TW_DP: //dir completed //change/revert dir permissions, cleanup for (member = g_list_last (user_data->dirdata); member != NULL; member = member->prev) { dirfix = member->data; if (dirfix != NULL) { if (g_str_equal (dirfix->path, localpath)) { if (!_e2_task_backend_chmod1 (localpath, dirfix->mode E2_ERR_PTR())) retval = E2TW_FIXME; g_free (dirfix->path); DEALLOCATE (E2_DirEnt, dirfix); user_data->dirdata = g_list_delete_link (user_data->dirdata, member); break; } } // else //should never happen CHECKME ok when walking list ? // user_data->dirdata = g_list_delete_link (user_data->dirdata, member); } break; case E2TW_DRR: //directory now readable retval |= E2TW_DRKEEP; //no permission reversion in walker case E2TW_D: //ensure dir is writable, if we can if (e2_fs_tw_adjust_dirmode (localpath, statptr, (W_OK | X_OK)) == 0) { //failed to set missing W and/or X perm //take a shot at doing the change, anyhow, probably fails mode = ((statptr->st_mode & user_data->clearmask) | user_data->setmask) & ALLPERMS; e2_fs_chmod (localpath, mode E2_ERR_NONE()); //FIXME warn user about failure retval |= E2TW_SKIPSUB; //don't try to do any descendant } else //dir can be processed { //add this dir to list of items to chmod afterwards dirfix = ALLOCATE (E2_DirEnt); CHECKALLOCATEDWARNT (dirfix, retval=E2TW_STOP;break;) dirfix->path = g_strdup (VPSTR(localpath)); dirfix->mode = ((statptr->st_mode & user_data->clearmask) | user_data->setmask) & ALLPERMS; user_data->dirdata = g_list_append (user_data->dirdata, dirfix); } break; case E2TW_DM: //dir not opened (reported upstream) case E2TW_DL: //ditto case E2TW_DNR: //unreadable directory (for which, error is reported upstream) //chmod for this will probably fail, but try anyhow // mode = statptr->st_mode; //ensure dir is writable, if we can //we don't need X permission to handle these newmode = e2_fs_tw_adjust_dirmode (localpath, statptr, W_OK); mode = ((statptr->st_mode & user_data->clearmask) | user_data->setmask) & ALLPERMS; if (newmode == 0) { //take a shot at doing the change, anyhow, probably fails e2_fs_chmod (localpath, mode E2_ERR_NONE()); //FIXME warn user about failure retval = E2TW_FIXME; } else //dir can be processed { if (!_e2_task_backend_chmod1 (localpath, mode E2_ERR_PTR())) retval = E2TW_FIXME; //FIXME warn user about failure } break; case E2TW_F: if (user_data->scope != E2_RECURSE_DIRS) { //change mode now, if we can // mode = (statptr->st_mode & user_data->clearmask) | user_data->setmask; // if (S_ISREG (statptr->st_mode)) // mode &= ALLPERMS; mode = ((statptr->st_mode & user_data->clearmask) | user_data->setmask) & ALLPERMS; if (!_e2_task_backend_chmod1 (localpath, mode E2_ERR_PTR())) retval = E2TW_FIXME; } case E2TW_SL: //no mode changes for links case E2TW_SLN: break; case E2TW_NS: //un-statable item (for which, error is reported upstream) retval = E2TW_FIXME; break; default: retval = E2TW_STOP; break; } #ifdef E2_VFS if (user_data->operr != NULL && *(user_data->operr) == NULL) *(user_data->operr) = E2_ERR_NAME; else E2_ERR_CLEAR #endif if (retval & E2TW_SKIPSUB) user_data->continued_after_problem = TRUE; if (retval & E2TW_FIXME) { user_data->continued_after_problem = TRUE; retval &= ~E2TW_FIXME; //continue after bad item } return retval; } /** @brief set owners of item @a path This is used when changing a single item, or recursively changing items. Possible errors: Name errors - see _e2_task_nftwfunc_delete documentation *EPERM This process lacks permission to make the requested change. Only privileged users or the file's owner can change the file's group. On most file systems, only privileged users can change the file owner; some file systems allow you to change the owner if you are currently the owner. *EROFS The file is on a read-only file system. @param localpath localised string, absolute path of item to be processed @param statbuf pointer to a 'completed' stat struct for @a path @param owner_id owner id to be set @param group_id group id to be set @return TRUE if succeeds */ static gboolean _e2_task_backend_chown1 (VPATH *localpath, struct stat *statbuf, uid_t owner_id, gid_t group_id E2_ERR_ARG()) { gint (*chown_func) () = (S_ISLNK (statbuf->st_mode)) ? e2_fs_lchown : e2_fs_chown; E2_ERR_BACKUP (localerr); if (chown_func (localpath, owner_id, group_id E2_ERR_SAMEARG()) && E2_ERR_PISNOT (ENOENT)) //don't care if the item is not there { e2_fs_error_local (_("Cannot change ownership of %s"), localpath E2_ERR_MSGC()); E2_ERR_CLEARBACKUP (localerr); return FALSE; } E2_ERR_CLEARBACKUP (localerr); return TRUE; } /** @brief helper function for recursive directory chowns The tree is being scanned breadth-first, no link-through. Dirs are made accessible if not already so and it's permitted, dirs are added to a list to be processed after all the tree has been traversed, other items are changed as requested (if possible) Error messages expect BGL open @param localpath absolute path of item to change, localised string @param statptr pointer to struct stat with info about @a localpath @param status code from the walker, indicating what type of report it is @param user_data pointer to user-specified data @return completion code: E2TW_CONTINUE if succeeds, others as appropriate */ static E2_TwResult _e2_task_twcb_chown (VPATH *localpath, const struct stat *statptr, E2_TwStatus status, E2_ChownData *user_data) { E2_TwResult retval = E2TW_CONTINUE; //default error code = none E2_ERR_DECLARE switch (status) { mode_t mode, newmode; E2_DirEnt *dirfix; GList *member; case E2TW_DP: //dir completed //chown and revert dir's permissions, cleanup for (member = g_list_last (user_data->dirdata); member != NULL; member = member->prev) { dirfix = (E2_DirEnt *)member->data; if (dirfix != NULL) { if (g_str_equal (dirfix->path, localpath)) { if (!_e2_task_backend_chown1 (localpath, (struct stat *)statptr, user_data->new_uid, user_data->new_gid E2_ERR_PTR())) retval = E2TW_FIXME; else if (!_e2_task_backend_chmod1 (localpath, dirfix->mode E2_ERR_PTR())) retval = E2TW_FIXME; g_free (dirfix->path); DEALLOCATE (E2_DirEnt, dirfix); user_data->dirdata = g_list_delete_link (user_data->dirdata, member); break; } } // else //should never happen CHECKME ok when walking list ? // user_data->dirdata = g_list_delete_link (user_data->dirdata, member); } break; case E2TW_DRR: //directory now readable case E2TW_D: //ensure dir is writable, if we can if (e2_fs_tw_adjust_dirmode (localpath, statptr, (W_OK | X_OK)) == 0) { //take a shot at doing the change, anyhow, probably fails _e2_task_backend_chown1 (localpath, (struct stat *) statptr, user_data->new_uid, user_data->new_gid E2_ERR_PTR()); //FIXME warn user about failure retval = E2TW_SKIPSUB; //don't try to do any descendant } else //dir can be processed { //add this dir to list of items to chmod afterwards dirfix = ALLOCATE (E2_DirEnt); CHECKALLOCATEDWARNT (dirfix, retval=E2TW_STOP;break;) dirfix->path = g_strdup (VPSTR(localpath)); dirfix->mode = statptr->st_mode & ALLPERMS; //want to restore the original value user_data->dirdata = g_list_append (user_data->dirdata, dirfix); } break; case E2TW_DM: //dir not opened (reported upstream) case E2TW_DL: //ditto case E2TW_DNR: //unreadable directory (for which, error is reported upstream) //touch for this will probably fail, but try anyhow mode = statptr->st_mode; //ensure dir is writable, if we can, don't need X permission newmode = e2_fs_tw_adjust_dirmode (localpath, statptr, W_OK); if (newmode == 0) { //take a shot at doing the change, anyhow, probably fails _e2_task_backend_chown1 (localpath, (struct stat *) statptr, user_data->new_uid, user_data->new_gid E2_ERR_PTR()); //FIXME warn user about failure retval = E2TW_FIXME; } else //dir can be processed { if (!_e2_task_backend_chown1 (localpath, (struct stat *) statptr, user_data->new_uid, user_data->new_gid E2_ERR_PTR())) retval = E2TW_FIXME; //E2TW_STOP; //prefer continue ?? if (newmode != mode) e2_fs_chmod (localpath, mode & ALLPERMS E2_ERR_NONE()); } break; case E2TW_F: case E2TW_SL: //valid and invalid links case E2TW_SLN: //broken links (CHECKME not reported as E2TW_PHYS is used) if (!_e2_task_backend_chown1 (localpath, (struct stat *) statptr, user_data->new_uid, user_data->new_gid E2_ERR_PTR())) retval = E2TW_FIXME; //E2TW_STOP; //prefer continue ?? break; case E2TW_NS: //un-statable item (for which, error is reported upstream) //(note - this is a physical walk, no link-through problem here) retval = E2TW_FIXME; break; default: retval = E2TW_STOP; break; } #ifdef E2_VFS if (user_data->operr != NULL && *(user_data->operr) == NULL) *(user_data->operr) = E2_ERR_NAME; else E2_ERR_CLEAR #endif if (retval & E2TW_SKIPSUB) user_data->continued_after_problem = TRUE; if (retval & E2TW_FIXME) { user_data->continued_after_problem = TRUE; retval &= ~E2TW_FIXME; //continue after bad item } return retval; } /** @brief delete item as part of recursive directory delete This is a callback for the treewalk function The return value does not change after a failure to delete, so that everything possible will be deleted, The ultimate parent-deletion will generate an appropriate error message after any failure Downstream error messasge expects BGL open @param localpath absolute path of item reported by the walker, localised string @param statptr pointer to struct stat with data about @a localpath @param status code from the walker, indicating what type of report it is @param user_data UNUSED NULL pointer unless E2_VFS defined, in which case maybe a GError** @return E2TW_CONTINUE or E2TW_SKIPSUB */ static E2_TwResult _e2_task_twcb_delete (VPATH *localpath, const struct stat *statptr, E2_TwStatus status, gpointer user_data) { E2_TwResult retval = E2TW_CONTINUE; E2_ERR_DECLARE switch (status) { case E2TW_DP: //dir completed if (e2_fs_remove (localpath E2_ERR_PTR())) { e2_fs_error_local (_("Cannot delete %s"), localpath E2_ERR_MSGL()); #ifndef E2_VFS E2_ERR_CLEAR #endif } break; case E2TW_DRR: retval = E2TW_DRKEEP; //no need for walker to revert mode case E2TW_D: //directory if (e2_fs_tw_adjust_dirmode (localpath, statptr, (W_OK | X_OK)) == 0) { //failed to set W and/or X perm, can't process any item in the dir //no DP report after skip, so try to delete the dir, probably fails if (e2_fs_remove (localpath E2_ERR_PTR())) { e2_fs_error_local (_("Cannot delete %s"), localpath E2_ERR_MSGL()); #ifndef E2_VFS E2_ERR_CLEAR #endif } retval |= E2TW_SKIPSUB; } break; /* case E2TW_F: //not directory or link case E2TW_SL: //symbolic link case E2TW_SLN: //symbolic link naming non-existing file try to delete these, fail if not empty etc case E2TW_DL: //dir, not opened due to tree-depth limit (reported upstream) case E2TW_DM: //dir, not opened due to different file system (reported upstream) case E2TW_DNR: //unreadable dir (for which, error is reported upstream) */ default: //don't care if this fails e2_fs_tw_adjust_dirmode (localpath, statptr, W_OK); case E2TW_NS: //un-stattable item (error reported upstream) e2_fs_remove (localpath E2_ERR_PTR()); //no need for error check break; } #ifdef E2_VFS if (user_data != NULL) { // if (exec_flags & E2TW_XERR) // { GError **callerr = (GError **)user_data; if (*callerr == NULL) *callerr = E2_ERR_NAME; else E2_ERR_CLEAR // } } else E2_ERR_CLEAR #endif return retval; } /** @brief delete item @a itempath This is for deleting any existing item that is to be replaced as part of a copy, move, rename, link task. It is called at the start of each of those tasks. When applied to a link, that is NOT traversed, so if it is a link to a directory (which requires a recursive delete) that dir will be untouched (unless/until processed in its own right, anyway). For dirs, errors other than ENOTEMPTY return FALSE. Not-empty dirs trigger a recursive delete, bottom-up, again, no link traverses (dir's in the same tree will be processed at some stage, in their own right) No error processing other than ENOTEMPTY (OK?) Assumes BGL is open @param localpath localised string, relative path of item to delete @return TRUE if succeeds */ //CHECKME always return TRUE so the process tries to continue? static gboolean _e2_task_backend_delete (VPATH *localpath E2_ERR_ARG()) { if (e2_fs_mount_is_mountpoint (localpath)) return FALSE; //decide whether or not item is a dir, not looking through links struct stat statbuf; #ifdef E2_VFS gboolean retval; #endif if (e2_fs_lstat (localpath, &statbuf E2_ERR_SAMEARG())) { #ifdef E2_VFS retval = E2_ERR_PIS (ENOENT); //ok if nothing to delete, otherwise error if (retval) { if (E2_ERR_NAME != NULL && *E2_ERR_NAME != NULL) { g_error_free (*E2_ERR_NAME); *E2_ERR_NAME = NULL; } } return retval; #else return (E2_ERR_PIS (ENOENT)); //ok if nothing to delete, otherwise error #endif } if (S_ISDIR (statbuf.st_mode) && !S_ISLNK (statbuf.st_mode)) { //dir /* //subject to permission, make sure we can traverse the dir //this fails if there is not sufficient write permission to delete the dir's contents if (!(statbuf.st_mode & S_IXUSR)) { mode_t thismode = (statbuf.st_mode | S_IXUSR | S_IWUSR) & ALLPERMS; if (!_e2_task_backend_chmod1 (localpath, thismode E2_ERR_SAMEARG())) return FALSE; } #ifdef E2_VFS if (E2_ERR_NAME != NULL && *E2_ERR_NAME != NULL) { g_error_free (*E2_ERR_NAME); *E2_ERR_NAME = NULL; } #endif //try for simple deletion first if (! e2_fs_rmdir (localpath E2_ERR_SAMEARG())) { return TRUE; } else if (!(E2_ERR_PIS (ENOTEMPTY) || E2_ERR_PIS (EEXIST))) { return FALSE; //error if failed for some reason other than not-empty } #ifdef E2_VFS if (E2_ERR_NAME != NULL && *E2_ERR_NAME != NULL) { g_error_free (*E2_ERR_NAME); *E2_ERR_NAME = NULL; } #endif */ //dir not empty, recursively delete its contents //FIXME which errors to report and/or ignore return (e2_fs_tw (localpath, _e2_task_twcb_delete, #ifdef E2_VFS E2_ERR_NAME #else NULL #endif , -1, //flags for: cb-error, no link follow, depth-first #ifdef E2_VFS E2TW_XERR | #endif E2TW_PHYS E2_ERR_SAMEARG())); } else //not dir //FIXME which errors to report and/or ignore return (!e2_fs_unlink (localpath E2_ERR_SAMEARG())); } /** @brief replace @a replace with @a dest, with error message if not possible @param dest localised path of item to be deleted @param replace localised path of item to be replaced by @a dest @return TRUE if the replacement was completed */ static gboolean _e2_task_backend_overwrite (VPATH *dest, VPATH *replace) { E2_ERR_DECLARE if (!_e2_task_backend_delete (dest E2_ERR_PTR())) { /* gchar *msg = (e2_fs_is_dir3 (dest E2_ERR_NONE())) ? _("Cannot delete part or all of existing %s"): _("Cannot delete existing %s"); e2_fs_error_local (msg, dest E2_ERR_MSGL()); */ e2_fs_error_local (_("Cannot delete existing %s"), dest E2_ERR_MSGL()); E2_ERR_CLEAR return FALSE; } else e2_fs_rename (replace, dest E2_ERR_NONE()); return TRUE; } /** @brief copy the regular file|fifo|socket|device @a src to @a dest If the item is a regular file, this copies the actual file - links are traversed to find the file Prints error message if @a src can't be read, or @a dest can't be created, then terminates @param src localised string, absolute path of item to copy @param dest localised string, absolute path of copy destination @param copyflags enumerator of copy mode @param srcstat pointer to stat struct for @a src @return TRUE if succeeds */ static gboolean _e2_task_backend_filecopy (VPATH *src, VPATH *dest, E2_FileTaskMode copyflags, const struct stat *srcstat) { //work with a temp name only if the dest exists already gchar *temp = e2_task_tempname (VPSTR(dest)); #ifdef E2_VFS VPATH tdata = { temp, dest->spacedata }; #endif printd (DEBUG, "dest path is %s", temp); if (S_ISREG (srcstat->st_mode)) { #ifdef E2_VFS if (!e2_fs_copy_file (src, srcstat, &tdata E2_ERR_NONE())) #else if (!e2_fs_copy_file (src, srcstat, temp E2_ERR_NONE())) #endif { if (temp != VPSTR(dest)) g_free (temp); return FALSE; } } /*the following mimic the 'cp' app. we do not clear the type-flags when creating these, as that would revert the item to a regular file, but we do re-set the permissions explicitly, to circumvent the mode mask */ else if (S_ISBLK (srcstat->st_mode) || S_ISCHR (srcstat->st_mode) || S_ISSOCK (srcstat->st_mode)) //CHECKME mknod() doco says nothing about handling sockets this way { if (mknod (temp, srcstat->st_mode, srcstat->st_rdev)) //CHECKME no need for any vfs treatment { if (temp != VPSTR(dest)) g_free (temp); E2_ERR_DECLARE #ifdef E2_VFS e2_fs_set_error_from_errno (&E2_ERR_NAME); #endif e2_fs_error_local (_("Cannot create special file %s"), dest E2_ERR_MSGL()); E2_ERR_CLEAR return FALSE; } } else if (S_ISFIFO (srcstat->st_mode)) { if (mkfifo (temp, srcstat->st_mode)) //CHECKME no need for any vfs treatment { if (temp != VPSTR(dest)) g_free (temp); E2_ERR_DECLARE #ifdef E2_VFS e2_fs_set_error_from_errno (&E2_ERR_NAME); #endif e2_fs_error_local (_("Cannot create FIFO %s"), dest E2_ERR_MSGL()); E2_ERR_CLEAR return FALSE; } } #ifdef E2_VFS if (temp != VPSTR(dest) && !_e2_task_backend_overwrite (dest, &tdata)) #else if (temp != dest && !_e2_task_backend_overwrite ((gchar *)dest, temp)) #endif { g_free (temp); return FALSE; } if (temp != VPSTR(dest)) g_free (temp); gpointer quickfunc; quickfunc = chaclfunc; //should be atomic, no need for mutex if (quickfunc == NULL) //no acl-change capacity available { if (S_ISREG (srcstat->st_mode)) e2_fs_chmod (dest, srcstat->st_mode & ALLPERMS E2_ERR_NONE()); else e2_fs_chmod (dest, srcstat->st_mode E2_ERR_NONE()); } else { gboolean (*realfunc) (VPATH *, const struct stat *, VPATH *) = quickfunc; if (! realfunc (src, srcstat, dest)) return FALSE; } if (copyflags & E2_FTM_SAMETIME) // && !e2_fs_lstat (src, srcstat E2_ERR_NONE())) { struct utimbuf tb; tb.modtime = srcstat->st_mtime; tb.actime = srcstat->st_atime; // e2_fs_utime (src, &tb E2_ERR_NONE()); //FIXME vfs e2_fs_utime (dest, &tb E2_ERR_NONE()); } return TRUE; } /** @brief 'copy' soft link itself (ie no traverse to link target) This creates a new symlink to the original target (which may be relative). It may be used during a direct or recursive copy. Possible errors: *EEXIST There is already an existing item named newname (meaning, here, some unknown process created that item since we deleted one of that same name !) *EROFS The new link item can't be created on a read-only file system. *ENOSPC The directory or file system cannot be extended to make the new link. *EIO A hardware error occurred while reading or writing data on the disk. Any error messages expect BGL to be open @param src localised string, absolute path of link to copy (a link "name") @param dest localised string, absolute path of new link name (also a link "name") @param copyflags enumerator of copy mode (E2_FTM_SAMETIME may be used) # * @param srcstat pointer to stat struct for @a src, UNUSED unless E2_FTM_SAMETIME is in effect @return TRUE if succeeds */ static gboolean _e2_task_backend_linkcopy1 (VPATH *src, VPATH *dest) { gchar linkto[PATH_MAX]; //+NAME_MAX]; E2_ERR_DECLARE //get target of link to copy gint len = e2_fs_readlink (src, linkto, sizeof (linkto) E2_ERR_PTR()); if (len > 0) { linkto[len] = '\0'; if (e2_option_bool_get ("relative-symlinks")) { gchar *freeme; if (!(linkto[0] == G_DIR_SEPARATOR || g_str_has_prefix (linkto, ".."G_DIR_SEPARATOR_S) || g_str_has_prefix (linkto, "."G_DIR_SEPARATOR_S))) { //the target is not a relative or absolute path freeme = g_path_get_dirname (VPSTR(src)); if (!g_str_equal (freeme, ".")) { gchar *temp = g_build_filename (freeme, linkto, NULL); g_free (freeme); freeme = e2_utils_create_relative_path (temp, VPCSTR(dest)); g_free (temp); g_strlcpy (linkto, freeme, sizeof(linkto)); } g_free (freeme); } else { freeme = e2_utils_create_relative_path (linkto, VPCSTR(dest)); g_strlcpy (linkto, freeme, sizeof(linkto)); g_free (freeme); } } gchar *temp = e2_task_tempname (VPSTR(dest)); #ifdef E2_VFS VPATH sdata = { linkto, src->spacedata }; VPATH tdata = { temp, dest->spacedata }; if (!e2_fs_symlink (&sdata, &tdata E2_ERR_NONE())) #else if (!e2_fs_symlink (linkto, temp E2_ERR_NONE())) #endif { gboolean retval = TRUE; if (temp != VPSTR(dest)) { #ifdef E2_VFS retval = _e2_task_backend_overwrite (dest, &tdata); #else retval = _e2_task_backend_overwrite (dest, temp); #endif g_free (temp); } /*FIXME this does nothing to a symlink, it changes the link target there is no lutime .. if (retval && (copyflags & E2_FTM_SAMETIME)) { struct utimbuf tb; tb.modtime = srcstat->st_mtime; tb.actime = srcstat->st_atime; e2_fs_utime (dest, &tb E2_ERR_NONE()); } */ return retval; } if (temp != VPSTR(dest)) g_free (temp); e2_fs_error_local (_("Cannot create new link to %s"), #ifdef E2_VFS &sdata E2_ERR_MSGL()); #else linkto E2_ERR_MSGL()); #endif E2_ERR_CLEAR return FALSE; } else { e2_fs_error_local (_("Cannot get target info for link %s"), src E2_ERR_MSGL()); E2_ERR_CLEAR return FALSE; } } /** @brief make a directory @a dest during a recursive dir copy task This creates the dir always with o+x,o+w permissions, then if need be stores the path and 'real' mode, for later setting of that real mode. An error message is printed if the task fails for any reason. Possible errors: Name errors - see _e2_task_nftwfunc_delete documentation *EACCES No write permission for the parent directory to which the new directory is to be added. *EEXIST A file named filename already exists (but in this context, it should have been deleted already) *EMLINK The parent directory has too many links (entries). *ENOSPC The file system doesn't have enough room to create the new directory. *EROFS The parent directory of the directory being created is on a read-only file system @param dest localised string, absolute path of dir to create @param mode stat mode flags of the dir being reproduced @return TRUE if succeeds */ static gboolean _e2_task_backend_mkdir1 (VPATH *dest, mode_t mode) { E2_ERR_DECLARE //need to set USR bits for mkdir to accept the change ? mode_t thismode = (mode | S_IXUSR | S_IWUSR) & ALLPERMS; if (e2_fs_mkdir (dest, thismode E2_ERR_PTR())) { e2_fs_error_local (_("Cannot create directory %s"), dest E2_ERR_MSGL()); E2_ERR_CLEAR return FALSE; } return TRUE; } /** @brief check whether is ok to overwrite @a dest @param src absolute path of item to copy, localised string @param dest absolute path of existing item to be overwritten, localised string @param mode pointer to store for flag to update @param retval pointer to store for treewalk exit code to update @return code corresponding to user's choice */ static DialogButtons _e2_task_backend_confirm (gchar *src, gchar *dest, E2_FileTaskMode *mode, E2_TwResult *retval) { e2_filelist_enable_refresh (); //allow updates while we wait gdk_threads_enter (); //no change to task status while waiting DialogButtons choice = e2_dialog_ow_check (src, dest, BOTHALL); gdk_threads_leave (); e2_filelist_disable_refresh (); switch (choice) { case YES_TO_ALL: *mode &= ~E2_FTM_CHECK; case OK: case CANCEL: *retval = E2TW_CONTINUE; break; default: *retval = E2TW_STOP; break; } return choice; } /** @brief callback function for recursive directory copying This is called for each item in the directory to be copied. Treewalk is breadth-first, not physical @param localpath absolute path of item to copy, localised string @param statptr pointer to struct stat with info about @a localpath @param status code from the walker, indicating what type of report it is @param user_data pointer to user-specified data @return completion code: E2TW_CONTINUE if succeeds, others as appropriate */ static E2_TwResult _e2_task_twcb_copy (VPATH *localpath, const struct stat *statptr, E2_TwStatus status, E2_CopyData *user_data) { E2_TwResult retval = E2TW_CONTINUE; mode_t mode = statptr->st_mode; E2_ERR_DECLARE gchar *dest = e2_utils_strcat (user_data->newroot, VPSTR(localpath) + user_data->oldroot_len); #ifdef E2_VFS VPATH ddata = { dest, user_data->destspace }; #endif switch (status) { DialogButtons choice; E2_DirEnt *dirfix; GList *member; case E2TW_DP: //dir completed //revert and set dir permissions, times if needed for (member = g_list_last (user_data->dirdata); member != NULL; member = member->prev) { dirfix = member->data; if (dirfix != NULL) { if (g_str_equal (dirfix->path, localpath)) { gpointer quickfunc; quickfunc = chaclfunc; //should be atomic, no need for mutex if (chaclfunc == NULL) //no acl-change capacity available { if ((statptr->st_mode & ALLPERMS) != dirfix->mode && !_e2_task_backend_chmod1 (localpath, dirfix->mode E2_ERR_PTR())) retval = E2TW_FIXME; //CHECKME - want cleanup of copied file to continue } else { gboolean (*realfunc) (VPATH *, const struct stat *, VPATH *) = quickfunc; #ifdef E2_VFS if (! realfunc (localpath, statptr, &ddata)) #else if (! realfunc (localpath, statptr, dest)) #endif retval = E2TW_FIXME; } if (user_data->taskmode & E2_FTM_SAMETIME) { struct utimbuf tb; tb.modtime = statptr->st_mtime; //dirfix->modtime; tb.actime = statptr->st_atime; //dirfix->axstime; // if (e2_fs_utime ((gchar *)filename, &tb E2_ERR_NONE())) // retval = E2TW_FIXME; #ifdef E2_VFS if (e2_fs_utime (&ddata, &tb E2_ERR_NONE())) //FIXME use E2_ERR_PTR if no error yet #else if (e2_fs_utime (dest, &tb E2_ERR_NONE())) //FIXME use E2_ERR_PTR if no error yet #endif retval = E2TW_FIXME; } #ifdef E2_VFS if (!_e2_task_backend_chmod1 (&ddata, dirfix->mode E2_ERR_PTR())) #else if (!_e2_task_backend_chmod1 (dest, dirfix->mode E2_ERR_PTR())) #endif retval = E2TW_FIXME; g_free (dirfix->path); DEALLOCATE (E2_DirEnt, dirfix); user_data->dirdata = g_list_delete_link (user_data->dirdata, member); break; } } // else //should never happen CHECKME ok to delete while interating ? // user_data->dirdata = g_list_delete_link (user_data->dirdata, member); } break; case E2TW_DRR: //dir now readable case E2TW_D: //dir if ((user_data->taskmode & E2_FTM_MERGE) //doing a merge-copy #ifdef E2_VFS && e2_fs_access2 (&ddata E2_ERR_PTR()) == 0) //destination item exists already #else && e2_fs_access2 (dest E2_ERR_PTR()) == 0) //destination item exists already #endif { #ifdef E2_VFS if (e2_fs_is_dir3 (&ddata E2_ERR_PTR())) //destination is a dir also #else if (e2_fs_is_dir3 (dest E2_ERR_PTR())) //destination is a dir also #endif //CHECKME equal-base-name check ? (for copy-as task) break; if (user_data->taskmode & E2_FTM_CHECK) { choice = _e2_task_backend_confirm ((gchar *)localpath, dest, &user_data->taskmode, &retval); if (choice == CANCEL || choice == NO_TO_ALL) { retval |= E2TW_SKIPSUB; break; } } } if (e2_fs_tw_adjust_dirmode (localpath, statptr, X_OK) != 0 #ifdef E2_VFS && _e2_task_backend_mkdir1 (&ddata, 0777)) #else && _e2_task_backend_mkdir1 (dest, 0777)) #endif { //store data about the dir, for later cleanup dirfix = ALLOCATE (E2_DirEnt); CHECKALLOCATEDWARNT (dirfix, retval = E2TW_STOP; break;) dirfix->path = g_strdup (VPSTR(localpath)); dirfix->mode = mode & ALLPERMS; if (user_data->taskmode & E2_FTM_SAMETIME) { //arrange to set times later, if the DP report doesn't happen dirfix->modtime = statptr->st_mtime; dirfix->axstime = statptr->st_atime; } user_data->dirdata = g_list_append (user_data->dirdata, dirfix); } else retval |= E2TW_SKIPSUB; break; case E2TW_DM: //dir, not opened due to different file system case E2TW_DL: //dir, not opened due to tree-depth limit (reported upstream) case E2TW_DNR: //unreadable dir (for which, error is reported upstream) if ((user_data->taskmode & E2_FTM_MERGE) //doing a merge-copy #ifdef E2_VFS && e2_fs_access2 (&ddata E2_ERR_PTR()) == 0) //destination item exists already #else && e2_fs_access2 (dest E2_ERR_PTR()) == 0) //destination item exists already #endif { #ifdef E2_VFS if (e2_fs_is_dir3 (&ddata E2_ERR_PTR())) //destination is a dir also #else if (e2_fs_is_dir3 (dest E2_ERR_PTR())) //destination is a dir also #endif //CHECKME equal-base-name check ? (for copy-as task) { retval = E2TW_FIXME; break; } if (user_data->taskmode & E2_FTM_CHECK) { choice = _e2_task_backend_confirm ((gchar *)localpath, dest, &user_data->taskmode, &retval); if (choice == CANCEL || choice == NO_TO_ALL) { retval = E2TW_FIXME; break; } } } #ifdef E2_VFS if (_e2_task_backend_mkdir1 (&ddata, 0777)) #else if (_e2_task_backend_mkdir1 (dest, 0777)) #endif { if (user_data->taskmode & E2_FTM_SAMETIME) { struct utimbuf tb; tb.modtime = statptr->st_mtime; tb.actime = statptr->st_atime; #ifdef E2_VFS e2_fs_utime (&ddata, &tb E2_ERR_NONE()); //FIXME use E2_ERR_PTR if no error yet #else e2_fs_utime (dest, &tb E2_ERR_NONE()); //FIXME use E2_ERR_PTR if no error yet #endif } if ((mode & ALLPERMS) != 0777) #ifdef E2_VFS _e2_task_backend_chmod1 (&ddata, mode & ALLPERMS E2_ERR_PTR()); #else _e2_task_backend_chmod1 (dest, mode & ALLPERMS E2_ERR_PTR()); #endif } retval = E2TW_FIXME; break; case E2TW_F: //file case E2TW_SL: //link case E2TW_SLN: //bad link if ((user_data->taskmode & E2_FTM_MERGE) //doing a merge-copy && (user_data->taskmode & E2_FTM_CHECK) //confirm overwrite #ifdef E2_VFS && e2_fs_access2 (&ddata E2_ERR_NONE()) == 0) //destination item exists already #else && e2_fs_access2 (dest E2_ERR_NONE()) == 0) //destination item exists already #endif { choice = _e2_task_backend_confirm ((gchar *)localpath, dest, &user_data->taskmode, &retval); if (choice == CANCEL || choice == NO_TO_ALL) break; } if (status == E2TW_F) { #ifdef E2_VFS if (!_e2_task_backend_filecopy (localpath, &ddata, #else if (!_e2_task_backend_filecopy (localpath, dest, #endif user_data->taskmode, statptr)) retval = E2TW_FIXME; //E2TW_STOP; } else //a link { #ifdef E2_VFS if (!_e2_task_backend_linkcopy1 (localpath, &ddata)) #else if (!_e2_task_backend_linkcopy1 ((gchar *)localpath, dest)) #endif retval = E2TW_FIXME; //FTW_STOP; /* changing link times ALWAYS fails if (retval == E2TW_CONTINUE && user_data->taskmode & E2_FTM_SAMETIME) { struct utimbuf tb; tb.modtime = statptr->st_mtime; //dirfix->modtime; tb.actime = statptr->st_atime; //dirfix->axstime; if (e2_fs_utime (dest, &tb E2_ERR_NONE())) retval = E2TW_FIXME; } */ } break; // case E2TW_NS: //un-statable item (error message upstream default: retval = E2TW_FIXME; //E2TW_STOP; break; } g_free (dest); #ifdef DEBUG_MESSAGES if (retval != E2TW_CONTINUE) printd (DEBUG, "Error code for %s is %d", localpath, # ifdef E2_VFS E2_ERR_NAME->code # else errno # endif ); #endif #ifdef E2_VFS if (user_data->operr != NULL && *(user_data->operr) == NULL) *(user_data->operr) = E2_ERR_NAME; else E2_ERR_CLEAR #endif if (retval & E2TW_SKIPSUB) user_data->continued_after_problem = TRUE; if (retval & E2TW_FIXME) { user_data->continued_after_problem = TRUE; retval &= ~E2TW_FIXME; //continue after bad item } return retval; //CHECKME report (some/all?) errors, else it keeps trying & hangs } /******************/ /***** public *****/ /******************/ /** @brief copy item @a src to @a dest Directories, links, others are handled distinctly Assumse BGL is open @param src localised string, absolute path of item to copy @param dest localised string, absolute path of copy destination @param mode flags determining specific features for the copying @return TRUE if copy succeeds */ gboolean e2_task_backend_copy (VPATH *src, VPATH *dest, E2_FileTaskMode mode) { //decide whether src is a dir, link or other struct stat statbuf; E2_ERR_DECLARE if (e2_fs_lstat (src, &statbuf E2_ERR_PTR())) { e2_fs_error_local (_("Cannot get information about %s"), src E2_ERR_MSGL()); E2_ERR_CLEAR return FALSE; } if (S_ISDIR (statbuf.st_mode)) { //recursive copy dir contents //park parameters where they can be accessed by helpers E2_CopyData data; #ifdef E2_VFS data.operr = &E2_ERR_NAME; data.destspace = dest->spacedata; #endif data.taskmode = mode; data.continued_after_problem = FALSE; data.oldroot_len = strlen (VPSTR(src)); if (mode & E2_FTM_MERGE) data.newroot = VPSTR(dest); else //work with a temp name (top-level dir) only if the dest exists already data.newroot = e2_task_tempname (VPSTR(dest)); data.dirdata = NULL; gboolean retval = e2_fs_tw (src, _e2_task_twcb_copy, &data, -1, //flags for: cb-errors, no thru-links, breadth-first #ifdef E2_VFS E2TW_XERR | #endif E2TW_PHYS E2_ERR_PTR()); //cleanups mostly done in twcb, but there may have been a DNR fix, //or an error which aborted the walk if (data.dirdata != NULL) { //revert old and new dir permissions etc, LIFO order #ifdef E2_VFS VPATH fixdata; fixdata.spacedata = dest->spacedata; #endif GList *member; for (member = g_list_last (data.dirdata); member != NULL; member = member->prev) { E2_DirEnt *dirfix = member->data; gchar *dstr = e2_utils_strcat (data.newroot, dirfix->path + data.oldroot_len); #ifdef E2_VFS fixdata.localpath = dstr; #endif if (mode & E2_FTM_SAMETIME) { struct utimbuf tb; tb.modtime = dirfix->modtime; tb.actime = dirfix->axstime; /* if (e2_fs_utime (dirfix->path, &tb E2_ERR_PTR())) { //FIXME vfs handle error E2_ERR_CLEAR data.continued_after_problem = TRUE; //FIXME a more useful message from this } */ #ifdef E2_VFS if (e2_fs_utime (&fixdata, &tb E2_ERR_PTR())) #else if (e2_fs_utime (dstr, &tb E2_ERR_PTR())) #endif { //FIXME vfs handle error E2_ERR_CLEAR data.continued_after_problem = TRUE; //FIXME a more useful message from this } } #ifdef E2_VFS if (!_e2_task_backend_chmod1 (&fixdata, dirfix->mode E2_ERR_PTR())) #else if (!_e2_task_backend_chmod1 (dirfix->path, dirfix->mode E2_ERR_PTR())) #endif { E2_ERR_CLEAR data.continued_after_problem = TRUE; //FIXME a more useful message from this } if (!_e2_task_backend_chmod1 (dest, dirfix->mode E2_ERR_PTR())) { E2_ERR_CLEAR data.continued_after_problem = TRUE; //FIXME a more useful message from this } g_free (dstr); g_free (dirfix->path); DEALLOCATE (E2_DirEnt, dirfix); } g_list_free (data.dirdata); } if (data.continued_after_problem) { e2_fs_error_simple (_("Cannot copy all of %s"), src); } //after reverting modes (coz the temp names are stored in the list if (!retval) { E2_ERR_CLEAR } else { #ifdef E2_VFS VPATH tdata = { data.newroot, dest->spacedata }; #endif if (data.continued_after_problem) { #ifdef E2_VFS if (!e2_fs_access2 (&tdata E2_ERR_NONE())) #else if (!e2_fs_access2 (data.newroot E2_ERR_NONE())) #endif { gchar *temp2, *temp3; temp2 = g_strconcat (VPSTR(dest), "-",_("incomplete"), NULL); temp3 = e2_task_tempname (temp2); #ifdef E2_VFS VPATH t3data = { temp3, dest->spacedata }; retval = e2_task_backend_rename (&tdata, &t3data); #else retval = e2_task_backend_rename (data.newroot, temp3); #endif if (temp3 != temp2) g_free (temp3); g_free (temp2); } } else if (data.newroot != VPSTR(dest)) { /* if (mode & E2_FTM_BACKUP) { get backup name retval = e2_task_backend_move (dest, backup) rename } else if (mode & E2_FTM_TRASH) { pack art, get from retval = e2_task_trashit (gpointer from, E2_ActionRuntime *art) free art rename } else */ if (!(mode & E2_FTM_MERGE)) #ifdef E2_VFS retval = _e2_task_backend_overwrite (dest, &tdata); #else retval = _e2_task_backend_overwrite (dest, data.newroot); #endif } } retval = retval && !data.continued_after_problem; if (data.newroot != VPSTR(dest)) g_free (data.newroot); return retval; } else //not a dir if (S_ISLNK (statbuf.st_mode)) //handle links separately, to avoid 'look-through' return (_e2_task_backend_linkcopy1 (src, dest)); else //not dir or link return (_e2_task_backend_filecopy (src, dest, mode, &statbuf)); } /** @brief move file @a src to @a dest @param src localised string, absolute path of item to move @param dest localised string, absolute path of move destination @return TRUE if move succeeds */ gboolean e2_task_backend_move (VPATH *src, VPATH *dest) { if (e2_fs_mount_is_mountpoint (src)) return FALSE; //work with a temp name only if the dest exists already gchar *temp = e2_task_tempname (VPSTR(dest)); #ifdef E2_VFS VPATH tdata = { temp, dest->spacedata }; #endif E2_ERR_DECLARE #ifdef E2_VFS if (e2_fs_rename (src, &tdata E2_ERR_PTR())) #else if (e2_fs_rename (src, temp E2_ERR_PTR())) #endif { if (temp != VPSTR(dest)) g_free (temp); if (E2_ERR_IS (EXDEV)) { //have to copy to another device E2_ERR_CLEAR return ( e2_task_backend_copy (src, dest, E2_FTM_SAMETIME) && e2_task_backend_delete (src) ); } else { gchar *utf = F_DISPLAYNAME_FROM_LOCALE (VPSTR(src)); gchar *utf2 = F_DISPLAYNAME_FROM_LOCALE (VPSTR(dest)); gchar *msg = g_strdup_printf (_("Cannot move %s to %s"), utf, utf2); e2_fs_error (msg E2_ERR_MSGL()); E2_ERR_CLEAR F_FREE (utf); F_FREE (utf2); g_free (msg); return FALSE; } } #ifdef E2_VFS else if (temp != VPSTR(dest) && !_e2_task_backend_overwrite (dest, &tdata)) #else else if (temp != dest && !_e2_task_backend_overwrite (dest, temp)) #endif { g_free (temp); return FALSE; } if (temp != VPSTR(dest)) g_free (temp); return TRUE; } /** @brief create soft link named @a name to @a target @param target localised string, absolute path of item to link to @param name localised string, absolute or relative path of link @return TRUE if creation succeeds */ gboolean e2_task_backend_link (VPATH *target, VPATH *name) { gchar *freeme; //work with a temp name only if the dest exists already gchar *temp = e2_task_tempname (VPSTR(name)); #ifdef E2_VFS VPATH tdata = { temp, name->spacedata }; VPATH fdata; #endif if (e2_option_bool_get ("relative-symlinks")) { freeme = e2_utils_create_relative_path (VPCSTR(target), VPCSTR(name)); #ifdef E2_VFS fdata.localpath = freeme; fdata.spacedata = name->spacedata; #endif } else freeme = NULL; E2_ERR_DECLARE #ifdef E2_VFS if (e2_fs_symlink ((freeme == NULL) ? target : &fdata, &tdata E2_ERR_PTR())) #else if (e2_fs_symlink ((freeme == NULL) ? target : freeme, temp E2_ERR_PTR())) #endif { e2_fs_error_local (_("Cannot create link to %s"), target E2_ERR_MSGL()); if (temp != VPSTR(name)) g_free (temp); if (freeme != NULL) g_free (freeme); E2_ERR_CLEAR return FALSE; } #ifdef E2_VFS else if (temp != VPSTR(name) && !_e2_task_backend_overwrite (name, &tdata)) #else else if (temp != name && !_e2_task_backend_overwrite (name, temp)) #endif { g_free (temp); if (freeme != NULL) g_free (freeme); return FALSE; } if (temp != VPSTR(name)) g_free (temp); if (freeme != NULL) g_free (freeme); return TRUE; } /** @brief rename item named @a oldsrc to @a newsrc Assumes BGL is off/open, for any error message @param oldsrc localised string, absolute path of item to rename @param newsrc localised string, absolute path of new name @return TRUE if rename succeeds */ gboolean e2_task_backend_rename (VPATH *oldsrc, VPATH *newsrc) { if (e2_fs_mount_is_mountpoint (oldsrc)) return FALSE; E2_ERR_DECLARE //work with a temp name only if the dest exists already gchar *temp = e2_task_tempname (VPSTR(newsrc)); #ifdef E2_VFS VPATH tdata = { temp, newsrc->spacedata }; if (e2_fs_rename (oldsrc, &tdata E2_ERR_PTR())) #else if (e2_fs_rename (oldsrc, temp E2_ERR_PTR())) #endif { gchar *utf = F_DISPLAYNAME_FROM_LOCALE (VPSTR(oldsrc)); gchar *utf2 = F_DISPLAYNAME_FROM_LOCALE (VPSTR(newsrc)); gchar *msg = g_strdup_printf (_("Cannot rename %s to %s"), utf, utf2); e2_fs_error (msg E2_ERR_MSGL()); E2_ERR_CLEAR F_FREE (utf); F_FREE (utf2); g_free (msg); if (temp != VPSTR(newsrc)) g_free (temp); return FALSE; } else if (temp != VPSTR(newsrc) && !_e2_task_backend_delete (newsrc E2_ERR_PTR())) { e2_fs_error_local (_("Cannot delete existing %s"), newsrc E2_ERR_MSGL()); g_free (temp); E2_ERR_CLEAR return FALSE; } #ifdef E2_VFS if (e2_fs_rename (&tdata, newsrc E2_ERR_NONE())) #else if (e2_fs_rename (temp, newsrc E2_ERR_NONE())) #endif { if (temp != VPSTR(newsrc)) g_free (temp); return FALSE; } if (temp != VPSTR(newsrc)) g_free (temp); return TRUE; } /** @brief delete item @a path @param path localised string, absolute path of item to delete @return TRUE if delete succeeds */ gboolean e2_task_backend_delete (VPATH *localpath) { E2_ERR_DECLARE if (_e2_task_backend_delete (localpath E2_ERR_PTR())) return TRUE; /* gchar *msg = (e2_fs_is_dir3 (path E2_ERR_NONE())) ? _("Cannot delete part or all of %s"): _("Cannot delete %s"); e2_fs_error_local (msg, path E2_ERR_MSGL()); */ e2_fs_error_local (_("Cannot delete %s"), localpath E2_ERR_MSGL()); E2_ERR_CLEAR return FALSE; } /** @brief change permissions of item @a path to @a mode Only an item's owner (as judged by the effective uid of the process) or a privileged user, can change item permissions. If invoked on a non-dir, or a dir without recursion, it is processed here. If recursion is invoked on a dir, a ntfw funtion is invoked. By that, all nested dir access permissons will be set to include x, non-dir items will be have their new permissions set when they are 'reported'. Finally, here, dirs will be set to their proper permissions (bottom-up order) at the end of the process Recursive chmod works on the host filesystem only. Links are not affected, their target is processed. (OK ??) Error messages expect BGL open @param path localised string, absolute path of item to process @param mode permissions string, symbolic or octal @param recurse code for recurse type (ignored if @a path is not a dir) @return TRUE if operation succeeds */ gboolean e2_task_backend_chmod (VPATH *localpath, gchar *mode, E2_RecurseType recurse) { E2_ChmodData data; struct stat statbuf; E2_ERR_DECLARE if (recurse != E2_RECURSE_NONE) { //decide whether src is a dir or not if (e2_fs_stat (localpath, &statbuf E2_ERR_PTR())) //looks _through_ links { //abort if we can't find the item e2_fs_error_local (_("Cannot get current data for %s"), localpath E2_ERR_MSGL()); E2_ERR_CLEAR return FALSE; } if (S_ISDIR (statbuf.st_mode)) { //recursive chmod //park parameters where they can be accessed by helpers #ifdef E2_VFS data.operr = &E2_ERR_NAME; #endif data.continued_after_problem = FALSE; data.scope = recurse; _e2_task_backend_parse_mode (mode, &data); //no processed dirs yet data.dirdata = NULL; gboolean retval = e2_fs_tw (localpath, _e2_task_twcb_chmod, &data, -1, //flags for: cb-errors, no thru-links, this filesystem only, breadth-first #ifdef E2_VFS E2TW_XERR | #endif E2TW_MOUNT | E2TW_PHYS E2_ERR_PTR()); //cleanups mostly done in twcb, but there may have been a DNR fix, //or an error which aborted the walk if (data.dirdata != NULL) { //change/revert dir permissions, LIFO (=bottom-up) order #ifdef E2_VFS VPATH fixdata; fixdata.spacedata = localpath->spacedata; #endif GList *member; for (member = g_list_last (data.dirdata); member != NULL; member = member->prev) { E2_ERR_CLEAR //should really not do this, but use E2_ERR_NONE arg after any error happens E2_DirEnt *dirfix = member->data; #ifdef E2_VFS fixdata.localpath = dirfix->path; if (!_e2_task_backend_chmod1 (&fixdata, dirfix->mode E2_ERR_PTR())) #else if (!_e2_task_backend_chmod1 (dirfix->path, dirfix->mode E2_ERR_PTR())) #endif data.continued_after_problem = TRUE; g_free (dirfix->path); DEALLOCATE (E2_DirEnt, dirfix); } g_list_free (data.dirdata); } if (data.continued_after_problem) { e2_fs_error_simple (_("Cannot change permission(s) of all of %s"), localpath); retval = FALSE; } #ifdef E2_VFS if (!retval) { E2_ERR_CLEAR } #endif return retval; } else //not dir, handle without recurse recurse = E2_RECURSE_NONE; } if (recurse == E2_RECURSE_NONE) { //get current mode if (e2_fs_lstat (localpath, &statbuf E2_ERR_PTR())) //no link pass-thru { e2_fs_error_local (_("Cannot get current permissions of %s"), localpath E2_ERR_MSGL()); E2_ERR_CLEAR return FALSE; } if (S_ISLNK (statbuf.st_mode)) { return FALSE; //link permissions can't be changed } //decode the mode string _e2_task_backend_parse_mode (mode, &data); //get new mode mode_t new_mode = ((statbuf.st_mode & data.clearmask) | data.setmask) & ALLPERMS; if (_e2_task_backend_chmod1 (localpath, new_mode E2_ERR_PTR())) return TRUE; E2_ERR_CLEAR } return FALSE; //warning prevention only } /** @brief change ownership of item @a path to @a owner_id and @a group_id If invoked on a non-dir, or a dir without recursion, it is processed here. If recursion is invoked on a dir, a ntfw funtion is invoked. By that, all nested dir access permissons will be set (breadth-first walk) to include x, non-dir items will be have their new permissions set when they are 'discovered'. Finally, here, dirs will be reverted to their proper permissions (bottom-up order) at the end of the process Recursive chown works on the host filesystem only. Links are not affected, their target is processed. (CHECKME ok??) Only an item's owner (as judged by the effective uid of the process) or a privileged user, can change item permissions. Assumes BGL is open @param path localised string, absolute path of item to process @param owner_id uid to be applied @param group_id gid to be applied @param recurse_dirs TRUE to recurse (ignored if @a path is not a dir) @return TRUE if the operation succeeds */ gboolean e2_task_backend_chown (VPATH *localpath, uid_t owner_id, gid_t group_id, gboolean recurse_dirs) { struct stat statbuf; E2_ERR_DECLARE // if (e2_fs_stat (path, &statbuf E2_ERR_PTR())) //looking _through_ links if (e2_fs_lstat (localpath, &statbuf E2_ERR_PTR())) //_not_ looking through links { e2_fs_error_local (_("Cannot get current data for %s"), localpath E2_ERR_MSGL()); E2_ERR_CLEAR return FALSE; } if (recurse_dirs) { //decide whether or not item is dir if (S_ISDIR (statbuf.st_mode)) { //dir //park the data where they can be accessed by the helper E2_ChownData data; #ifdef E2_VFS data.operr = &E2_ERR_NAME; #endif data.continued_after_problem = FALSE; data.new_uid = owner_id; data.new_gid = group_id; data.dirdata = NULL; gboolean retval = e2_fs_tw (localpath, _e2_task_twcb_chown, &data, -1, //flags for: cb-errors, fix-DNR, breadth-first, this filesystem only, no link pass-thru #ifdef E2_VFS E2TW_XERR | #endif E2TW_FIXDIR | E2TW_MOUNT | E2TW_PHYS E2_ERR_PTR()); //cleanups mostly done in twcb, but there may have been a DNR fix, //or an error which aborted the walk if (data.dirdata != NULL) { //chmod and revert all dirs' permissions, LIFO order #ifdef E2_VFS VPATH fixdata; fixdata.spacedata = localpath->spacedata; #endif GList *member; for (member=g_list_last (data.dirdata); member!=NULL; member=member->prev) { E2_DirEnt *dirfix = member->data; #ifdef E2_VFS fixdata.localpath = dirfix->path; if (!_e2_task_backend_chown1 (&fixdata, &statbuf, owner_id, group_id E2_ERR_PTR())) #else if (!_e2_task_backend_chown1 (dirfix->path, &statbuf, owner_id, group_id E2_ERR_PTR())) #endif { data.continued_after_problem = TRUE; //prevent duplication of error #ifdef E2_VFS _e2_task_backend_chmod1 (&fixdata, dirfix->mode E2_ERR_NONE()); #else _e2_task_backend_chmod1 (dirfix->path, dirfix->mode E2_ERR_NONE()); #endif } #ifdef E2_VFS else if (!_e2_task_backend_chmod1 (&fixdata, dirfix->mode E2_ERR_PTR())) #else else if (!_e2_task_backend_chmod1 (dirfix->path, dirfix->mode E2_ERR_PTR())) #endif { E2_ERR_CLEAR data.continued_after_problem = TRUE; //FIXME a more helpful message from this } g_free (dirfix->path); DEALLOCATE (E2_DirEnt, dirfix); } g_list_free (data.dirdata); } if (data.continued_after_problem) { e2_fs_error_simple (_("Cannot change ownership of all of %s"), localpath); retval = FALSE; } #ifdef E2_VFS if (!retval) { E2_ERR_CLEAR } #endif return retval; } else //not dir, ignore the recurse flag recurse_dirs = FALSE; } if (!recurse_dirs) { return (_e2_task_backend_chown1 (localpath, &statbuf, owner_id, group_id E2_ERR_NONE())); } return FALSE; //this can never happen, but it prevents warning } /** @brief perform the default action for a specfied item If no default action exists, the user is invited to create one No thread-protection is done here or downstream, the caller must handle that if needed. @param path localised string, absolute or relative path of the item to be 'opened' @param ask TRUE to invite user to choose some actions @return TRUE if the operation succeeded */ gboolean e2_task_backend_open (VPATH *localpath, gboolean ask) { // printd (DEBUG, "e2_task_backend_open ()"); gboolean addpath, retval, exec; gint res; gchar *command, *ext, *freeme, *utf, *usepath, *base = NULL; //sometimes eg if filelist refresh is still underway, the CWD is not set //to active pane dir, so always prepend that if no other dir is nominated //and if not just activating updir-entry #ifdef E2_VFS VPATH tdata; #endif usepath = VPSTR (localpath); if (g_path_is_absolute (usepath) || g_str_equal (usepath, "..")) addpath = FALSE; else { usepath = e2_utils_dircat (curr_view, usepath, TRUE); addpath = TRUE; } utf = F_FILENAME_FROM_LOCALE (usepath); //not DISPLAY /* localpath = g_strdup (localpath); don't need real path yet #ifdef E2_VFS //checking a virtual dir in the process of being mounted may cause bad delay //so we fake a rough guess at what is intended ... //FIXME how ?? (also needed in _e2_pane_change_dir()) printd (DEBUG, "NEED fake check whether %s is a link", path); E2_ERR_DECLARE #endif if (e2_fs_walk_link (&path E2_ERR_NONE())) //if item is a link, get target path { if (!g_path_is_absolute (path)) { //FIXME a returned relative path is relative to some part of old path ? //E2_VFSTMPOK freeme = F_FILENAME_TO_LOCALE (curr_view->dir); base = path; path = e2_utils_strcat (freeme, path); F_FREE (freeme); g_free (base); } } */ #ifdef E2_VFS tdata.localpath = usepath; tdata.spacedata = localpath->spacedata; //checking a virtual dir in the process of being mounted may cause bad delay //so we fake a rough guess at what is intended ... //FIXME how ?? (also needed in _e2_pane_change_dir()) printd (DEBUG, "NEED fake check whether %s is a directory", usepath); if ( // 1 || e2_fs_is_dir3 (&tdata E2_ERR_NONE())) #else if (e2_fs_is_dir3 (usepath)) #endif { exec = FALSE; //no special treatment for dirs freeme = ext = g_strconcat (".", _(""), NULL);//exec stays FALSE } else { // printd (DEBUG, "check X permission of %s", path); #ifdef E2_VFS exec = !e2_fs_access (&tdata, X_OK E2_ERR_NONE()); #else exec = !e2_fs_access (usepath, X_OK); #endif freeme = NULL; //not a constructed extension, no free needed base = g_path_get_basename (utf); ext = strchr (base, '.'); //assumes '.' is ascii } if (ext == NULL //no extension ("." can apply to one type only, probably executables) || ext == base) //it's a hidden file { #ifdef E2_VFS if (!exec && e2_fs_is_text (&tdata E2_ERR_NONE())) #else if (!exec && e2_fs_is_text (usepath)) #endif { #ifdef E2_VFS retval = e2_task_backend_view (&tdata); #else retval = e2_task_backend_view (usepath); #endif } else if (!exec) //&& non-text is implied { if (ask) { #ifdef E2_VFS res = e2_filetype_dialog_create (&tdata, FALSE, FALSE, FALSE); //enter command or view #else res = e2_filetype_dialog_create (usepath, FALSE, FALSE, FALSE); //enter command or view #endif retval = (res == E2_TYPE_HANDLED); } else retval = FALSE; } else //executable non-dir { /* if (g_str_equal (dir, ".") && ! g_str_has_prefix (path, E2_COMMAND_PREFIX)) command = g_strdup_printf ("%s%s", E2_COMMAND_PREFIX, utf); else command = path; #ifdef E2_COMMANDQ res = e2_command_run (command, E2_COMMAND_RANGE_DEFAULT, TRUE); #else res = e2_command_run (command, E2_COMMAND_RANGE_DEFAULT); #endif if (command != path) g_free (command); */ //FIXME better aesthetic, shorten the command string where appropriate res = e2_command_run (utf, E2_COMMAND_RANGE_DEFAULT #ifdef E2_COMMANDQ , TRUE #endif ); retval = (res == 0); } } else // the file has an extension of some sort { gchar *action, *real_action; do { ext++; //NCHR(ext); //skip the . prefix action = e2_filetype_get_default_action (ext); if (action != NULL) { real_action = strchr (action, '@'); if (real_action != NULL) real_action++; else real_action = action; //user may have prepended "./" to command, but we don't need //that cuz we use absolute path if (exec && g_str_has_prefix (real_action, E2_COMMAND_PREFIX)) real_action += 2; //sizeof (E2_COMMAND_PREFIX); //skip leading ascii chars if (exec) //exec as well as extension { if (ask //hack to exclude valid exec extension like .sh && (!(g_str_has_prefix (real_action, "%f") || g_str_has_prefix (real_action, "%%f") || g_str_has_prefix (real_action, "%p") || g_str_has_prefix (real_action, "%%p")))) { #ifdef E2_VFS res = e2_filetype_dialog_create (&tdata, FALSE, TRUE, FALSE); #else res = e2_filetype_dialog_create (usepath, FALSE, TRUE, FALSE); #endif if (res == E2_TYPE_CANCELLED || res == E2_TYPE_HANDLED) { retval = (res == E2_TYPE_HANDLED); g_free (action); break; } } } //not exec, or not ask, or exec+ask but user chose default action //always ascii @, don't need g_utf8_strchr() command = e2_utils_replace_name (real_action, utf); if (command == NULL) //no replaced macro in real_action command = g_strdup_printf ("%s \"%s\"", real_action, utf); res = e2_command_run (command, E2_COMMAND_RANGE_DEFAULT #ifdef E2_COMMANDQ , TRUE #endif ); g_free (command); retval = (res == 0); g_free (action); break; } } while ((ext = strchr (ext, '.')) != NULL); if (ext == NULL) //extension not recognised { if (exec) //just execute it { /* dir = g_path_get_dirname (path); if (g_str_equal (dir, ".") && ! g_str_has_prefix (path, E2_COMMAND_PREFIX)) command = g_strdup_printf ("%s%s", E2_COMMAND_PREFIX, utf); else command = path; res = e2_command_run (command, E2_COMMAND_RANGE_DEFAULT); #ifdef E2_COMMANDQ , TRUE #endif ); g_free (dir); if (command != path) g_free (command); */ res = e2_command_run (utf, E2_COMMAND_RANGE_DEFAULT #ifdef E2_COMMANDQ , TRUE #endif ); retval = (res == 0); } else if (ask) { #ifdef E2_VFS res = e2_filetype_dialog_create (&tdata, TRUE, FALSE, TRUE); //enter filetype, command or view #else res = e2_filetype_dialog_create (usepath, TRUE, FALSE, TRUE); //enter filetype, command or view #endif retval = (res == E2_TYPE_HANDLED); } else retval = FALSE; } } if (addpath) g_free (usepath); F_FREE (utf); if (freeme != NULL) g_free (freeme); if (base != NULL) g_free (base); return retval; } /** @brief open text file @a localpath using internal or external viewer This does no mutex management, so that must be handled by the caller. @param localpath localised string, path of file to be viewed @return TRUE if the viewer task was completed successfully */ gboolean e2_task_backend_view (VPATH *localpath) { if (e2_option_bool_get ("use-external-viewer")) { gchar *viewer = e2_option_str_get ("command-viewer"); if (*viewer != '\0') { gchar *utf = F_FILENAME_FROM_LOCALE (VPSTR (localpath)); gchar *command = e2_utils_replace_name (viewer, utf); if (command == NULL) command = g_strdup_printf ("%s \"%s\"", viewer, utf); gint res = e2_command_run (command, E2_COMMAND_RANGE_DEFAULT #ifdef E2_COMMANDQ , TRUE #endif ); F_FREE (utf); g_free (command); return (res == 0); } else return FALSE; } else return (e2_view_dialog_create (localpath)); } emelfm2-0.4.1/src/e2_filelist.h0000600000175000017500000000454311010340377015152 0ustar cairocairo/* $Id: e2_filelist.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_FILELIST_H__ #define __E2_FILELIST_H__ #include "emelfm2.h" /*enum { FILENAME = 0, SIZE, PERM, OWNER, GROUP, see e2.h MODIFIED, ACCESSED, CHANGED }; //, MAX_COLUMNS }; */ //additional model columns, not displayed enum { NAMEKEY = MAX_COLUMNS, FINFO, FORECOLOR, BACKCOLOR, #ifdef E2_SELTXT_RECOLOR SELCOLOR, #endif MODEL_COLUMNS }; //VISIBLE, MODEL_COLUMNS }; typedef enum { PANE1, PANE2, PANEACTIVE, PANEINACTIVE } E2_ListChoice; typedef struct _E2_Column { gchar *title; gint size; //default width gint (*sort_func) (); } E2_Column; //gboolean order; //sort order flag, TRUE for ascending, FALSE for descending E2_Column e2_all_columns[MAX_COLUMNS]; void e2_filelist_start_refresh_polling (void); void e2_filelist_disable_one_refresh (E2_ListChoice pane); void e2_filelist_enable_one_refresh (E2_ListChoice pane); gboolean e2_filelist_disable_refresh_action (gpointer from, E2_ActionRuntime *art); gboolean e2_filelist_enable_refresh_action (gpointer from, E2_ActionRuntime *art); void e2_filelist_disable_refresh (void); void e2_filelist_reset_refresh (void); void e2_filelist_enable_refresh (void); gboolean e2_filelist_request_refresh (gchar *dir, gboolean immediate); //void e2_filelist_request_focus (GtkWidget *focus_wid); gboolean e2_filelist_check_dirty (gpointer userdata); gboolean e2_filelist_clear_old_stores (gpointer user_data); GtkListStore *e2_filelist_fill_store (GList *entries, ViewInfo *view); GtkListStore *e2_filelist_make_store (void); #ifdef E2_VFS GtkListStore *e2_filelist_copy_store (GtkListStore *original); #endif #endif //ndef __E2_FILELIST_H__ emelfm2-0.4.1/src/utils/0000700000175000017500000000000011015120161013720 5ustar cairocairoemelfm2-0.4.1/src/utils/e2_widget.h0000600000175000017500000001047011010340377015756 0ustar cairocairo/* $Id: e2_widget.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/utils/e2_widget.h @brief miscelleanous GtkWidget utility function headers This file is the header file for the miscelleanous GtkWidget utility functions. */ #ifndef __E2_WIDGET_H__ #define __E2_WIDGET_H__ #ifdef USE_GTK2_12TIPS void e2_widget_set_toggletip (GtkWidget *widget, const gchar *initialtip, const gchar *othertip); #else void e2_widget_set_tooltip (GtkTooltips *tooltips, GtkWidget *widget, gchar *text); #endif void e2_widget_swap_tooltip (GtkWidget *widget); GtkWidget *e2_widget_get_icon (const gchar *icon, GtkIconSize size); GtkWidget *e2_widget_add_mid_label (GtkWidget *box, const gchar *text, gfloat align, gboolean exp, guint pad); GtkWidget *e2_widget_add_label (GtkWidget *box, const gchar *text, gfloat xalign, gfloat yalign, gboolean exp, guint pad); //FIXME: remove //#define add_entry(box, init_text, fill, pad) e2_widget_add_entry (box, init_text, fill, FALSE) GtkWidget *e2_widget_add_entry (GtkWidget *box, gchar *init_text, gboolean exp, gboolean select_text); //FIXME: remove //#define add_hbox(box, homogen, spacing, fill, pad) e2_widget_add_box (box, fill, pad, FALSE, homogen, spacing) GtkWidget *e2_widget_get_box (gboolean vertical, gboolean homogen, gint spacing); GtkWidget *e2_widget_add_box (GtkWidget *parent, gboolean exp, guint pad, gboolean vertical, gboolean homogen, gint spacing); GtkWidget *e2_widget_add_sw (GtkWidget *box, GtkPolicyType h_policy, GtkPolicyType v_policy, gboolean exp, guint pad); GtkWidget *e2_widget_get_sw_plain (GtkPolicyType h_policy, GtkPolicyType v_policy); GtkWidget *e2_widget_get_sw (GtkPolicyType h_policy, GtkPolicyType v_policy, GtkShadowType shadow); void e2_widget_sw_add_with_viewport (GtkWidget *sw, GtkWidget *child); GtkWidget *e2_widget_add_separator (GtkWidget *box, gboolean exp, guint pad); GtkWidget *e2_widget_add_table (GtkWidget *box, gint rows, gint cols, gboolean homogen, gboolean exp, guint pad); GtkWidget *e2_widget_add_mid_label_to_table (GtkWidget *table, gchar *text, gfloat align, gint left, gint right, gint top, gint bottom); //GtkWidget *e2_widget_add_entry_to_table (GtkWidget *table, gchar *init_text, // gint left, gint right, gint top, gint bottom); //GtkWidget *e2_widget_add_framed_table (GtkWidget *box, gchar *title, gint rows, // gint cols, gboolean exp, guint pad); //GtkWidget *e2_widget_add_framed_widget(GtkWidget *box, gchar *title, // GtkWidget *widget, gboolean exp, guint pad); GtkWidget *e2_widget_add_frame (GtkWidget *box, gboolean fill, guint pad, gchar *title, gboolean strech); GtkWidget *e2_widget_add_eventbox (GtkWidget *box, gboolean fill, guint pad); GtkWidget *e2_widget_get_notebook (gpointer func, gpointer data); GtkWidget *e2_widget_add_notebook (GtkWidget *box, gboolean fill, guint pad, gpointer func, gpointer data); GtkWidget *e2_widget_add_notebook_page (//GtkWidget *dialog, GtkWidget *notebook, gchar *name, GtkPolicyType swpolicy); void e2_widget_get_font_pixels (GtkWidget *widget, int *width, int *height); void e2_widget_set_font (GtkWidget *widget, const gchar *font_string); GtkWidget *e2_widget_add_expander (GtkWidget *box, gchar *text, E2_OptionSet *boolset, GtkWidget *controller, GtkWidget *child); GtkWidget *e2_widget_add_tied_check_button (GtkWidget *box, E2_OptionSet *boolset, GtkWidget *controller); void e2_widget_handle_depends (GtkWidget *widget, E2_OptionSet *boolset); #ifdef E2_ASSISTED void e2_widget_set_label_relations (GtkLabel *label, GtkWidget *widget); AtkObject *e2_widget_get_accessible (GtkWidget *widget, const gchar *name, const gchar *desc, AtkRole role); #endif #endif //ndef __E2_WIDGET_H__ emelfm2-0.4.1/src/utils/e2_utils.h0000600000175000017500000001064511010340377015637 0ustar cairocairo/* $Id: e2_utils.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2003-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_UTILS_H__ #define __E2_UTILS_H__ #include "emelfm2.h" #define LF 10 #define CR 13 typedef enum { E2_DOTS_START, E2_DOTS_MIDDLE, E2_DOTS_END, } E2_DotMode; typedef struct _E2_Duo { gpointer a; gpointer b; } E2_Duo; typedef struct _E2_Trio { gpointer a; gpointer b; gpointer c; } E2_Trio; /* typedef struct _E2_Quartet { gpointer a; gpointer b; gpointer c; gpointer d; } E2_Quartet; */ typedef struct _E2_Sextet { gpointer a; gpointer b; gpointer c; gpointer d; gpointer e; gpointer f; } E2_Sextet; typedef struct _E2_Nontet { gpointer a; gpointer b; gpointer c; gpointer d; gpointer e; gpointer f; gpointer g; gpointer h; gpointer i; } E2_Nontet; //E2_Trio *e2_utils_trio_new (void); E2_Sextet *e2_utils_sextet_new (void); E2_Nontet *e2_utils_nontet_new (void); //void e2_utils_trio_destroy (E2_Trio *t); void e2_utils_sextet_destroy (E2_Sextet *s); void e2_utils_nontet_destroy (E2_Nontet *n); void e2_utils_show_memory_message (void); void e2_utils_memory_error (void); void e2_utils_show_help (gchar *title); gchar *e2_utils_color2str (GdkColor *color); gchar *e2_utils_str_replace (const gchar *str, const gchar *old, const gchar *new); gint e2_utils_LF_line_ends (gchar *text); gchar *e2_utils_revert_line_ends (gchar *text, guint linecount, gint separator); void e2_utils_get_charset (const gchar **encoding); void e2_utils_rowstr_split (gchar *line, gint columns, gchar *parts[]); gchar *e2_utils_str_stretch (gchar *string); gchar *e2_utils_str_shorten (gchar *string, gint limit, E2_DotMode position); gchar *e2_utils_str_to_lower (gchar *string); gint e2_utils_get_byte_position (const gchar *utf8_string, gint charoffset); gchar *e2_utils_dircat (ViewInfo *view, const gchar *string, gboolean localised); gchar *e2_utils_strcat (const gchar *string1, const gchar *string2); gchar **e2_utils_str_breakup (const gchar *string, const gchar *delimiter, gint max_tokens); gchar *e2_utils_find_whitespace (gchar *string); gchar *e2_utils_pass_whitespace (gchar *string); gchar *e2_utils_unquote_string (const gchar *utf8string); gchar *e2_utils_bare_strchr (gchar *string, gchar c); gchar *e2_utils_get_first_arg (gchar *string, gboolean quoted); gchar *e2_utils_get_tempname (const gchar *orig); gboolean e2_utils_goto_accessible_path (E2_PaneRuntime *rt); gboolean e2_utils_get_parent_path (gchar *path, gboolean ignore_trailer); gchar *e2_utils_path_clean (gchar *path); gchar *e2_utils_translate_relative_path (gchar *current_dir, gchar *new_dir); gchar *e2_utils_create_relative_path (const gchar *src, const gchar *dest); gchar *e2_utils_get_temp_path (const gchar *id); const gchar *e2_utils_get_trash_path (void); gchar *e2_utils_get_icons_path (gboolean withtrailer); #ifdef E2_IMAGECACHE void e2_utils_init_icon_sizes (void); gint e2_utils_get_icon_size (GtkIconSize size); GtkIconSize e2_utils_get_best_icon_size (gint psize); #endif gboolean e2_utils_check_stock_icon (const gchar *name); const gchar *e2_utils_get_output_font (void); void e2_utils_update_gtk_settings (void); gchar *e2_utils_expand_macros (gchar *text, gchar *for_each); gchar *e2_utils_replace_name (const gchar *string, const gchar *newtext); gchar *e2_utils_replace_name2 (gchar *text, gchar *path, GPtrArray *array, gboolean single); gchar *e2_utils_replace_vars (gchar *raw); gchar *e2_utils_replace_wildcards (gchar *raw); void e2_utils_get_abs_pos (GtkWidget *widget, gint *x, gint *y); GdkModifierType e2_utils_get_modifiers (void); void e2_utils_beep (void); gboolean e2_utils_multi_src (GList *srclist); gunichar e2_utils_get_mnemonic_char (gchar *label); guint e2_utils_get_mnemonic_keycode (gchar *label); void e2_utils_block_thread_signals (void); #endif //ndef __E2_UTILS_H__ emelfm2-0.4.1/src/utils/debug.c0000600000175000017500000000376110643544426015207 0ustar cairocairo/* $Id: debug.c 469 2007-07-06 22:58:30Z tpgww $ Copyright (C) 2003-2007 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "emelfm2.h" #ifdef DEBUG_MESSAGES #include #include #include "e2_cl_option.h" void printd_raw (gint level, gchar *file, gint line, const gchar *format, ...) { if (level > e2_cl_options.debug_level) return; gchar *lvl; va_list args; if (e2_cl_options.verbose) { static glong sec = 0; static glong usec = 0; struct timeval t; gettimeofday (&t, NULL); if (!sec) sec = t.tv_sec; if (!usec) usec = t.tv_usec; glong dsec = t.tv_sec - sec; glong dusec = t.tv_usec - usec; sec = t.tv_sec; usec = t.tv_usec; if ((dsec > 0) && (dsec < 10)) dusec += dsec * 1000000; if ((dusec < 10000) && (dsec == 0)) printf ("%6ld us %s/%d ", dusec, file, line); else if ((dusec < (1000000)) && (dsec == 0)) printf ("%6ld ms %s/%d ", (dusec / 1000), file, line); else printf ("%6ld s %s/%d ", (dusec / 1000000) + dsec, file, line); } switch (level) { case ERROR: lvl = "ERROR "; break; case WARN: lvl = "WARN "; break; case INFO: lvl = "INFO "; break; case NOTICE: lvl = "NOTICE"; break; default: lvl = "DEBUG "; break; } printf ("[%s] ", lvl); va_start (args, format); vprintf (format, args); va_end (args); printf ("\n"); } #endif //def DEBUG_MESSAGES emelfm2-0.4.1/src/utils/debug.h0000600000175000017500000000321711010340377015174 0ustar cairocairo/* $Id: debug.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2003-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __DEBUG_H__ #define __DEBUG_H__ #include "build.h" #include //debug-level names enum { WARN = 1, INFO, ERROR, NOTICE, DEBUG }; //#define IS_A(wid) g_type_name(GTK_WIDGET_TYPE(wid)) #define IS_A(wid) g_type_name(G_OBJECT_TYPE(G_OBJECT(wid))) #ifdef DEBUG_MESSAGES_ALWAYS # ifndef DEBUG_MESSAGES # define DEBUG_MESSAGES # endif #else //the Makefile sets E2_DEBUG_LEVEL to 2, or 5 if debugging is requested #if E2_DEBUG_LEVEL > 2 # define DEBUG_MESSAGES #else # undef DEBUG_MESSAGES #endif #endif #ifdef DEBUG_MESSAGES void printd_raw (gint level, gchar *file, gint line, const gchar *format, ...); #define printd(level, str, args...) printd_raw (level, __FILE__, __LINE__, str, ## args) #define ERR_STR strerror (errno) #else //don't include any code when user is not really interested #define printd(level, str, args...) #endif #endif //ndef __DEBUG_H__ emelfm2-0.4.1/src/utils/e2_textiter.h0000600000175000017500000000362110643544426016357 0ustar cairocairo/* $Id: e2_textiter.h 469 2007-07-06 22:58:30Z tpgww $ Portions copyright (C) 2003-2007 tooar Portions copyright (C) 2000, 2002 Paolo Maggi Portions copyright (C) 2002, 2003 Jeroen Zwartepoorte This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __GTK_SOURCE_ITER_H__ #define __GTK_SOURCE_ITER_H__ #include //this is a superset of GtkTextSearchFlags typedef enum { E2_SEARCH_VISIBLE_ONLY = 1, //=GTK_TEXT_SEARCH_VISIBLE_ONLY E2_SEARCH_TEXT_ONLY = 1 << 1, //=GTK_TEXT_SEARCH_VISIBLE_ONLY E2_SEARCH_CASE_INSENSITIVE = 1 << 2, //possible future support in gtk E2_SEARCH_REGEXP = 1 << 3, //possible future support in gtk E2_SEARCH_WHOLE_WORD = 1 << 4 } E2TextSearchFlags; //#define GTK_SEARCH_FLAGS (GTK_TEXT_SEARCH_VISIBLE_ONLY | GTK_TEXT_SEARCH_VISIBLE_ONLY) gboolean e2_iter_forward_search ( const GtkTextIter *iter, const gchar *str, E2TextSearchFlags flags, GtkTextIter *match_start, GtkTextIter *match_end, const GtkTextIter *limit); gboolean e2_iter_backward_search ( const GtkTextIter *iter, const gchar *str, E2TextSearchFlags flags, GtkTextIter *match_start, GtkTextIter *match_end, const GtkTextIter *limit); #endif //def __GTK_SOURCE_ITER_H__ emelfm2-0.4.1/src/utils/e2_menu.h0000600000175000017500000000564411010340377015446 0ustar cairocairo/* $Id: e2_menu.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/utils/e2_menu.h @brief Menu utilitiy functions header. This is the header file for the menu utility functions. */ #ifndef __E2_MENU_H__ #define __E2_MENU_H__ typedef enum { E2_CHILD_ALL, E2_CHILD_ACTIVE, E2_CHILD_OUTPUT, } E2_ChildMenuType; //callbacks void e2_menu_destroy_cb (GtkWidget *menu, gpointer data); void e2_menu_control_cb (GtkWidget *menu_item, gpointer data); //public void e2_menu_connect (GtkWidget *menu, gboolean active); void e2_menu_popup (GtkWidget *menu, gint button, guint32 time); //FIXME: remove /*#define add_menu_item(menu, label, func, data) \ e2_menu_add (menu, label, NULL, NULL, func, data) #define e2_menu_add_simple(menu, label, icon, func, data) \ e2_menu_add (menu, label, icon, NULL, func, data) */ GtkWidget *e2_menu_add (GtkWidget *menu, gchar *label, gchar *icon, gchar *tip, void *func, gpointer data); GtkWidget *e2_menu_add_action (GtkWidget *menu, gchar *label, gchar *icon, gchar *tip, gchar *action, gchar *arg, gpointer free); GtkWidget *e2_menu_add_check (GtkWidget *menu, gchar *label, gboolean state, void *func, gpointer data); GtkWidget *e2_menu_add_radio (GtkWidget *menu, GSList **group, gchar *label, gboolean state, void *func, gpointer data); GtkWidget *e2_menu_add_separator (GtkWidget *menu); GtkWidget *e2_menu_add_tear_off (GtkWidget *menu); GtkWidget *e2_menu_add_submenu (GtkWidget *menu, gchar *label_text, gchar *icon); GtkWidget *e2_menu_add_toggle (GtkWidget *menu, gchar *label, gchar *icon, gchar *tip, gchar *toggle, gchar *cmd); GtkWidget *e2_menu_create_options_menu (GtkWidget *controller, GtkWidget *menu, E2_OptionSet *set, ...); void e2_menu_create_bookmarks_menu (E2_Action *action, E2_OptionSet *set, GtkWidget *menu, GtkTreeIter *iter); void e2_menu_create_plugins_menu (GtkWidget *menu, gboolean is_selection); GtkWidget *e2_menu_create_child_menu (E2_ChildMenuType type, gpointer func); GtkWidget *e2_menu_create_filter_menu (ViewInfo *view); GtkWidget *e2_menu_create_custom_menu (gchar *name); void e2_menu_custom_option_register (void); #ifdef E2_FS_MOUNTABLE gboolean e2_menu_create_mounts_menu (gpointer from, E2_ActionRuntime *art); #endif #endif //ndef __E2_MENU_H__ emelfm2-0.4.1/src/utils/e2_combobox.c0000600000175000017500000004313211010340377016277 0ustar cairocairo/* $Id: e2_combobox.c 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/utils/e2_combobox.c @brief GtkComboBox utilities This file contains utility functions for the GtkComboBox widget */ #include "emelfm2.h" #include "e2_combobox.h" #include "e2_pane.h" #include "e2_command_line.h" static void _e2_combobox_block_changed (GtkWidget *combo) { g_object_set_data (G_OBJECT (combo), "e2_changed_blocked", GINT_TO_POINTER (TRUE)); } static void _e2_combobox_unblock_changed (GtkWidget *combo) { g_object_set_data (G_OBJECT (combo), "e2_changed_blocked", GINT_TO_POINTER (FALSE)); //= NULL } /** @brief determine the index of the last row in the model of @a combo @param combo the entry text combo box to be interrogated @return the index of the last item in the model, -1 if there's nothing */ static gint _e2_combobox_last_index (GtkComboBox *combo) { GtkTreeModel *model = gtk_combo_box_get_model (combo); return ((gint)gtk_tree_model_iter_n_children (model, NULL) - 1); } /*******************/ /**** callbacks ****/ /*******************/ /** @brief key-press-event signal callback to handle accessing combobox history items This applies to all created comboboxes whether or not they have a text-entry @param entry the combobox entry widget which received the keypress @param event pointer to key event data struct @param data pointerised gboolean, TRUE to cycle to other end of history @return TRUE if pressed key was or */ static gboolean _e2_combobox_key_press_cb (GtkWidget *entry, GdkEventKey *event, gpointer data) { if (!(event->keyval == GDK_Up || event->keyval == GDK_Down)) return FALSE; GtkComboBox *combo = GTK_COMBO_BOX (entry->parent); gboolean cycle = (data != NULL); gint pos = gtk_combo_box_get_active (combo); if (event->keyval == GDK_Down) { if (pos < _e2_combobox_last_index (combo)) pos++; //assumes -1 can go to 0 always else if (cycle) pos = 0; } else //(event->keyval == GDK_Up) { if (pos > 0) pos--; // else if ((pos == 0 && cycle) || pos == -1) else if ((pos == 0 || pos == -1) && cycle) //disable goto end of history if no cycling pos = _e2_combobox_last_index (combo); } if (pos == -1) gtk_entry_set_text (GTK_ENTRY (entry), ""); else { e2_combobox_set_active (GTK_WIDGET (combo), pos); gchar *text = e2_combobox_get_active (GTK_WIDGET (combo)); if (text != NULL) { gtk_entry_set_text (GTK_ENTRY (entry), text); g_free (text); } gtk_editable_set_position (GTK_EDITABLE (entry), -1); } return TRUE; } /** @brief key press signal callback to handle text shortening or history clearing A Delete keypress in the combobox entry triggers deletion of current string and any matching item(s) in @a history. A Delete keypress in the combobox entry triggers deletion of current string and the whole history list @a history (hopefully that's not static if also used by other widgets) Otherwise, Delete clears text from cursor to end @param entry the entry widget for the combo box @param event pointer to event data struct @param history pointer to history list for the combo box @return TRUE if the keypress was Delete */ static gboolean _e2_combobox_key_press_cb2 (GtkWidget *entry, GdkEventKey *event, GList **history) { gint start; GtkComboBox *combo; GtkTreeModel *model; if (event->keyval == GDK_Delete) { guint modifiers = gtk_accelerator_get_default_mod_mask (); if ((event->state & modifiers) == GDK_SHIFT_MASK) { //there is no action for clearing "the rest" of an entry start = gtk_editable_get_position (GTK_EDITABLE (entry)); gtk_editable_delete_text (GTK_EDITABLE (entry), start, -1); return TRUE; } else if ((event->state & modifiers) == GDK_MOD1_MASK) { const gchar *this = gtk_entry_get_text (GTK_ENTRY (entry)); if (this != NULL) //cannot happen ? { combo = GTK_COMBO_BOX (entry->parent); model = gtk_combo_box_get_model (combo); GtkTreeIter iter; if (gtk_tree_model_get_iter_first (model, &iter)) { while (e2_tree_find_iter_from_str (model, 0, this, &iter, FALSE) && gtk_list_store_remove (GTK_LIST_STORE (model), &iter)) {} } if (history != NULL && *history != NULL) { GList *tmp; while ((tmp = g_list_find_custom (*history, this, (GCompareFunc) e2_list_strcmp)) != NULL) { g_free (tmp->data); *history = g_list_delete_link (*history, tmp); } } } gtk_entry_set_text (GTK_ENTRY (entry), ""); return TRUE; } else if ((event->state & modifiers) == (GDK_SHIFT_MASK | GDK_MOD1_MASK)) { //hardcode history clearance for most comboboxes //a keybinding probably applies to dirlines and commandline but //in that case they don't use this function gtk_entry_set_text (GTK_ENTRY (entry), ""); combo = GTK_COMBO_BOX (entry->parent); model = gtk_combo_box_get_model (combo); gtk_list_store_clear (GTK_LIST_STORE (model)); if (history != NULL && *history != NULL) //too bad for any other widget using the history, if that's static e2_list_free_with_data (history); return TRUE; } } return FALSE; } /** @brief combobox "changed" signal callback When a history item is selected, if @a data is non-NULL (TRUE) the combobox entry is focused. If @a data is NULL (FALSE) then an "activated" signal is emitted on the entry. When the combobox entry is simply edited, nothing is done. @param combo the combobox widget whose entry was changed @param data pointerised gboolean, TRUE if @a combo was created with the E2_COMBOBOX_FOCUS_ON_CHANGE flag set @return */ static void _e2_combobox_changed_cb (GtkWidget *combo, gpointer data) { // printd (DEBUG, "_e2_combobox_changed_cb: combo:_ data: %s", (data == NULL) ? "FALSE":"TRUE"); if (g_object_get_data (G_OBJECT (combo), "e2_changed_blocked") != NULL) { // printd (DEBUG, "_e2_combobox_changed_cb BLOCKED"); return; } //active item will be -1 whenever an item is edited if ((GTK_WIDGET_VISIBLE (combo)) && (gtk_combo_box_get_active (GTK_COMBO_BOX (combo)) != -1)) { gboolean focus = GPOINTER_TO_INT (data); GtkWidget *entry = GTK_BIN (combo)->child; if (focus) { gtk_widget_grab_focus (entry); gtk_editable_set_position (GTK_EDITABLE (entry), -1); } else g_signal_emit_by_name (G_OBJECT (entry), "activate"); } } /******************/ /***** public *****/ /******************/ /** @brief "activate" signal callback for created GtkComboBoxEntry comboboxes This function is a callback for the "activate" signal of the GtkEntry that belongs to a combobox with a text entry, unless that combobox is created with flag E2_COMBOBOX_NO_AUTO_HISTORY. The current contents of @a entry, if not empty, are prepended to the box's history list. @param entry entry of a GtkComboBoxEntry @param data pointerised gboolean, TRUE if double history entries are allowed @return */ void e2_combobox_activated_cb (GtkWidget *entry, gpointer data) { printd (NOTICE, "e2_combobox_activated_cb"); gchar *text = g_strdup (gtk_entry_get_text (GTK_ENTRY (entry))); g_strstrip (text); if (*text != '\0') { GtkComboBox *combo = GTK_COMBO_BOX (entry->parent); gtk_combo_box_prepend_text (combo, text); e2_combobox_set_active (GTK_WIDGET (combo), 0); if (data == NULL) // !GPOINTER_TO_INT (data)) { //remove any existing item that's the same GtkTreeModel *model = gtk_combo_box_get_model (combo); GtkTreeIter iter; gtk_tree_model_get_iter_first (model, &iter); //this is the one just prepended if (gtk_tree_model_iter_next (model, &iter)) { //actually, there should only ever be 1 match at most while (e2_tree_find_iter_from_str_simple (model, 0, text, &iter, FALSE)) { GtkTreeIter test; test = iter; gboolean more = gtk_tree_model_iter_next (model, &test); gtk_list_store_remove (GTK_LIST_STORE (model), &iter); //after this, iter has data for next item, if any if (!more) break; } } } } g_free (text); } /** @brief determine whether the history model of @a combo has any content @param combo the combo box to be interrogated @return TRUE if the model has something */ gboolean e2_combobox_has_history (GtkComboBox *combo) { GtkTreeModel *model = gtk_combo_box_get_model (combo); return (gtk_tree_model_iter_n_children (model, NULL) > 0); } /* * @brief activate the last history item of combo box @a combo This also puts the contents of the history line into the combo entry, and moves the cursor to the end @param combo GtkComboBox to work on @return */ /*void e2_combobox_select_last (GtkWidget *combo) { GtkWidget *entry = GTK_BIN (combo)->child; // GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo)); gint count = _e2_combobox_last_index ((GTK_COMBO_BOX (combo)); gchar *text = g_strdup (gtk_entry_get_text (GTK_ENTRY (entry))); gint pos = gtk_editable_get_position (GTK_EDITABLE (entry)); // _e2_combobox_block_changed (combo); //CHECKME e2_combobox_set_active (combo, count); // _e2_combobox_unblock_changed (combo); //CHECKME gtk_entry_set_text (GTK_ENTRY (entry), text); gtk_editable_set_position (GTK_EDITABLE (entry), pos); g_free (text); } */ /* * @brief get the string from the last row in the model of @a combo @param combo the entry text combo box to be interrogated @return newly-allocated copy of the string, or NULL if there's nothing */ /*gchar *e2_combobox_last_text (GtkComboBox *combo) { gchar *text = NULL; GtkTreeModel *mdl = gtk_combo_box_get_model (combo); GtkTreeIter iter; guint count = gtk_tree_model_iter_n_children (mdl, NULL); if (count > 0 && gtk_tree_model_iter_nth_child (mdl, &iter, NULL, count-1)) gtk_tree_model_get (mdl, &iter, 0, &text, -1); return text; } */ /** @brief get the string from the first row in the model of @a combo @param combo the entry text combo box to be interrogated @return newly-allocated copy of the string, or NULL if there's nothing */ gchar *e2_combobox_first_text (GtkComboBox *combo) { gchar *text = NULL; GtkTreeModel *mdl = gtk_combo_box_get_model (combo); GtkTreeIter iter; if (gtk_tree_model_get_iter_first (mdl, &iter)) gtk_tree_model_get (mdl, &iter, 0, &text, -1); return text; } /** @brief activate history item @a num of combo box @a combo @param combo GtkComboBox to work on @param num the integer index of the item to activate @return */ void e2_combobox_set_active (GtkWidget *combo, gint num) { printd (DEBUG, "e2_combobox_set_active: %d", num); _e2_combobox_block_changed (combo); gtk_combo_box_set_active (GTK_COMBO_BOX (combo), num); _e2_combobox_unblock_changed (combo); } /** @brief return currently-selected string from history This function gets the active GtkTreeIter from the GtkComboBox and tries to get the history string from the model and returns it. @param combo GtkComboBox to work on @return string from history or NULL if nothing is selected */ gchar *e2_combobox_get_active (GtkWidget *combo) { /* GtkTreeIter iter; if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter)) { GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo)); gchar *retval; gtk_tree_model_get (model, &iter, 0, &retval, -1); return retval; } else return NULL; */ return (gtk_combo_box_get_active_text (GTK_COMBO_BOX (combo))); //FIXME only for text combos } /* * @brief backup all of the history data of @a combo into @a history @param combo the combo box to be interrogated @parm list list of history-items @return */ /*void e2_combobox_save_history (GtkWidget *combo, GList **list) { if (*list != NULL) e2_list_free_with_data (list); GtkTreeIter iter; GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo)); if (gtk_tree_model_get_iter_first (model, &iter)) { gchar *text; do { gtk_tree_model_get (model, &iter, 0, &text, -1); *list = g_list_append (*list, text); } while (gtk_tree_model_iter_next (model, &iter)); } } */ /** @brief append a GList to a GtkComboBox's history This function appends a GList of strings (@a list) to the history of a GtkComboBox. NOTE only for boxes created with gtk_combo_box_new_text () @param combo GtkComboBox to work on @param list GList of utf8 strings @return */ void e2_combobox_append_history (GtkWidget *combo, GList *list) { GList *tmp; for (tmp = list; tmp != NULL; tmp = g_list_next (tmp)) gtk_combo_box_append_text (GTK_COMBO_BOX (combo), tmp->data); } /** @brief append string array to a GtkComboBox's history NOTE only for boxes created with gtk_combo_box_new_text () @param combo combobox @param num number of strings in @a array @param array array of utf8 strings @return */ void e2_combobox_append_history_counted (GtkWidget *combo, guint num, gchar **array) { guint i; for (i = 0; i < num; i++) gtk_combo_box_append_text (GTK_COMBO_BOX (combo), array[i]); } /** @brief append NULL-terminated string array to a GtkComboBox's history This function appends array of strings @a strv to the history of a GtkComboBox. NOTE only for boxes created with gtk_combo_box_new_text () @param combo combobox @param strv a null-terminated array of utf8 strings @return */ void e2_combobox_append_history_strv (GtkWidget *combo, gchar **strv) { guint i; for (i = 0; strv[i] != NULL; i++) gtk_combo_box_append_text (GTK_COMBO_BOX (combo), strv[i]); } /** @brief create a GtkComboBox This function creates a GtkComboBox, respecting the @a flags, and then connects @a activate_cb to the "activate" signal of the GtkComboBox entry. @a activate_data will be a pointer to E2_CommandLineRuntime struct for the command line, or generally NULL for an ad-hoc combobox @a history size figures prominently in any (downstream) clearing of the combobox model, so it should not be static if there's a prospect of multiple widgets clearing the history @param activate_cb "activate" callback for the combobox entry, or NULL @param activate_data data to provide to the activate callback @param history history-list pointer, or NULL @param flags flags to influence the usage of the GtkComboBox @return the GtkComboBox that was created and packed */ GtkWidget *e2_combobox_get (gpointer activate_cb, gpointer activate_data, GList **history, E2_ComboBoxFlags flags) { GtkWidget *combo; if (flags & E2_COMBOBOX_HAS_ENTRY) { combo = gtk_combo_box_entry_new_text (); //enable deletions g_signal_connect (G_OBJECT (GTK_BIN (combo)->child), "key-press-event", G_CALLBACK (_e2_combobox_key_press_cb2), history); g_signal_connect (G_OBJECT (combo), "changed", G_CALLBACK (_e2_combobox_changed_cb), GINT_TO_POINTER (flags & E2_COMBOBOX_FOCUS_ON_CHANGE)); if (!(flags & E2_COMBOBOX_NO_AUTO_HISTORY)) g_signal_connect (G_OBJECT (GTK_BIN (combo)->child), "activate", G_CALLBACK (e2_combobox_activated_cb), GINT_TO_POINTER (flags & E2_COMBOBOX_ALLOW_DOUBLE)); } else combo = gtk_combo_box_new_text (); if (history != NULL) e2_combobox_append_history (combo, *history); //enable keyboard-initiated history-accessing g_signal_connect (G_OBJECT (GTK_BIN (combo)->child), "key-press-event", G_CALLBACK (_e2_combobox_key_press_cb), GINT_TO_POINTER (flags & E2_COMBOBOX_CYCLE_HISTORY)); //connect caller-specified callback for "activate" signal to combobox's entry if (activate_cb != NULL) g_signal_connect (G_OBJECT (GTK_BIN (combo)->child), "activate", G_CALLBACK (activate_cb), activate_data); if (!(flags & E2_COMBOBOX_MENU_STYLE)) { /* gchar *name = "list-style"; //work around gtk silliness, the 'appears-as-list' //is a style property instead of a widget property gchar *str = g_strconcat ("style \"", name, "-style\" { GtkComboBox::appears-as-list = 1 } widget \"*.", name, "\" style \"", name, "-style\"", NULL); gtk_rc_parse_string (str); g_free (str); gtk_widget_set_name (combo, name); */ gtk_rc_parse_string ( "style \"list-style-style\" { GtkComboBox::appears-as-list = 1 } " "widget \"*.list-style\" style \"list-style-style\""); gtk_widget_set_name (combo, "list-style"); } gtk_widget_show (combo); return combo; } /** @brief pack a new GtkComboBox into @a box This function creates a GtkComboBox using e2_combobox_get(), and then packs it into @a box and connects @a func to the "activate" signal. @param box parent box to add the GtkComboBox to @param expand packing property @param padding packing property @param activate_cb entry activate-signal callback, or NULL @param activate_data callback data @param history combo history list, or NULL @param flags flags to influence the usage of the GtkComboBox @return the GtkComboBox that was created and packed */ GtkWidget *e2_combobox_add (GtkWidget *box, gboolean expand, guint padding, gpointer activate_cb, gpointer activate_data, GList **history, E2_ComboBoxFlags flags) { GtkWidget *combo = e2_combobox_get (activate_cb, activate_data, history, flags); //add padding to the left & right side of the combobox GtkWidget *pad_box; if (GTK_IS_HBOX (box)) pad_box = gtk_vbox_new (FALSE, padding); else pad_box = gtk_hbox_new (FALSE, padding); //now add the combo to the padding container gtk_box_pack_start (GTK_BOX (pad_box), combo, TRUE, TRUE, padding); //add padding container to parent container gtk_box_pack_start (GTK_BOX (box), pad_box, expand, expand, padding); gtk_widget_show (pad_box); return combo; } emelfm2-0.4.1/src/utils/e2_button.c0000600000175000017500000003501310711017436016005 0ustar cairocairo/* $Id: e2_button.c 682 2007-10-28 05:33:18Z tpgww $ Copyright (C) 2004-2007 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "emelfm2.h" #include "e2_button.h" E2_Button E2_BUTTON_OK = { "",N_("_OK"),GTK_STOCK_OK, NULL, E2_BTN_DEFAULT,E2_BTN_DEFAULT,GTK_RESPONSE_OK}; E2_Button E2_BUTTON_CANCEL = { "",N_("_Cancel"),GTK_STOCK_CANCEL,NULL,0,0,GTK_RESPONSE_CANCEL}; E2_Button E2_BUTTON_YES = { "",N_("_Yes"),GTK_STOCK_YES, NULL, E2_BTN_DEFAULT,E2_BTN_DEFAULT,GTK_RESPONSE_YES}; E2_Button E2_BUTTON_NO = { "",N_("_No"),GTK_STOCK_NO,NULL,0,0,GTK_RESPONSE_NO}; E2_Button E2_BUTTON_YESTOALL = // { "",N_("Yes to _all"),GTK_STOCK_APPLY,NULL,0,0,E2_RESPONSE_YESTOALL}; { "",N_("Yes to _all"),"apply_all_"E2IP".png",NULL,0,0,E2_RESPONSE_YESTOALL}; E2_Button E2_BUTTON_NOTOALL = { "",N_("_Stop"),GTK_STOCK_STOP,NULL,0,0,E2_RESPONSE_NOTOALL}; E2_Button E2_BUTTON_APPLY = { "",N_("_Apply"),GTK_STOCK_APPLY,NULL,0,0,GTK_RESPONSE_APPLY}; E2_Button E2_BUTTON_APPLYTOALL = // { "",N_("_Apply to all"),GTK_STOCK_APPLY,NULL,0,0,E2_RESPONSE_APPLYTOALL}; { "",N_("_Apply to all"),"apply_all_"E2IP".png",NULL,0,0,E2_RESPONSE_APPLYTOALL}; E2_Button E2_BUTTON_REFRESH = { "",N_("_Refresh"),GTK_STOCK_REFRESH,NULL,0,0,E2_RESPONSE_REFRESH}; E2_Button E2_BUTTON_CLOSE = { "",N_("_Close"),GTK_STOCK_CLOSE,NULL,0,0,GTK_RESPONSE_CLOSE}; E2_Button E2_BUTTON_CREATE = { "",N_("C_reate"),GTK_STOCK_YES,NULL,0,0,E2_RESPONSE_CREATE}; //co-use with close/cancel E2_Button E2_BUTTON_REMOVE = { "",N_("_Remove"),GTK_STOCK_REMOVE,NULL,0,0,E2_RESPONSE_REMOVE}; //check no co-use with refresh void e2_button_setup_labels (void) { E2_BUTTON_OK.label = gettext (E2_BUTTON_OK.default_label); E2_BUTTON_CANCEL.label = gettext (E2_BUTTON_CANCEL.default_label); E2_BUTTON_YES.label = gettext (E2_BUTTON_YES.default_label); E2_BUTTON_NO.label = gettext (E2_BUTTON_NO.default_label); E2_BUTTON_YESTOALL.label = gettext (E2_BUTTON_YESTOALL.default_label); E2_BUTTON_NOTOALL.label = gettext (E2_BUTTON_NOTOALL.default_label); E2_BUTTON_APPLY.label = gettext (E2_BUTTON_APPLY.default_label); E2_BUTTON_APPLYTOALL.label = gettext (E2_BUTTON_APPLYTOALL.default_label); E2_BUTTON_REFRESH.label = gettext (E2_BUTTON_REFRESH.default_label); E2_BUTTON_CLOSE.label = gettext (E2_BUTTON_CLOSE.default_label); E2_BUTTON_CREATE.label = gettext (E2_BUTTON_CREATE.default_label); E2_BUTTON_REMOVE.label = gettext (E2_BUTTON_REMOVE.default_label); } /** @brief set new (or no) icon for existing button @a button e2 uses non-standard buttons, containing alignment containing h/vbox, the latter with image (if any) packed 1st @a stock may be a gtk stock name or a custom icon filename @param button the button widget to be updated @param stock string describing icon or NULL to clear the icon @return */ void e2_button_set_image (GtkWidget *button, gchar *stock) { if (e2_option_bool_get ("dialog-button-icons")) { GtkWidget *image = (stock != NULL) ? e2_widget_get_icon (stock, GTK_ICON_SIZE_BUTTON) : NULL; if (stock == NULL || image != NULL) { GtkWidget *bbox = GTK_BIN (GTK_BIN (button)->child)->child; GtkBoxChild *child1 = (GtkBoxChild *) GTK_BOX (bbox)->children->data; if (GTK_WIDGET_TYPE (child1->widget) == g_type_from_name ("GtkImage")) gtk_container_remove (GTK_CONTAINER (bbox), child1->widget); if (image != NULL) { gtk_box_pack_start (GTK_BOX (bbox), image, FALSE, FALSE, 0); gtk_box_reorder_child (GTK_BOX (bbox), image, 0); gtk_widget_show (image); } } } } /** @brief get button-button which can be default and focused @param label button label which may contain markup and/or mnemonic @param stock string describing icon, or NULL @param tip tooltip for the button, or NULL @param callback callback function for "clicked" signal, or NULL @param data data specified for the callback @return the button widget */ GtkWidget *e2_button_get (gchar *label, gchar *stock, gchar *tip, gpointer callback, gpointer data) { GtkWidget *button = e2_button_get_full (label, stock, GTK_ICON_SIZE_BUTTON, tip, callback, data, E2_BUTTON_CAN_DEFAULT | E2_BUTTON_CAN_FOCUS); gtk_widget_show_all (button); return button; } /** @brief create button This is used for toolbar buttons, and (maybe indirectly) for dialog buttons @param label button label which may contain markup and/or mnemonic, or NULL @param stock string describing icon, or NULL @param size code for size of button-icon @param tip tooltip for the button, or NULL @param callback callback function for "clicked" signal, or NULL @param data data specified for the callback @param flags flags indicating button properties @return the button widget */ GtkWidget *e2_button_get_full (gchar *label, gchar *stock, GtkIconSize size, gchar *tip, gpointer callback, gpointer data, E2_ButtonFlags flags) { GtkWidget *button = gtk_button_new (); if (flags & E2_BUTTON_CAN_DEFAULT) GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); else GTK_WIDGET_UNSET_FLAGS (button, GTK_CAN_DEFAULT); if (flags & E2_BUTTON_CAN_FOCUS) GTK_WIDGET_SET_FLAGS (button, GTK_CAN_FOCUS); else GTK_WIDGET_UNSET_FLAGS (button, GTK_CAN_FOCUS); if (tip != NULL) #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif button, tip); if (callback != NULL) g_signal_connect_data (G_OBJECT (button), "clicked", G_CALLBACK (callback), data, (flags & E2_BUTTON_FREE_DATA) ? (GClosureNotify) g_free : NULL, 0); GtkWidget *bbox; if (flags & E2_BUTTON_ICON_ABOVE_LABEL) bbox = gtk_vbox_new (FALSE, 2); //fixed gap, not E2_PADDING_XSMALL else bbox = gtk_hbox_new (FALSE, 2); gboolean need_align = FALSE; if (stock != NULL) { GtkWidget *img = e2_widget_get_icon (stock, size); if ((flags & E2_BUTTON_SHOW_MISSING_ICON) && (img == NULL)) img = e2_widget_get_icon (GTK_STOCK_MISSING_IMAGE, size); if (img != NULL) { gtk_box_pack_start (GTK_BOX (bbox), img, FALSE, FALSE, 0); need_align = TRUE; } } if ((label != NULL) && (*label != '\0')) { GtkWidget *lab = gtk_label_new (""); gtk_label_set_markup_with_mnemonic (GTK_LABEL (lab), label); gtk_box_pack_start (GTK_BOX (bbox), lab, TRUE, TRUE, 0); need_align = TRUE; } if (need_align) { GtkWidget *align = gtk_alignment_new (0.5, 0.5, 0.0, 0.0); gtk_container_add (GTK_CONTAINER (align), bbox); gtk_container_add (GTK_CONTAINER (button), align); } else //should never happen gtk_container_add (GTK_CONTAINER (button), bbox); return button; } /** @brief add button-sized button to the start of @a box @param box widget to hold the button @param exp TRUE to make the button expand with @a box @param pad padding between button and @a box @param label button label which may contain markup and/or mnemonic @param stock string describing icon, or NULL @param tip tooltip for the button, or NULL @param callback callback function for "clicked" signal, or NULL @param data data specified for the callback @return the button widget */ GtkWidget *e2_button_add (GtkWidget *box, gboolean exp, guint pad, gchar *label, gchar *stock, gchar *tip, gpointer callback, gpointer data) { GtkWidget *button = e2_button_get (label, stock, tip, callback, data); gtk_box_pack_start (GTK_BOX (box), button, exp, TRUE, pad); gtk_widget_show_all (button); return button; } /** @brief add button-sized button to the end of @a box @param box widget to hold the button @param exp TRUE to make the button expand with @a box @param pad padding between button and @a box @param label button label which may contain markup and/or mnemonic @param stock string describing icon, or NULL @param tip tooltip for the button, or NULL @param callback callback function for "clicked" signal, or NULL @param data data specified for the callback @return the button widget */ GtkWidget *e2_button_add_end (GtkWidget *box, gboolean exp, guint pad, gchar *label, gchar *stock, gchar *tip, gpointer callback, gpointer data) { GtkWidget *button = e2_button_get (label, stock, tip, callback, data); gtk_box_pack_end (GTK_BOX (box), button, exp, TRUE, pad); gtk_widget_show_all (button); return button; } /** @brief create check or toggle button with mmemonic label @param check TRUE for check button, FALSE for toggle button @param state initial state of the button @param label button label which may contain markup and/or mnemonic @param tip tooltip for the button, or NULL @param callback callback function for "toggled" signal, or NULL @param data data specified for the callback @return the button widget */ GtkWidget *e2_button_get_toggle (gboolean check, gboolean state, gchar *label, gchar *tip, gpointer callback, gpointer data) { GtkWidget *button; if (check) button = gtk_check_button_new_with_mnemonic (label); else button = gtk_toggle_button_new_with_mnemonic (label); GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); GTK_WIDGET_SET_FLAGS (button, GTK_CAN_FOCUS); if (state) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE); if (tip != NULL) #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif button, tip); if (callback != NULL) g_signal_connect (G_OBJECT (button), "toggled", G_CALLBACK (callback), data); gtk_widget_show (button); return button; } /** @brief add check or toggle button with mmemonic label to @a box @param box widget to hold the button @param check TRUE for check button, FALSE for toggle button @param state initial state of the button @param label button label which may contain mnemonic @param tip tooltip for the button, or NULL @param exp TRUE to make the button expand with @a box @param pad padding between button and @a box @param callback callback function for "toggled" signal, or NULL @param data data specified for the callback @return the button widget */ GtkWidget *e2_button_add_toggle (GtkWidget *box, gboolean check, gboolean state, gchar *label, gchar *tip, gboolean exp, guint pad, gpointer callback, gpointer data) { GtkWidget *button = e2_button_get_toggle (check, state, label, tip, callback, data); gtk_box_pack_start (GTK_BOX (box), button, exp, TRUE, pad); return button; } /** @brief add radio button with mmemonic label to @a box @param box widget to hold the button @param label button label which may contain mnemonic, or NULL if no label @param group the radio group, or NULL to start a group @param state the intitial state of the button @param exp TRUE to make the button expand with @a box @param pad padding between button and @a box @param callback callback function for "toggled" signal, or NULL @param data data specified for the callback @return the button widget */ GtkWidget *e2_button_add_radio (GtkWidget *box, gchar *label, GSList *group, gboolean state, gboolean exp, guint pad, gpointer callback, gpointer data) { GtkWidget *radio_button = (label != NULL) ? gtk_radio_button_new_with_mnemonic (group, label): gtk_radio_button_new (group); if (state) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio_button), TRUE); if (callback != NULL) g_signal_connect (G_OBJECT (radio_button), "toggled", G_CALLBACK (callback), data); gtk_box_pack_start (GTK_BOX (box), radio_button, exp, TRUE, pad); gtk_widget_show (radio_button); return radio_button; } /** @brief add button with mmemonic label to @a table @param table table to hold the button @param label button label which may contain mnemonic @param callback callback function for "clicked" signal, or NULL @param data data specified for the callback @param left @param right @param top @param bottom @return the button widget */ GtkWidget *e2_button_add_to_table ( GtkWidget *table, gchar *label, gpointer callback, gpointer data, gint left, gint right, gint top, gint bottom) { GtkWidget *button; button = gtk_button_new_with_mnemonic (label); if (callback != NULL) g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (callback), data); gtk_table_attach_defaults (GTK_TABLE (table), button, left, right, top, bottom); gtk_widget_show (button); return button; } /** @brief add check button with optional mmemonic label to @a table @param table table to hold the button @param label button label which may contain mnemonic, or NULL @param state the intitial state of the button @param callback callback function for "toggled" signal, or NULL @param data data specified for the callback @param left @param right @param top @param bottom @return the button widget */ GtkWidget *e2_button_add_toggle_to_table ( GtkWidget *table, gchar *label, gboolean state, gpointer callback, gpointer data, gint left, gint right, gint top, gint bottom) { GtkWidget *check_button; check_button = (label == NULL) ? gtk_check_button_new () : gtk_check_button_new_with_mnemonic (label); if (state) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button), TRUE); if (callback != NULL) g_signal_connect (G_OBJECT (check_button), "toggled", G_CALLBACK (callback), data); gtk_table_attach_defaults (GTK_TABLE (table), check_button, left, right, top, bottom); gtk_widget_show (check_button); return check_button; } /** @brief add check button with optional mmemonic label to @a table @param table table to hold the button @param label button label which may contain mnemonic @param group radio group, or NULL to start a group @param state the intitial state of the button @param callback callback function for "clicked" signal, or NULL @param data data specified for the callback @param left @param right @param top @param bottom @return the button widget */ GtkWidget *e2_button_add_radio_to_table ( GtkWidget *table, gchar *label, GSList *group, gboolean state, gpointer callback, gpointer data, gint left, gint right, gint top, gint bottom) { GtkWidget *radio_button = gtk_radio_button_new_with_mnemonic (group, label); if (state) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio_button), TRUE); if (callback != NULL) g_signal_connect (G_OBJECT (radio_button), "toggled", G_CALLBACK (callback), data); gtk_table_attach_defaults (GTK_TABLE (table), radio_button, left, right, top, bottom); gtk_widget_show (radio_button); return radio_button; } emelfm2-0.4.1/src/utils/e2_textiter.c0000600000175000017500000003712310643544426016356 0ustar cairocairo/* $Id: e2_textiter.c 469 2007-07-06 22:58:30Z tpgww $ Portions copyright (C) 2003-2007 tooar Portions copyright (C) 2000, 2002 Paolo Maggi Portions copyright (C) 2002, 2003 Jeroen Zwartepoorte This sourcecode is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This sourcecode is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with emelFM2. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /* Parts of this file are from the gedit or glimmer projects. */ #include "emelfm2.h" #include #include "e2_textiter.h" #define GTK_TEXT_UNKNOWN_CHAR 0xFFFC /** @brief move foward @a count characters, ignoring any that match supplied flags @param iter text iter referring to current position @param count the no. of chars to move @param skip_invisible TRUE to skip invisible chars @param skip_nontext TRUE to skip non-text chars @param skip_decomp TRUE to skip canonical decompositions @return */ static void _e2_textiter_forward_chars_with_skipping ( GtkTextIter *iter, gint count, gboolean skip_invisible, gboolean skip_nontext, gboolean skip_decomp) { gint i; g_return_if_fail (count >= 0); i = count; while (i > 0) { gboolean ignored = FALSE; if (skip_nontext && gtk_text_iter_get_char (iter) == GTK_TEXT_UNKNOWN_CHAR) ignored = TRUE; if (!ignored && skip_invisible //&& _gtk_text_btree_char_is_invisible (iter) ) ignored = TRUE; if (!ignored && skip_decomp) { /* being utf8-correct sucks; this accounts for extra offsets coming from canonical decompositions of utf8 characters (e.g. accented characters) which g_utf8_normalize() performs */ gunichar *decomp; gsize decomp_len; decomp = g_unicode_canonical_decomposition ( gtk_text_iter_get_char (iter), &decomp_len); i -= (decomp_len - 1); g_free (decomp); } gtk_text_iter_forward_char (iter); if (!ignored) --i; } } /** @brief scan forwards from @a start for text which matches @a lines This performs line-by-line case-insensitive comparisons It may be re-entrant, when @a lines has > 1 member @param start pointer to textiter in buffer where the search is to commence @param lines array of \n-trailing line-strings, together comprising the search string @param visible_only TRUE to ignore hidden chars in the text @param slice TRUE to include "unknown" (0xFFFC) chars in the text @param match_start textiter to store the start of a matching string, or NULL when re-entering to process line(s) after 1st @param match_end textiter store the start of a matching string, or NULL @return TRUE if a match was found */ static gboolean _e2_textiter_forward_lines_match ( const GtkTextIter *start, const gchar **lines, gboolean visible_only, gboolean slice, GtkTextIter *match_start, GtkTextIter *match_end) { GtkTextIter next; gchar *line_text; const gchar *found; gint offset; if (*lines == NULL || **lines == '\0') { //nothing to match, so everything matches if (match_start) *match_start = *start; if (match_end) *match_end = *start; return TRUE; } //CHECKME why break search into lines ? next = *start; if (!gtk_text_iter_forward_line (&next)) { if (gtk_text_iter_is_end (start)) return FALSE; else { next = *start; gtk_text_iter_forward_to_end (&next); //CHECKME } } else if (gtk_text_iter_equal (start, &next)) //no more text in buffer return FALSE; if (slice) { if (visible_only) line_text = gtk_text_iter_get_visible_slice (start, &next); else line_text = gtk_text_iter_get_slice (start, &next); } else { if (visible_only) line_text = gtk_text_iter_get_visible_text (start, &next); else line_text = gtk_text_iter_get_text (start, &next); } if (match_start != NULL) //if this is the first line we're matching { found = e2_utf8_strcasestr (line_text, *lines); } else { //not the first line, we have to match from the start of the line if (e2_utf8_caseless_match (line_text, *lines, -1, -1)) found = line_text; else found = NULL; } if (found == NULL) { g_free (line_text); return FALSE; } //get offset to start of search string offset = g_utf8_strlen (line_text, found - line_text); next = *start; /* If match start needs to be returned, set it to the start of the search string */ _e2_textiter_forward_chars_with_skipping (&next, offset, visible_only, !slice, FALSE); if (match_start != NULL) { *match_start = next; } //go to end of search string _e2_textiter_forward_chars_with_skipping (&next, g_utf8_strlen (*lines, -1), visible_only, !slice, TRUE); g_free (line_text); ++lines; if (match_end != NULL) *match_end = next; /* try to match the rest of the lines forward, passing NULL for match_start so lines_match will try to match the entire line */ return _e2_textiter_forward_lines_match (&next, lines, visible_only, slice, NULL, match_end); } /** @brief scan backwards from @a start for text which matches @a lines This performs line-by-line case-insensitive comparisons @param start pointer to textiter in buffer where the search is to commence @param lines array of \n-trailing line-strings, together comprising the search string @param visible_only TRUE to ignore hidden chars in the text @param slice TRUE to include "unknown" (0xFFFC) chars in the text @param match_start textiter to store the start of a matching string, or NULL when re-entering to parse lines after 1st @param match_end textiter store the start of a matching string, or NULL @return TRUE if a match was found */ static gboolean _e2_textiter_backward_lines_match ( const GtkTextIter *start, const gchar **lines, gboolean visible_only, gboolean slice, GtkTextIter *match_start, GtkTextIter *match_end) { GtkTextIter line, next; gchar *line_text; const gchar *found; gint offset; if (*lines == NULL || **lines == '\0') { //nothing to match, so everything matches if (match_start) *match_start = *start; if (match_end) *match_end = *start; return TRUE; } line = next = *start; if (gtk_text_iter_get_line_offset (&next) == 0) //CHECKME why break search into lines ? { if (!gtk_text_iter_backward_line (&next)) return FALSE; } else gtk_text_iter_set_line_offset (&next, 0); if (slice) { if (visible_only) line_text = gtk_text_iter_get_visible_slice (&next, &line); else line_text = gtk_text_iter_get_slice (&next, &line); } else { if (visible_only) line_text = gtk_text_iter_get_visible_text (&next, &line); else line_text = gtk_text_iter_get_text (&next, &line); } if (match_start != NULL) //if this is the first line we're matching { found = e2_utf8_strrcasestr (line_text, *lines); } else { //not the first line, we have to match from the start of the line if (e2_utf8_caseless_match (line_text, *lines, -1, -1)) found = line_text; else found = NULL; } if (found == NULL) { g_free (line_text); return FALSE; } //get offset to start of search string offset = g_utf8_strlen (line_text, found - line_text); _e2_textiter_forward_chars_with_skipping (&next, offset, visible_only, !slice, FALSE); //if match start needs to be returned, set it to the start of the search string if (match_start != NULL) { *match_start = next; } //go to end of search string _e2_textiter_forward_chars_with_skipping (&next, g_utf8_strlen (*lines, -1), visible_only, !slice, TRUE); g_free (line_text); ++lines; if (match_end != NULL) *match_end = next; /* try to match the rest of the lines forward, passing NULL for match_start so lines_match will try to match the entire line */ return _e2_textiter_forward_lines_match (&next, lines, visible_only, slice, NULL, match_end); } /** @brief Search forward from @a iter to try to to find next match of @a str in textbuffer This is like gtk_text_iter_forward_search(), but supports case-insensitive and whole-word searching. Any match is returned by setting @a match_start to the first character of the match and @a match_end to the first character after the match. The search will not continue past @a limit. Note that a search is a linear or O(n) operation, so you may wish to use @a limit to avoid locking up your UI on large buffers. If the GTK_SOURCE_SEARCH_VISIBLE_ONLY flag is present, the match may have invisible text interspersed in @a str. i.e. @a str will be a possibly-noncontiguous subset of the matched range. Similarly, if you specify GTK_SOURCE_SEARCH_TEXT_ONLY, the match may have pixbufs or child widgets inside the matched range. If these flags are not given, the match must be exact; the special 0xFFFC character in @a str will match embedded pixbufs or child widgets. If you specify the GTK_SOURCE_SEARCH_CASE_INSENSITIVE flag, the text will be matched regardless of its case. @param iter GtkTextIter where the search begins @param str search string, may include 1 or more \n @param flags flags affecting how the search is done @param match_start return location for start of match, or NULL @param match_end return location for end of match, or NULL @param limit upper bound for the match start, or NULL for the end of the buffer @return TRUE if a match was found */ gboolean e2_iter_forward_search ( const GtkTextIter *iter, const gchar *str, E2TextSearchFlags flags, GtkTextIter *match_start, GtkTextIter *match_end, const GtkTextIter *limit) { gboolean visible_only, slice, retval; GtkTextIter match, search; g_return_val_if_fail (iter != NULL, FALSE); g_return_val_if_fail (str != NULL, FALSE); if (!(flags & E2_SEARCH_CASE_INSENSITIVE)) { search = *iter; rescan: retval = gtk_text_iter_forward_search (&search, str, flags, match_start, match_end, limit); if (retval && (flags & E2_SEARCH_WHOLE_WORD) && (!gtk_text_iter_starts_word (match_start) //to allow incremental searching, whole_words option causes only a check //for word start here, the end-check is done when highlighting //|| !gtk_text_iter_ends_word (match_end) )) { search = *match_start; if (gtk_text_iter_forward_char (&search)) goto rescan; retval = FALSE; } return retval; } if (limit != NULL && gtk_text_iter_compare (iter, limit) > 0) return FALSE; if (*str == '\0') //matching nothing { //if we can move one char, return that location for a match match = *iter; if (gtk_text_iter_forward_char (&match)) { if (limit == NULL || gtk_text_iter_compare (&match, limit) <= 0) { if (match_start != NULL) *match_start = match; if (match_end != NULL) *match_end = match; return TRUE; } } return FALSE; } //split search string into lines gchar **lines = e2_utils_str_breakup (str, "\n", -1); if (lines == NULL) return FALSE; //FIXME warn user about error visible_only = (flags & E2_SEARCH_VISIBLE_ONLY) != 0; slice = (flags & E2_SEARCH_TEXT_ONLY) == 0; retval = FALSE; search = *iter; do { /* This loop has an inefficient worst-case, where gtk_text_iter_get_text() is called repeatedly on a single line */ GtkTextIter end; rescan2: if (limit != NULL && gtk_text_iter_compare (&search, limit) > 0) break; if (_e2_textiter_forward_lines_match (&search, (const gchar**)lines, visible_only, slice, &match, &end)) { if (limit == NULL || gtk_text_iter_compare (&end, limit) <= 0) { if ((flags & E2_SEARCH_WHOLE_WORD) && (!gtk_text_iter_starts_word (&match) //see comment above re end-checking when highlighting || !gtk_text_iter_ends_word (&end) )) { search = match; if (gtk_text_iter_forward_char (&search)) goto rescan2; } else { retval = TRUE; if (match_start != NULL) *match_start = match; if (match_end != NULL) *match_end = end; } } break; } } while (gtk_text_iter_forward_line (&search)); g_strfreev (lines); return retval; } /** @brief Search backward from @a iter to try to find next match of @a str in textbuffer This is like gtk_text_iter_backward_search(), but supports case-insensitive and whole-word searching. See comments for e2_iter_forward_search(). @param iter a GtkTextIter where the search begins @param str search string, may include 1 or more \n @param flags bitmask of flags affecting the search @param match_start return location for start of match, or NULL @param match_end return location for end of match, or NULL @param limit lower bound of match end, or NULL for start of buffer @return TRUE if a match was found */ gboolean e2_iter_backward_search ( const GtkTextIter *iter, const gchar *str, E2TextSearchFlags flags, GtkTextIter *match_start, GtkTextIter *match_end, const GtkTextIter *limit) { gboolean visible_only, slice, retval; GtkTextIter search, match; g_return_val_if_fail (iter != NULL, FALSE); g_return_val_if_fail (str != NULL, FALSE); if (!(flags & E2_SEARCH_CASE_INSENSITIVE)) { search = *iter; rescan: retval = gtk_text_iter_backward_search (&search, str, flags, match_start, match_end, limit); if (retval && (flags & E2_SEARCH_WHOLE_WORD) && (!gtk_text_iter_starts_word (match_start) //see comment above re end-checking when highlighting || !gtk_text_iter_ends_word (match_end) )) { search = *match_start; if (gtk_text_iter_backward_char (&search)) goto rescan; retval = FALSE; } return retval; } if (limit != NULL && gtk_text_iter_compare (iter, limit) < 0) return FALSE; if (*str == '\0') //matching nothing { //if we can move one char, return that location for the match match = *iter; if (gtk_text_iter_backward_char (&match)) { if (limit == NULL || gtk_text_iter_compare (&match, limit) >= 0) { if (match_start != NULL) *match_start = match; if (match_end != NULL) *match_end = match; return TRUE; } } return FALSE; } //split search string into lines gchar **lines = e2_utils_str_breakup (str, "\n", -1); if (lines == NULL) return FALSE; //FIXME warn user about error visible_only = (flags & E2_SEARCH_VISIBLE_ONLY) != 0; slice = (flags & E2_SEARCH_TEXT_ONLY) == 0; retval = FALSE; search = *iter; while (TRUE) { /* This loop has an inefficient worst-case, where gtk_text_iter_get_text() is called repeatedly on each single line */ GtkTextIter end; rescan2: if (limit != NULL && gtk_text_iter_compare (&search, limit) < 0) break; if (_e2_textiter_backward_lines_match (&search, (const gchar**)lines, visible_only, slice, &match, &end)) { if (limit == NULL || gtk_text_iter_compare (&end, limit) >= 0) { if ((flags & E2_SEARCH_WHOLE_WORD) && (!gtk_text_iter_starts_word (&match) //see comment above re end-checking when highlighting || !gtk_text_iter_ends_word (&end) )) { search = match; if (gtk_text_iter_backward_char (&search)) goto rescan2; } else { retval = TRUE; if (match_start) *match_start = match; if (match_end) *match_end = end; } } break; } if (gtk_text_iter_get_line_offset (&search) == 0) //at start of line { if (!gtk_text_iter_backward_line (&search)) //can't move to start of previous line break; } else gtk_text_iter_set_line_offset (&search, 0); //go to start of current line and check again } g_strfreev (lines); return retval; } emelfm2-0.4.1/src/utils/e2_combobox.h0000600000175000017500000000575411010340377016314 0ustar cairocairo/* $Id: e2_combobox.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/utils/e2_combobox.h @brief GtkComboBox utilities header This is the header file for the GtkComboBox utilities. */ #ifndef __E2_COMBOBOX_H__ #define __E2_COMBOBOX_H__ /** @brief These flags influence the GtkComboBox creation in several ways. These flags influence the GtkComboBox creation in several ways when creating them with e2_combobox_get() or e2_combobox_add(). */ typedef enum { E2_COMBOBOX_HAS_ENTRY = 1, //the GtkComboBox has an entry //(you should not use menu-style in that case) E2_COMBOBOX_MENU_STYLE = 1<<1, //the drop down widget is a menu instead of a list E2_COMBOBOX_FOCUS_ON_CHANGE = 1<<2, //if the GtkComboBox emits the changed signal, //grab the focus for it E2_COMBOBOX_NO_AUTO_HISTORY = 1<<3, //prevent automatic update of history list //of GtkComboBox which has an entry E2_COMBOBOX_ALLOW_DOUBLE = 1<<4, //allow the same history entries to be added //more than once E2_COMBOBOX_CYCLE_HISTORY = 1<<5, //if the GtkComboBox has an entry, the history //will (if necessary) be cycled upon pressing GDK_UP or GDK_DOWN } E2_ComboBoxFlags; //callbacks void e2_combobox_activated_cb (GtkWidget *entry, gpointer data); //public void e2_combobox_block_changed (GtkWidget *combo); void e2_combobox_unblock_changed (GtkWidget *combo); void e2_combobox_set_active (GtkWidget *combo, gint num); gchar *e2_combobox_get_active (GtkWidget *combo); //void e2_combobox_save_history (GtkWidget *combo, GList **list); void e2_combobox_append_history (GtkWidget *combo, GList *list); void e2_combobox_append_history_counted (GtkWidget *combo, guint num, gchar **array); void e2_combobox_append_history_strv (GtkWidget *combo, gchar **strv); gboolean e2_combobox_has_history (GtkComboBox *combo); //void e2_combobox_select_last (GtkWidget *combo); //gchar *e2_combobox_last_text (GtkComboBox *combo); gchar *e2_combobox_first_text (GtkComboBox *combo); GtkWidget *e2_combobox_get (gpointer activate_cb, gpointer activate_data, GList **history, E2_ComboBoxFlags flags); GtkWidget *e2_combobox_add (GtkWidget *box, gboolean expand, guint padding, gpointer activate_cb, gpointer activate_data, GList **history, E2_ComboBoxFlags flags); #endif //ndef __E2_COMBOBOX_H__ emelfm2-0.4.1/src/utils/e2_utf8.h0000600000175000017500000001104111010340377015354 0ustar cairocairo/* $Id: e2_utf8.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/utils/e2_utf8.h @brief utf8 string utilities header This is the header file for the utf8 string utility functions. */ #ifndef __E2_UTF8_H__ #define __E2_UTF8_H__ //support for file paths/names ONLY in utf-8 //GLib and GTK+ by default assume that filenames are //encoded in UTF-8 rather than some other encoding that //is specific to a locale. ASCII is compatible with UTF-8 // the ..FILENAME_FROM.. macros make a utf8 name from a locale-encoded one // the ..FILENAME_TO.. macros make a locale-encoded name from a utf8 one //#define E2_FILES_UTF8ONLY - see Makefile #ifdef E2_FILES_UTF8ONLY #define F_DISPLAYNAME_FROM_LOCALE(d) d //the D_ versions are for when a duplicate is needed //regardless of whether E2_FILES_UTF8ONLY is defined or not #define D_FILENAME_FROM_LOCALE(d) g_strdup(d) #define D_FILENAME_TO_LOCALE(d) g_strdup(d) //the F_ versions are for when there is an associated free of //the created duplicate, when E2_FILES_UTF8ONLY is not defined #define F_FILENAME_FROM_LOCALE(d) d #define F_FILENAME_TO_LOCALE(d) d #define F_FREE(d) #else /* this version of defines applies when build-time conversion code is used #define D_FILENAME_FROM_LOCALE(d) e2_utf8_filename_from_locale(d) #define D_FILENAME_TO_LOCALE(d) e2_utf8_filename_to_locale(d) //#define F_FILENAME_FROM_LOCALE(d) _free_me_=e2_utf8_filename_from_locale(d) //#define F_FILENAME_TO_LOCALE(d) _free_me_=e2_utf8_filename_to_locale(d) //#define F_FREE g_free(_free_me_) #define F_FILENAME_FROM_LOCALE(d) e2_utf8_filename_from_locale(d) #define F_FILENAME_TO_LOCALE(d) e2_utf8_filename_to_locale(d) #define F_FREE(d) g_free(d) gchar *e2_utf8_filename_from_locale_backup (const gchar *d); gchar *e2_utf8_filename_to_locale_backup (const gchar *d); */ // this version of defines applies when run-time conversion code is used #define F_DISPLAYNAME_FROM_LOCALE(d) (*e2_display_from_locale)(d) #define D_FILENAME_FROM_LOCALE(d) (*e2_fname_dupfrom_locale)(d) #define D_FILENAME_TO_LOCALE(d) (*e2_fname_dupto_locale)(d) #define F_FILENAME_FROM_LOCALE(d) (*e2_fname_from_locale)(d) #define F_FILENAME_TO_LOCALE(d) (*e2_fname_to_locale)(d) #define F_FREE(d) (*e2_fname_free)(d) //pointers to functions used to convert (or not) coding of file path/name strings gchar *(*e2_display_from_locale) (const gchar *); gchar *(*e2_fname_to_locale) (const gchar *); gchar *(*e2_fname_from_locale) (const gchar *); gchar *(*e2_fname_dupto_locale) (const gchar *); gchar *(*e2_fname_dupfrom_locale) (const gchar *); void (*e2_fname_free) (gpointer); //pointers set to these functions when coding conversion is not needed gchar *e2_utf8_not_converted (const gchar *); void e2_utf8_not_freed (gpointer); // #endif #define NCHR(p) p=g_utf8_next_char(p) #define PCHR(p) p=g_utf8_prev_char(p) //inline gchar *e2_utf8_filename_from_locale (const gchar *d); //inline gchar *e2_utf8_filename_to_locale (const gchar *d); //inline gchar *e2_utf8_to_locale (const gchar *d); gchar *e2_utf8_to_locale_fallback (const gchar *d); gchar *e2_utf8_from_locale (const gchar *d); gchar *e2_utf8_from_locale_fallback (const gchar *d); //macro for inlining speedier conversions #define e2_utf8_from_locale_fast(d) \ (g_utf8_validate (d, -1, NULL) ) ? g_strdup (d) : \ ((__utf__ = g_locale_to_utf8 (d, -1, NULL, NULL, NULL)) != NULL) ? __utf__ : \ e2_utf8_from_locale_fallback (d); const gchar *e2_utf8_strcasestr (const gchar *haystack, const gchar *needle); const gchar *e2_utf8_strrcasestr (const gchar *haystack, const gchar *needle); gboolean e2_utf8_caseless_match (const gchar *s1, const gchar *s2, gssize n1, gssize n2); gchar *e2_utf8_ndup (const gchar *str, glong num); gchar *e2_utf8_escape (const gchar *str, gunichar c); gchar *e2_utf8_unescape (const gchar *str, gunichar c); gboolean e2_utf8_detect_charset (const guchar *text, const gchar **charset); #endif // ndef __E2_UTF8_H__ emelfm2-0.4.1/src/utils/e2_list.c0000600000175000017500000001561110657730113015452 0ustar cairocairo/* $Id: e2_list.c 602 2007-08-13 01:13:47Z tpgww $ Copyright (C) 2004-2007 tooar Portions copyright (C) 1999 Michael Clark. This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/utils/e2_list.c @brief glist utilities glist utility-functions */ #include "emelfm2.h" #include /** @brief compare function for finding @a b in a GList of strings @param a string data for current member of the glist being scanned @param b string to find in the glist @return 0 if the strings are equal */ gint e2_list_strcmp (const gchar *a, const gchar *b) { return !g_str_equal (a, b); } /** @brief update history-list for string @a latest @a latest will be prepended to the list. @param latest string to be listed @param history store for history list pointer @param cur store for new history length, or NULL @param max the maximum length of the list, or 0 for no limit @param doubl TRUE to allow multiple-instances of @a command in the list @return */ void e2_list_update_history (const gchar *latest, GList **history, guint *cur, guint max, gboolean doubl) { GList *tmp = doubl ? NULL : g_list_find_custom (*history, latest, (GCompareFunc) e2_list_strcmp); if (tmp != NULL) { //found the string to be added in history already if (tmp != *history) { //somewhere later in the list *history = g_list_remove_link (*history, tmp); *history = g_list_concat (tmp, *history); } //no net change of list length } else { //multiples allowed, or new entry tmp = *history; if (tmp == NULL || !g_str_equal ((gchar *)tmp->data, latest)) { *history = g_list_prepend (*history, g_strdup (latest)); if (max > 0 && g_list_length (*history) > max) { tmp = g_list_last (*history); g_free (tmp->data); *history = g_list_delete_link (*history, tmp); } } } if (cur != NULL) *cur = g_list_length (*history); } /** @brief convert a list of strings to an array of strings @param list GList with gchar pointers as data @return NULL-terminated array of strings from @a list */ gchar **e2_list_to_strv (GList *list) { guint len = g_list_length (list); gchar **strv = g_malloc ((len + 1) * sizeof (gpointer)); //try_malloc gint i; GList *l = list; for (i = 0; i < len; i++) { strv[i] = l->data; l = g_list_next (l); } strv[len] = NULL; return strv; } /** @brief copy a GList of strings This function copies a GList and also copies the data of each element with g_strdup. @param list the GList to copy @return new GList that has to be freed */ GList *e2_list_copy_with_data (GList *list) { GList *copy = NULL; GList *tmp; for (tmp = g_list_first (list); tmp != NULL; tmp = g_list_next (tmp)) { copy = g_list_append (copy, g_strdup (tmp->data)); } return copy; } /** @brief clear list of strings The list pointer is set to NULL after clearing @param list store for list pointer @return */ void e2_list_free_with_data (GList **list) { if ((list != NULL) && (*list != NULL)) { g_list_foreach (*list, (GFunc) g_free, NULL); g_list_free (*list); *list = NULL; } } /** @brief clear listed strings, but not the list itself @param list store for list pointer @return */ /* UNUSED void e2_list_free_data_only (GList **list) { if ((list != NULL) && (*list != NULL)) { g_list_foreach (*list, (GFunc) g_free, NULL); } } */ /** @brief find member of list @a list, whose data is @a search_text @param list pointer to list to be scanned @param search_text the text to find @return pointer to list member that holds @a search_text, or NULL */ /* UNUSED GList *e2_list_find_data_string (GList *list, gchar *search_text) { GList *tmp; gchar *curr; for (tmp = list; tmp != NULL; tmp = tmp->next) { curr = tmp->data; if (g_str_equal (curr, search_text)) return tmp; } return NULL; } */ /** @brief break @a list into two parts, the second part starting at index @a breaker @param list pointer to list to be processed, may be NULL If @a part1 == @a part2, @a part1 will simply be overwritten If @a part1 &/| @a part2 is NULL, corresponding list will be cleared, any data will leak @param breaker index of a member of @a list which will start the second part @param part1 store for pointer to first broken part, may be NULL @param part2 store for pointer to second broken part, may be NULL @return */ void e2_list_nth_break (GList **list, guint breaker, GList **part1, GList **part2) { if (*list == NULL) e2_list_break (NULL, NULL, FALSE, part1, part2); else if (breaker > g_list_length (*list)) e2_list_break (list, NULL, TRUE, part1, part2); else e2_list_break (list, g_list_nth (*list, breaker), FALSE, part1, part2); } /** @brief break @a list into two parts, the second part starting with @a breaker @param list pointer to list to be processed, may be NULL @a part1 or @a part2 may be address of @a list If @a part1 == @a part2, @a part1 will simply be overwritten If @a part1 &/| @a part2 is NULL, corresponding list will be cleared, any data will leak @param breaker pointer to member of @a list which will start the second part, may be NULL @param nullatend if TRUE, and @a breqak is NULL, it's at the end of @a list, i.e. from a nextward scan @param part1 store for pointer to first broken part, may be NULL @param part2 store for pointer to second broken part, may be NULL @return */ void e2_list_break (GList **list, GList *breaker, gboolean nullatend, GList **part1, GList **part2) { if (*list != NULL) { if (breaker != NULL) { GList *prev = breaker->prev; if (prev != NULL) { prev->next = NULL; breaker->prev = NULL; if (part1 != NULL) *part1 = *list; else { g_list_free (*list); //any data will leak *list = NULL; } } if (part2 != NULL) *part2 = breaker; //g_list_concat (NULL, breaker); else { g_list_free (breaker); //any data will leak // breaker = NULL; } } else //NULL breaker { if (nullatend) { if (part1 != NULL) *part1 = *list; else { g_list_free (*list); //any data will leak *list = NULL; } if (part2 != NULL) *part2 = NULL; } else { if (part1 != NULL) *part1 = NULL; if (part2 != NULL) *part2 = *list; else { g_list_free (*list); //any data will leak *list = NULL; } } } } else //list doesn't exist { if (part1 != NULL) *part1 = NULL; if (part2 != NULL) *part2 = NULL; } } emelfm2-0.4.1/src/utils/e2_list.h0000600000175000017500000000271011010340377015444 0ustar cairocairo/* $Id: e2_list.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelfm2. emelfm2 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 3, or (at your option) any later version. emelfm2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_LIST_H__ #define __E2_LIST_H__ #include "emelfm2.h" gint e2_list_strcmp (const gchar *a, const gchar *b); void e2_list_update_history (const gchar *command, GList **history, guint *cur, guint max, gboolean doubl); gchar **e2_list_to_strv (GList *list); GList *e2_list_copy_with_data (GList *list); void e2_list_free_with_data (GList **list); //void e2_list_free_data_only (GList **list); //GList *e2_list_find_data_string (GList *list, gchar *search_text); void e2_list_nth_break (GList **list, guint breaker, GList **part1, GList **part2); void e2_list_break (GList **list, GList *breaker, gboolean nullatend, GList **part1, GList **part2); #endif //ndef __E2_LIST_H__ emelfm2-0.4.1/src/utils/e2_button.h0000600000175000017500000000744111010340377016012 0ustar cairocairo/* $Id: e2_button.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_BUTTON_H__ #define __E2_BUTTON_H__ typedef enum { E2_BTN_DEFAULT = 1 << 0, E2_BTN_HIDDEN = 1 << 1, E2_BTN_GREY = 1 << 2, E2_BTN_RENAMED = 1 << 3, E2_BTN_TIPPED = 1 << 4, //the button has a custom tip E2_BTN_FREETIP = 1 << 5, //the button has a custom tip to be freed after use } E2_ButtonType; typedef struct _E2_Button { gchar *label; gchar *default_label; gchar *stock; //only used for dialogs gchar *tip; //custom tip or NULL E2_ButtonType showflags; E2_ButtonType default_flags; gint response; } E2_Button; typedef enum { E2_BUTTON_FREE_DATA = 1 << 0, E2_BUTTON_ICON_ABOVE_LABEL = 1 << 1, E2_BUTTON_CAN_FOCUS = 1 << 2, E2_BUTTON_CAN_DEFAULT = 1 << 3, E2_BUTTON_SHOW_MISSING_ICON = 1 << 4 } E2_ButtonFlags; E2_Button E2_BUTTON_OK; E2_Button E2_BUTTON_CANCEL; E2_Button E2_BUTTON_YES; E2_Button E2_BUTTON_NO; E2_Button E2_BUTTON_YESTOALL; E2_Button E2_BUTTON_NOTOALL; E2_Button E2_BUTTON_APPLY; E2_Button E2_BUTTON_APPLYTOALL; E2_Button E2_BUTTON_REFRESH; E2_Button E2_BUTTON_CLOSE; E2_Button E2_BUTTON_CREATE; E2_Button E2_BUTTON_REMOVE; // custom button signal responses, used in dialogs #define E2_RESPONSE_YESTOALL 111 #define E2_RESPONSE_APPLYTOALL 112 #define E2_RESPONSE_NOTOALL 113 #define E2_RESPONSE_CREATE 114 #define E2_RESPONSE_REFRESH 115 #define E2_RESPONSE_REMOVE 116 #define E2_RESPONSE_FIND 117 #define E2_RESPONSE_USER1 120 #define E2_RESPONSE_USER2 121 #define E2_RESPONSE_USER3 122 void e2_button_setup_labels (void); void e2_button_set_image (GtkWidget *button, gchar *stock); GtkWidget *e2_button_get (gchar *label, gchar *stock, gchar *tip, gpointer callback, gpointer data); GtkWidget *e2_button_get_full (gchar *label, gchar *stock, GtkIconSize size, gchar *tip, gpointer callback, gpointer data, E2_ButtonFlags flags); GtkWidget *e2_button_add (GtkWidget *box, gboolean exp, guint pad, gchar *label, gchar *stock, gchar *tip, gpointer callback, gpointer data); GtkWidget *e2_button_add_end (GtkWidget *box, gboolean exp, guint pad, gchar *label, gchar *stock, gchar *tip, gpointer callback, gpointer data); GtkWidget *e2_button_get_toggle (gboolean check, gboolean state, gchar *label, gchar *tip, gpointer func, gpointer data); GtkWidget *e2_button_add_toggle (GtkWidget *box, gboolean check, gboolean state, gchar *label, gchar *tip, gboolean exp, guint pad, gpointer func, gpointer data); GtkWidget *e2_button_add_radio (GtkWidget *box, gchar *label, GSList *group, gboolean state, gboolean exp, guint pad, gpointer func, gpointer data); GtkWidget *e2_button_add_to_table (GtkWidget *table, gchar *label, gpointer func, gpointer data, gint left, gint right, gint top, gint bottom); GtkWidget *e2_button_add_toggle_to_table(GtkWidget *table, gchar *label, gboolean state, gpointer func, gpointer data, gint left, gint right, gint top, gint bottom); GtkWidget *e2_button_add_radio_to_table (GtkWidget *table, gchar *label, GSList *group, gboolean state, gpointer func, gpointer data, gint left, gint right, gint top, gint bottom); #endif //ndef __E2_BUTTON_H__ emelfm2-0.4.1/src/utils/e2_utf8.c0000600000175000017500000006511511012145246015362 0ustar cairocairo/* $Id: e2_utf8.c 881 2008-05-12 22:54:30Z tpgww $ Copyright (C) 2004-2008 tooar Portion _maybe_ copyright (C) 2004-2005 Tarot Osuji This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/utils/e2_utf8.c @brief utf8 string utilities This file contains utilitiy functions for utf8 string handling. */ #include "emelfm2.h" #include #include "e2_utf8.h" #include "e2_cl_option.h" /** @brief get position in string @a str This function acts like g_utf8_offset_to_pointer() except that if it finds a decomposable character it consumes the decomposition length from the given offset. So it's useful when the offset was calculated for the normalized version of str, but we need a pointer to str itself @param str string to scan @param offset character offset within @a str @return the resulting pointer */ static const gchar *_e2_utf8_pointer_from_offset_skipping_decomp (const gchar *str, gint offset) { gsize decomp_len; gunichar *decomp; const gchar *p; p = str; while (offset > 0) { decomp = g_unicode_canonical_decomposition (g_utf8_get_char (p), &decomp_len); g_free (decomp); p = g_utf8_next_char (p); offset -= decomp_len; } return p; } /** @brief find first occurrence of @a needle in @a haystack, regardless of case of @a haystack @param haystack string to scan @param needle string to find @return pointer to @a needle in @a haystack, or NULL if not found */ const gchar *e2_utf8_strcasestr (const gchar *haystack, const gchar *needle) { g_return_val_if_fail (haystack != NULL, NULL); g_return_val_if_fail (needle != NULL, NULL); gsize haystack_len, needle_len; needle_len = g_utf8_strlen (needle, -1); if (needle_len == 0) return haystack; gchar *p, *caseless_haystack; const gchar *retval = NULL; p = g_utf8_casefold (haystack, -1); caseless_haystack = g_utf8_normalize (p, -1, G_NORMALIZE_ALL); g_free (p); haystack_len = g_utf8_strlen (caseless_haystack, -1); if (haystack_len < needle_len) goto cleanup; p = caseless_haystack; needle_len = strlen (needle); gint i = 0; while (*p) { if ((strncmp (p, needle, needle_len) == 0)) { retval = _e2_utf8_pointer_from_offset_skipping_decomp (haystack, i); goto cleanup; } p = g_utf8_next_char (p); i++; } cleanup: g_free (caseless_haystack); return retval; } /** @brief find last occurrence of @a needle in @a haystack, regardless of case of @a haystack @param haystack string to scan @param needle string to find @return pointer to @a needle in @a haystack, or NULL if not found */ const gchar *e2_utf8_strrcasestr (const gchar *haystack, const gchar *needle) { g_return_val_if_fail (haystack != NULL, NULL); g_return_val_if_fail (needle != NULL, NULL); gsize haystack_len, needle_len; needle_len = g_utf8_strlen (needle, -1); if (needle_len == 0) return haystack; gchar *p, *caseless_haystack; const gchar *retval = NULL; p = g_utf8_casefold (haystack, -1); caseless_haystack = g_utf8_normalize (p, -1, G_NORMALIZE_ALL); g_free (p); haystack_len = g_utf8_strlen (caseless_haystack, -1); if (haystack_len < needle_len) goto cleanup; gint i = haystack_len - needle_len; p = g_utf8_offset_to_pointer (caseless_haystack, i); needle_len = strlen (needle); while (p >= caseless_haystack) { if (strncmp (p, needle, needle_len) == 0) { retval = _e2_utf8_pointer_from_offset_skipping_decomp (haystack, i); goto cleanup; } p = g_utf8_prev_char (p); i--; } cleanup: g_free (caseless_haystack); return retval; } /** @brief compare utf8 strings @a s1 and @a s2, without reference to case @param s1 string to compare @param s2 other string to compare @param n1 length of @a s1 (in bytes) or -1 if @a s1 is NULL-terminated @param n2 length of @a s2 (in bytes) or -1 if @a s2 is NULL-terminated @return TRUE if @a s1 matches @a s2, apart from any case-difference */ gboolean e2_utf8_caseless_match (const gchar *s1, const gchar *s2, gssize n1, gssize n2) { gchar *casefold; gchar *normalized_s1; gchar *normalized_s2; // gint len_s1; // gint len_s2; gboolean ret; g_return_val_if_fail (s1 != NULL, FALSE); g_return_val_if_fail (s2 != NULL, FALSE); g_return_val_if_fail (n1 >= -1, FALSE); g_return_val_if_fail (n2 >= -1, FALSE); if (n1 == 0 && n2 == 0) return TRUE; else if (n1 == 0 || n2 == 0) return FALSE; casefold = g_utf8_casefold (s1, n1); normalized_s1 = g_utf8_normalize (casefold, -1, G_NORMALIZE_ALL); g_free (casefold); // len_s1 = strlen (normalized_s1); casefold = g_utf8_casefold (s2, n2); normalized_s2 = g_utf8_normalize (casefold, -1, G_NORMALIZE_ALL); g_free (casefold); // len_s2 = strlen (normalized_s2); // ret = (len_s1 == len_s2) ? // (strcmp (normalized_s1, normalized_s2) == 0) : FALSE; ret = (strcmp (normalized_s1, normalized_s2) == 0); g_free (normalized_s1); g_free (normalized_s2); return ret; } /** @brief duplicate utf8 string, truncated after @a num characters if the string is longer than that @param str the string to be duplicated @param num maximum no. of characters in @a str to be processed @return the duplicated string */ gchar *e2_utf8_ndup (const gchar *str, glong num) { glong size = g_utf8_strlen (str, -1); if (num > size) num = size; gchar *end = g_utf8_offset_to_pointer (str, num); glong byte_size = end - str + 1; gchar *utf8 = g_malloc (byte_size); return g_utf8_strncpy (utf8, str, num); } /** @brief escape all instances of character @a c in utf-8 string @a str @param str the string to be processed @param c the character to be escaped if found in @a str @return newly-allocated string with any occurrence of @a c escaped with a '\' */ gchar *e2_utf8_escape (const gchar *str, gunichar c) { glong clen = g_utf8_strlen (str, -1); glong i; gint count = 0; const gchar *tmp = str; for (i = 0; i < clen; i++) { if (g_utf8_get_char (tmp) == c) count++; tmp = g_utf8_next_char (tmp); } if (count > 0) { gint len = strlen (str); gint size = (len + count + 1) * sizeof (gchar); gchar *ret = g_malloc (size); count = 0; for (i = 0; i < clen; i++) { gchar *cur = g_utf8_offset_to_pointer (str, i); if (g_utf8_get_char (cur) == c) g_utf8_strncpy (g_utf8_offset_to_pointer (ret, count++), "\\", 1); g_utf8_strncpy (g_utf8_offset_to_pointer (ret, count++), cur, 1); } ret[size - 1] = '\0'; return ret; } else return g_strdup (str); } /** @brief unescape all instances of character @a c in utf-8 string @a str @param str the string to be processed @param c the character to be unescaped if found in @a str @return newly-allocated string without any occurrence of '\'in front of @a c, or NULL after error */ gchar *e2_utf8_unescape (const gchar *str, gunichar c) { glong count = 0; gint size = (strlen (str) + 1) * sizeof (gchar); gchar *ret = g_try_malloc (size); //FIXME nandle error CHECKALLOCATEDWARN (ret, return NULL); *ret = '\0'; //in case str is empty const gchar *tmp = str; while (*tmp != '\0') { if ((g_utf8_get_char (tmp)) == '\\') { gchar *next = g_utf8_next_char (tmp); if ((next != NULL) && g_utf8_get_char (next) == c) tmp = next; } g_utf8_strncpy (g_utf8_offset_to_pointer (ret, count++), tmp, 1); tmp = g_utf8_next_char (tmp); } /* not needed - each g_utf8_strncpy() appends a '\0' if (count < g_utf8_strlen (str, -1)) g_utf8_strncpy (g_utf8_offset_to_pointer (ret, count), "\0" , 1); else ret[size - 1] = '\0'; */ printd (DEBUG, "ret: %s", ret); return ret; } //inline gchar *e2_utf8_not_converted (const gchar *d) { return (gchar *) d; } void e2_utf8_not_freed (gpointer dummy) {} #ifndef E2_FILES_UTF8ONLY //with run-time checking, a pointer may be set to this func, so no inline //inline gchar *e2_utf8_filename_from_locale (const gchar *d) { GError *error = NULL; gchar *ret = g_filename_to_utf8 (d, -1, NULL, NULL, &error); if (error == NULL) return ret; printd (WARN, "locale filename to UTF8 conversion failed: %s", error->message); g_error_free (error); // return e2_utf8_filename_from_locale_backup (d); //} //gchar *e2_utf8_filename_from_locale_backup (const gchar *d) //{ printd (WARN, "falling back to '%s'", e2_cl_options.fallback_encoding); // GError * error = NULL; // gchar * ret = g_convert_with_fallback (d, -1, "UTF-8", e2_cl_options.fallback_encoding, "?", NULL, NULL, &error); if (error != NULL) { printd (WARN, "fallback locale filename to UTF8 conversion failed: %s", error->message); g_error_free (error); ret = g_strdup(_("Unknown")); } return ret; } //with run-time checking, a pointer may be set to this func, so no inline //inline gchar *e2_utf8_filename_to_locale (const gchar *d) { GError *error = NULL; gchar *ret = g_filename_from_utf8 (d, -1, NULL, NULL, &error); if (error == NULL) return ret; printd (WARN, "UTF8 filename to locale conversion failed: %s", error->message); g_error_free (error); // return e2_utf8_filename_to_locale_backup (d); //} //gchar *e2_utf8_filename_to_locale_backup (const gchar *d) //{ printd (WARN, "falling back to '%s'", e2_cl_options.fallback_encoding); // GError * error = NULL; // gchar * ret = g_convert_with_fallback (d, -1, e2_cl_options.fallback_encoding, "UTF-8", "?", NULL, NULL, &error); if (error != NULL) { printd (WARN, "fallback UTF8 filename to locale conversion failed: %s", error->message); g_error_free (error); ret = g_strdup(_("Unknown")); } return ret; } #endif // ndef E2_FILES_UTF8ONLY gchar *e2_utf8_from_locale (const gchar *d) { if (g_utf8_validate (d, -1, NULL)) return g_strdup (d); gchar *ret = g_locale_to_utf8 (d, -1, NULL, NULL, NULL); if (ret != NULL) return ret; printd (WARN, "initial conversion of localised string %s to UTF8 failed", d); return e2_utf8_from_locale_fallback (d); } gchar *e2_utf8_from_locale_fallback (const gchar *d) { printd (WARN, "falling back to '%s'", e2_cl_options.fallback_encoding); GError *error = NULL; gchar *ret = g_convert_with_fallback (d, -1, "UTF-8", e2_cl_options.fallback_encoding, "?", NULL, NULL, &error); if (error != NULL) { printd (WARN, "fallback locale string to UTF8 conversion failed: %s", error->message); g_error_free (error); ret = g_strdup(_("Unknown")); } return ret; } //#if 0 //UNUSED //inline gchar *e2_utf8_to_locale (const gchar *d) { GError *error = NULL; gchar *ret = g_locale_from_utf8 (d, -1, NULL, NULL, &error); if (error == NULL) return ret; printd (WARN, "UTF8 string to locale conversion failed: %s", error->message); g_error_free (error); return e2_utf8_to_locale_fallback (d); } gchar *e2_utf8_to_locale_fallback (const gchar *d) { printd (WARN, "falling back to '%s'", e2_cl_options.fallback_encoding); GError *error = NULL; gchar *ret = g_convert_with_fallback (d, -1, e2_cl_options.fallback_encoding, "UTF-8", "?", NULL, NULL, &error); if (error != NULL) { printd (WARN, "fallback UTF8 string to locale conversion failed: %s", error->message); g_error_free (error); error = NULL; ret = g_strdup(_("Unknown")); } return ret; } //#endif //0 /************************************* encoding-detection code adapted from leafpad 0.8.7 by Tarot Osuji */ /* A complete list of charsets officially supported in glibc 2.2.3 is: ISO-8859-{1,2,3,5,6,7,8,9,13,15}, CP1251, UTF-8, EUC-{KR,JP,TW}, KOI8-{R,U}, GB2312, GB18030, GBK, BIG5, BIG5-HKSCS and TIS-620 (Romanian may be switching to ISO-8859-16) */ enum { IANA = 0, OPENI18N, CODEPAGE, ENCODING_TYPECOUNT }; enum { LATIN1 = 0, LATIN2, LATIN3, // LATIN4, LATIN5, LATIN6, LATIN7, ARABIC, CYRILIC, CYRILIC_TJ, CYRILIC_UA, GREEK, HEBREW, CHINESE_CN, CHINESE_HK, CHINESE_TW, JAPANESE, KOREAN, VIETNAMESE, THAI, GEORGIAN, ENCODE_COUNT }; //up to this many locales per encoding #define MAX_LOCALES 10 const gchar *locale_table[ENCODE_COUNT][MAX_LOCALES] = { /* LATIN1 */ {NULL}, /* LATIN2 */ {"cs", "de", "hr", "hu", "ro", "pl", "sk", "sq", "sr", "uz"}, //this effectively sets MAX_LOCALES /* LATIN3 */ {"eo", "gl", "mt", NULL}, // / * LATIN4 * / {"et", "mi", NULL}, Estonian, Latvian, and Lithuanian essentially obsolete; /* LATIN5 */ {"az", "tr", NULL}, /* LATIN6 */ {"et", "ga", "is", "kl", "mi", NULL}, /* LATIN7 */ {"da", "fi", "lt", "lv", "no", "pl", "se", "sl", "sv", NULL}, /* ARABIC */ {"ar", "fa", "ur", NULL}, /* CYRILIC */ {"be", "bg", "ky", "mk", "mn", "ru", "tt", NULL}, /* CYRILIC_TJ */{"tg", NULL}, /* CYRILIC_UA */{"uk", NULL}, /* GREEK */ {"el", NULL}, //modern /* HEBREW */ {"he", "iw", "yi", NULL}, //modern /* CHINESE_CN */{"zh_CN", NULL}, /* CHINESE_HK */{"zh_HK", NULL}, /* CHINESE_TW */{"zh_TW", NULL}, /* JAPANESE */ {"ja", NULL}, /* KOREAN */ {"ko", NULL}, /* VIETNAMESE */{"vi", NULL}, /* THAI */ {"th", NULL}, /* GEORGIAN */ {"ka", NULL} }; /******************************************* / * LATIN8 * / { "ISO-8859-14", , }, //a.k.a celtic adds missing gaelic and welsh (cy) to latin1 mainly Old Gaelic, also ok for Albanian, Breton, Catalan, Danish, Dutch, English, Finnish, French, Gaelic, German, Irish, Italian, Norwegian, Portuguese, Swedish and Welsh. / * LATIN9 * / { "ISO-8859-15", , }, //latin1 with euro and some missing things / * LATIN10 * / { "ISO-8859-16", , }, //for Albanian, Croatian, Hungarian, Polish, Romanian and Slovenian, but also French, German, Italian and Irish Gaelic (new orthography). It differs from the other / * ARABIC * / { "ISO-8859-6-E", , }, //explicit bi-directional vartiant / * ARABIC * / { "ISO-8859-6-I", , }, //implicit bi-directional variant / * HEBREW * / { "ISO-8859-8-E", , }, //explicit bi-directional variant / * HEBREW * / { "ISO-8859-8-I", , }, //implicit bi-directional variant ******************************************/ const gchar *encoding_table [ENCODE_COUNT][ENCODING_TYPECOUNT] = { /* IANA OpenI18N Codepage */ /* LATIN1 */ { "ISO-8859-1", "ISO-8859-15", "CP1252" }, /* LATIN2 */ { "ISO-8859-2", "ISO-8859-16", "CP1250" }, /* LATIN3 */ { "ISO-8859-3", NULL, NULL }, // /* LATIN4 */ { "ISO-8859-4", "ISO-8859-13", "CP1257" }, /* LATIN5 */ { "ISO-8859-9", NULL, "CP1254" }, /* LATIN6 */ { "ISO-8859-10", NULL, "CP1257" }, /* LATIN7 */ { "ISO-8859-13", NULL, "CP1257" }, /* ARABIC */ { "ISO-8859-6", NULL, "CP1256" }, //ISO-8859-5 for e.g. bulgarian, belarusian, russian and macedonian, not ukrainian, "never really caught on" /* CYRILIC */ { "ISO-8859-5", "KOI8-R", "CP1251" }, //russian, macedonian /* CYRILIC_TJ */{ "ISO-8859-5", "KOI8-T", "CP1251" }, //tajic /* CYRILIC_UA */{ "ISO-8859-5", "KOI8-U", "CP1251" }, //ukrainian /* GREEK */ { "ISO-8859-7", NULL, "CP1253" }, //modern /* HEBREW */ { "ISO-8859-8", NULL, "CP1255" }, //modern /* CHINESE_CN */{ "GB2312", "GB18030", "CP936" }, /* CHINESE_HK */{ "BIG5", "BIG5-HKSCS", "CP950" }, /* CHINESE_TW */{ "BIG5", "EUC-TW", "CP950" }, /* JAPANESE */ { "ISO-2022-JP", "EUC-JP", "CP932" }, /* KOREAN */ { "ISO-2022-KR", "EUC-KR", "CP949" }, /* VIETNAMESE */{ NULL, "VISCII", "CP1258" }, /* THAI */ { "ISO-8859-11", "TIS-620", "CP874" }, //ISO-8859-11 is unofficial /* GEORGIAN */ { NULL, "GEORGIAN-PS", NULL } }; static guint get_locale_index (void) { static guint code = ENCODE_COUNT; if (code == ENCODE_COUNT) //we only look this up once { const gchar *env = g_getenv ("LC_ALL"); if (env == NULL) env = g_getenv ("LANG"); if (env != NULL && strlen (env) >= 2) { guint i, j = 1; while (code == ENCODE_COUNT && j < ENCODE_COUNT) { for (i = 0; i < MAX_LOCALES; i++) { if (locale_table[j][i] == NULL) break; if (strncmp (env, locale_table[j][i], strlen(locale_table[j][i])) == 0) { code = j; break; } } j++; } } if (code == ENCODE_COUNT) code = LATIN1; //default if we can't find anything supported } return code; } static const gchar *detect_charset_cyrillic (const guchar *text) { guchar c; gboolean noniso = FALSE; guint32 xc = 0, xd = 0, xef = 0; const gchar *charset; while ((c = *text++) != '\0') { if (c >= 0x80 && c <= 0x9F) noniso = TRUE; else if (c >= 0xC0 && c <= 0xCF) xc++; else if (c >= 0xD0 && c <= 0xDF) xd++; else if (c >= 0xE0) xef++; } if (!noniso && ((xc + xef) < xd)) charset = "ISO-8859-5"; else if ((xc + xd) < xef) charset = "CP1251"; else charset = encoding_table [get_locale_index()][OPENI18N]; return charset; } static const gchar *detect_charset_chinese (const guchar *text) { guchar c; const gchar *charset = NULL; while ((c = *text++) != '\0') { if (c >= 0x81 && c <= 0x87) { charset = "GB18030"; break; } else if (c >= 0x88 && c <= 0xA0) { c = *text++; if ((c >= 0x30 && c <= 0x39) || (c >= 0x80 && c <= 0xA0)) { charset = "GB18030"; break; } //else GBK/Big5-HKSCS cannot determine } else if ((c >= 0xA1 && c <= 0xC6) || (c >= 0xC9 && c <= 0xF9)) { c = *text++; if (c >= 0x40 && c <= 0x7E) { charset = "BIG5"; break; } else if ((c >= 0x30 && c <= 0x39) || (c >= 0x80 && c <= 0xA0)) { charset = "GB18030"; break; } } else if (c >= 0xC7) { c = *text++; if ((c >= 0x30 && c <= 0x39) || (c >= 0x80 && c <= 0xA0)) { charset = "GB18030"; break; } } } if (charset == NULL) charset = encoding_table [get_locale_index()][IANA]; return charset; } static const gchar *detect_charset_japanese (const guchar *text) { guchar c; gchar *charset = NULL; while ((c = *text++) != '\0') { if (c >= 0x81 && c <= 0x9F) { if (c == 0x8E) /* SS2 */ { c = *text++; if ((c >= 0x40 && c <= 0xA0) || (c >= 0xE0 && c <= 0xFC)) { charset = "CP932"; break; } } else if (c == 0x8F) /* SS3 */ { c = *text++; if (c >= 0x40 && c <= 0xA0) { charset = "CP932"; break; } else if (c >= 0xFD) break; } else { charset = "CP932"; break; } } else if (c >= 0xA1 && c <= 0xDF) { c = *text++; if (c <= 0x9F) { charset = "CP932"; break; } else if (c >= 0xFD) break; } else if (c >= 0xE0 && c <= 0xEF) { c = *text++; if (c >= 0x40 && c <= 0xA0) { charset = "CP932"; break; } else if (c >= 0xFD) break; } else if (c >= 0xF0) break; } if (charset == NULL) charset = "EUC-JP"; return charset; } static const gchar *detect_charset_korean (const guchar *text) { guchar c; gboolean noneuc = FALSE; gboolean nonjohab = FALSE; gchar *charset = NULL; while ((c = *text++) != '\0') { if (c >= 0x81 && c < 0x84) { charset = "CP949"; break; } else if (c >= 0x84 && c < 0xA1) { noneuc = TRUE; c = *text++; if ((c > 0x5A && c < 0x61) || (c > 0x7A && c < 0x81)) { charset = "CP1361"; break; } else if (c == 0x52 || c == 0x72 || c == 0x92 || (c > 0x9D && c < 0xA1) || c == 0xB2 || (c > 0xBD && c < 0xC1) || c == 0xD2 || (c > 0xDD && c < 0xE1) || c == 0xF2 || c == 0xFE) { charset = "CP949"; break; } } else if (c >= 0xA1 && c <= 0xC6) { c = *text++; if (c < 0xA1) { noneuc = TRUE; if ((c > 0x5A && c < 0x61) || (c > 0x7A && c < 0x81)) { charset = "CP1361"; break; } else if (c == 0x52 || c == 0x72 || c == 0x92 || (c > 0x9D && c < 0xA1)) { charset = "CP949"; break; } else if (c == 0xB2 || (c > 0xBD && c < 0xC1) || c == 0xD2 || (c > 0xDD && c < 0xE1) || c == 0xF2 || c == 0xFE) nonjohab = TRUE; } } else if (c > 0xC6 && c <= 0xD3) { c = *text++; if (c < 0xA1) { charset = "CP1361"; break; } } else if (c > 0xD3 && c < 0xD8) { nonjohab = TRUE; c = *text++; } else if (c >= 0xD8) { c = *text++; if (c < 0xA1) { charset = "CP1361"; break; } } if (noneuc && nonjohab) { charset = "CP949"; break; } } if (charset == NULL) { if (noneuc) charset = "CP949"; else charset = "EUC-KR"; } return charset; } static gboolean detect_noniso (const guchar *text) { guchar c; while ((c = *text++) != '\0') { if (c >= 0x80 && c <= 0x9F) return TRUE; } return FALSE; } /** @brief detect any recognised byte-order-mark at start of @a text @param text the string to be processed Note There's no distinction between flavours of UTF-16 and the corresponding UCS-2's, or between UTF-32's and corresponding UCS-4's No checking for file length, particularly in the case of UTF-32 @return encoding name if any is recognised, or NULL */ static const gchar *detect_charset_BOM (const guchar *text) { guchar c; gint count; guchar array[3]; count = 0; switch (*text++) { case '+': //2B 2F 76 and one of the following bytes: [ 3C | 3D | 3E | 3F ] array[0] = '/'; array[1] = 'v'; while (count < 2 && (c = *text++) == array[count]) {count++;} if (count == 2 && ((c = *text) >= '\\' && c <= '_')) return "UTF-7"; break; case 0xEF: //EF BB BF array[0] = 0xBB; array[1] = 0xBF; while (count < 2 && (c = *text++) == array[count]) {count++;} if (count == 2) return "UTF-8"; break; /*UCS-2, UCS-2, UCS-2BE, UCS-2LE (often conflated with UTF-16 but they're not the same) have same BOM as corresponding UTF-16. Ditto for UCS-4's and UTF-32's */ case 0xFE: //Big Endian FE FF if (*text == 0xFF) return "UTF-16BE"; //could actually be UCS-2BE, not quite the same break; case 0xFF: //Little Endian FF FE 00 00 //Little Endian FF FE array[0] = 0xFE; array[1] = 0; array[2] = 0; while (count < 3 && (c = *text++) == array[count]) {count++;} if (count == 3) return "UTF-32LE"; if (count == 1) return "UTF-16LE"; //could actually be UCS-2LE, not quite the same break; case 0: //CHECKME if file is empty, next checks may be a buffer overflow ? //Big Endian 00 00 FE FF array[0] = 0; array[1] = 0xFE; array[2] = 0xFF; while (count < 3 && (c = *text++) == array[count]) {count++;} if (count == 3) return "UTF-32BE"; break; case 0x0E: //0E FE FF array[0] = 0xFE; array[1] = 0xFF; while (count < 2 && (c = *text++) == array[count]) {count++;} if (count == 2) return "SCSU"; break; case 0xDD: //DD 73 66 73 array[0] = 's'; array[1] = 'f'; array[2] = 's'; while (count < 3 && (c = *text++) == array[count]) {count++;} if (count == 3) return "UTF-EBCDIC"; break; case 0xFB: //FB EE 28 array[0] = 0xEE; array[1] = 0x28; while (count < 2 && (c = *text++) == array[count]) {count++;} if (count == 2) return "BOCU-1"; default: break; } return NULL; } /** @brief detect character encoding of 0-terminated string @a text @a text may be validated as UTF-8 in spite of a nominally different coding @a charset is set to "UNKNOWN" if nothing specific is recognised @param text 0-terminated string to be processed @param charset store for name of detected charset @return TRUE if @a text is found to be valid UTF-8 (which includes plain ascii) */ gboolean e2_utf8_detect_charset (const guchar *text, const gchar **charset) { const guchar *start; guchar c; gboolean isutf8; *charset = detect_charset_BOM (text); if (*charset != NULL) //FIXME test for short file length if UTF-32* return (g_str_equal (*charset, "UTF-8")); start = text; isutf8 = FALSE; while ((c = *text++) != '\0') { if (c > 0x7F) { if ((c & 0xC0) == 0xC0) isutf8 = g_utf8_validate ((gchar *)--text, -1, NULL); break; } else if (c == '~') { c = *text++; if (c == '{') { c = *text++; //maybe 1st GB2312 (chinese) char if (c != '\0') { c = *text++; //maybe } or 2nd GB2312 (chinese) char if (c == '}' && *text == '~') { *charset = "HZ"; break; } else { c = *text++; //maybe } after 2nd GB2312 (chinese) char if (c == '}' && *text == '~' && *(text-2) > 0x7F) { *charset = "HZ"; break; } } } } } else if (c == 0x1B) /* ESC */ { c = *text++; if (c == '$') { c = *text++; switch (c) { case 'B': // JIS X 0208-1983 case '@': // JIS X 0208-1978 *charset = "ISO-2022-JP"; break; case 'A': // GB2312-1980 *charset = "ISO-2022-JP-2"; break; case '(': c = *text++; switch (c) { case 'C': // KSC5601-1987 case 'D': // JIS X 0212-1990 *charset = "ISO-2022-JP-2"; } break; case ')': c = *text++; if (c == 'C') *charset = "ISO-2022-KR"; // KSC5601-1987 default: break; } if (*charset != NULL) break; } } } if (isutf8) { //validation succeeded if (!g_get_charset (charset)) //use default encoding if it's utf8-compatible *charset = "UTF-8"; } else //no validation requested, or validation failed if (c == '\0') //reached end of text without detection { //it's almost certainly ASCII, assume the default encoding applies //FIXME except if that's incompatible with ascii // printd (DEBUG, "Specific UTF8 detection failed, reverted to DEFAULT CHARSET %s", *charset); e2_utils_get_charset (charset); isutf8 = TRUE; //no conversion needed } else // !isutf8 && not scanned to end of buffer if (*charset == NULL) //not detected ISO2022 or HZ { //next, assume that the file originates in the user's locale guint code = get_locale_index(); switch (code) { case CYRILIC: case CYRILIC_UA: case CYRILIC_TJ: *charset = detect_charset_cyrillic(text); // fuzzy... break; case CHINESE_CN: case CHINESE_TW: case CHINESE_HK: *charset = detect_charset_chinese(text); break; case JAPANESE: *charset = detect_charset_japanese(text); break; case KOREAN: *charset = detect_charset_korean(text); break; case VIETNAMESE: case THAI: case GEORGIAN: *charset = encoding_table [code][OPENI18N]; break; default: e2_utils_get_charset (charset); if (strcmp (*charset, "UTF-8") == 0) //CHECKME this is no use as the text isn't compliant { if (detect_noniso (text)) *charset = encoding_table [code][CODEPAGE]; else *charset = encoding_table [code][OPENI18N]; } if (*charset == NULL) *charset = encoding_table [code][IANA]; break; } } if (*charset == NULL) { //all checks failed, send back something ... // e2_utils_get_charset (charset); // printd (DEBUG, "Detection failed 2, reverted to DEFAULT CHARSET %s", *charset); *charset = "UNKNOWN"; //no translation } return isutf8; } emelfm2-0.4.1/src/utils/e2_menu.c0000600000175000017500000007524310776374631015465 0ustar cairocairo/* $Id: e2_menu.c 853 2008-04-07 10:38:17Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/utils/e2_menu.c @brief Menu utility functions This file contains utility and helper functions for menus. */ #include "emelfm2.h" #include #include #include "e2_menu.h" #include "e2_dialog.h" #include "e2_plugins.h" #include "e2_task.h" #ifdef E2_FS_MOUNTABLE # include "e2_filelist.h" #endif extern pthread_mutex_t task_mutex; /*******************/ /**** callbacks ****/ /*******************/ /** @brief callback to destroy widget @a menu @param menu the menu widget to destroy @param data UNUSED data specified when the callback was connected @return */ void e2_menu_destroy_cb (GtkWidget *menu, gpointer data) { //CHECKME is this not done automatically ? gtk_container_foreach (GTK_CONTAINER (menu), (GtkCallback)gtk_widget_destroy, NULL); gtk_widget_destroy (menu); } /** @brief callback to @param menu_item the activated menu-item widget @param widget widget specified when the callback was connected @return */ void e2_menu_control_cb (GtkWidget *menu_item, gpointer widget) { e2_option_connect (widget, gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (menu_item))); } /** @brief update set value when tied check menu item is activated @param menu_item the item that was activated @param set pointer to data for optionset tied to @a menu_item @return */ static void _e2_menu_check_value_changed_cb (GtkWidget *menu_item, E2_OptionSet *set) { GtkWidget *controller = g_object_get_data (G_OBJECT (menu_item), "e2-controller-widget"); if (!GPOINTER_TO_INT (g_object_get_data (G_OBJECT (controller), "e2-controller-blocked"))) e2_option_bool_set_direct (set, gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (menu_item))); } /** @brief set @a menu_item state if it's not blocked and not already at the desired state This is a hook-function callack upon change of set data associated with @a menu_item @param state pointerised TRUE/FALSE, the value to apply to @a menu_item @param menu_item the widget to change @return TRUE always */ static gboolean _e2_menu_check_change_value (gpointer state, GtkWidget *menu_item) { GtkWidget *controller = g_object_get_data (G_OBJECT (menu_item), "e2-controller-widget"); if (!GPOINTER_TO_INT (g_object_get_data (G_OBJECT (controller), "e2-controller-blocked"))) { gboolean value = GPOINTER_TO_INT (state); gboolean current = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (menu_item)); if (value != current) gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item), value); } return TRUE; } /** @brief add tied check item to @a menu @param menu menu widget @param set pointer to data for set whose value is tied to the check button @param controller widget which may have "e2-controller-blocked" data to prevent set value being updated when item is activated, can be NULL @param func void* callback function for handling menu item selection, or NULL @param data pointer to data to send to the callback @return the menu item widget */ static GtkWidget *_e2_menu_add_tied_check (GtkWidget *menu, E2_OptionSet *set, GtkWidget *controller, gpointer func, gpointer data) { GtkWidget *menu_item = e2_menu_add_check (menu, set->desc, e2_option_bool_get_direct (set), func, data); g_object_set_data (G_OBJECT (menu_item), "e2-controller-widget", controller); //this will update the set value when the check menu item is activated g_signal_connect (G_OBJECT (menu_item), "toggled", G_CALLBACK (_e2_menu_check_value_changed_cb), set); //this will update the check menu item when the config data value changes e2_option_attach_value_changed_simple (set, menu_item, _e2_menu_check_change_value, menu_item); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif menu_item, set->tip); //conform to dependent set if any e2_widget_handle_depends (menu_item, set); return menu_item; } /****************/ /**** public ****/ /****************/ /** @brief add item to @a menu @param menu menu widget @param label menu item text, optionally with mnemonic @param icon custom iconfile path, or stock-icon identifier @param tip tooltip string @param func void* callback function for handling menu item selection, or NULL @param data pointer to data to send to the callback @return the menu item widget */ GtkWidget *e2_menu_add (GtkWidget *menu, gchar *label, gchar *icon, gchar *tip, void *func, gpointer data) { GtkWidget *menu_item; if ((icon != NULL) && (e2_option_bool_get ("menu-show-icons"))) { menu_item = gtk_image_menu_item_new_with_mnemonic (label != NULL ? label : ""); GtkWidget *image = e2_widget_get_icon (icon, e2_option_int_get ("menu-isize") + 1); if (image != NULL) gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), image); } else menu_item = gtk_menu_item_new_with_mnemonic (label); //turn on markup usage for the menu item GList *children = gtk_container_get_children (GTK_CONTAINER (menu_item)); GtkWidget *lab = children->data; //this is a GtkAccelLabel; gtk_label_set_use_markup (GTK_LABEL (lab), TRUE); if (func != NULL) g_signal_connect (G_OBJECT (menu_item), "activate", G_CALLBACK (func), data); gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item); gtk_widget_show_all (menu_item); if (tip != NULL && *tip != '\0') #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif menu_item, tip); return menu_item; } /** @brief add action item to @a menu @param menu menu widget @param label menu item text, optionally with mnemonic @param icon custom iconfile path, or stock-icon identifier @param tip tooltip string @param action_name identifier of the form "_A(n)._A(m)", or external command @param arg argument(s) string for the action @param freefunc pointer to function that frees @a arg when the action is finished @return the menu item widget */ GtkWidget *e2_menu_add_action (GtkWidget *menu, gchar *label, gchar *icon, gchar *tip, gchar *action_name, gchar *arg, gpointer freefunc) { gchar *real_arg; E2_Action *action = e2_action_get_with_custom (action_name, arg, &real_arg); E2_ActionRuntime *a_rt = e2_action_pack_runtime (action, real_arg, freefunc); GtkWidget *menu_item = e2_menu_add (menu, label, icon, tip, e2_action_run, a_rt); g_object_set_data_full (G_OBJECT (menu_item), "free-callback-data", a_rt, (GDestroyNotify) e2_action_free_runtime); return menu_item; } /** @brief add check item to @a menu @param menu menu widget @param label menu item text, optionally with mnemonic @param state initial state of the created item, T/F @param func void* callback function for handling menu item selection, or NULL @param data pointer to data to send to the callback @return the menu item widget */ GtkWidget *e2_menu_add_check (GtkWidget *menu, gchar *label, gboolean state, void *func, gpointer data) { GtkWidget *check = gtk_check_menu_item_new_with_mnemonic (label); if (menu != NULL) gtk_menu_shell_append (GTK_MENU_SHELL (menu), check); gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (check), state); if (func != NULL) g_signal_connect (G_OBJECT (check), "toggled", G_CALLBACK (func), data); gtk_widget_show (check); return check; } /** @brief add radio item to @a menu @param menu menu widget, or NULL @param group pointer to gslist of radio group @param label menu item text, optionally with mnemonic @param state initial state of the created item, T/F @param func void* callback function for handling menu-item selection, or NULL @param data pointer to data to send to the callback @return the menu item widget */ GtkWidget *e2_menu_add_radio (GtkWidget *menu, GSList **group, gchar *label, gboolean state, void *func, gpointer data) { GtkWidget *radio = gtk_radio_menu_item_new_with_mnemonic (*group, label); *group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (radio)); if (menu != NULL) gtk_menu_shell_append (GTK_MENU_SHELL (menu), radio); gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (radio), state); if (func != NULL) g_signal_connect (G_OBJECT (radio), "activate", G_CALLBACK (func), data); gtk_widget_show (radio); return radio; } /** @brief add separator to @a menu @param menu menu widget, or NULL @return the menu item widget */ GtkWidget *e2_menu_add_separator (GtkWidget *menu) { GtkWidget *sep = gtk_separator_menu_item_new (); if (menu != NULL) gtk_menu_shell_append (GTK_MENU_SHELL (menu), sep); gtk_widget_show (sep); return sep; } /** @brief add tear-off to @a menu @param menu menu widget @return the menu item widget */ GtkWidget *e2_menu_add_tear_off (GtkWidget *menu) { GtkWidget *menu_item = gtk_tearoff_menu_item_new (); gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item); gtk_widget_show (menu_item); return menu_item; } /** @brief add sub-menu to @a menu Note - no use to add a tooltip for item, it does not show @param menu menu widget @param label string with the name to appear in the menu @param icon icon-file name string @return the sub-menu item widget */ GtkWidget *e2_menu_add_submenu (GtkWidget *menu, gchar *label, gchar *icon) { GtkWidget *menu_item = e2_menu_add (menu, label, icon, NULL, NULL, NULL); GtkWidget *submenu = gtk_menu_new (); gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), submenu); gtk_widget_show (submenu); return submenu; } /** @brief create toggle menu item and add it to @a menu This expects to be called twice in succession for each toggle-item. None of the strings may be NULL @param menu the menu to which the item will be added @param label menu label string @param icon menu icon name @param tip menu tip string @param type string "toggle.on" or "toggle.off" @param cmd toggle action, for creating a hash key @return NULL for the first of the pair, the menu item after the second */ GtkWidget *e2_menu_add_toggle (GtkWidget *menu, gchar *label, gchar *icon, gchar *tip, gchar *type, gchar *cmd) { static gboolean first = TRUE; static gboolean firststate; static gchar *firstlabel; static gchar *firsticon; static gchar *firsttip; static gchar *firstcmd; //NEVER FREE THIS if (label == NULL) label = ""; if (icon == NULL) icon = ""; if (tip == NULL) tip = ""; if (cmd == NULL) cmd = ""; if (first) { //process 1st of a pair first = FALSE; //park until we get the paired name, so we can make hash key firststate = g_str_has_suffix (type, _A(111)); //_(on firstlabel = g_strdup (label); firsticon = g_strdup (icon); firsttip = g_strdup (tip); firstcmd = g_strdup (cmd); return NULL; } //process 2nd of the pair first = TRUE; //hash table key is the joined action strings gchar *hashkey = g_strconcat (firstcmd, ".", cmd, NULL); //get toggle data, if any E2_ToggleData *data = g_hash_table_lookup (toggles_hash, hashkey); if (data == NULL) { data = ALLOCATE (E2_ToggleData); //deallocation when # cleared CHECKALLOCATEDFATAL (data); g_hash_table_insert (toggles_hash, g_strdup (hashkey), data); data->current_state = firststate; data->boxes = NULL; if (firststate) { data->true_action = firstcmd; //action command string data->false_action = g_strdup (cmd); //ditto } else { data->false_action = firstcmd; data->true_action = g_strdup (cmd); } } gchar *uselabel, *useicon, *usetip; if (data->current_state == firststate) { uselabel = firstlabel; useicon = firsticon; usetip = firsttip; } else { uselabel = label; useicon = icon; usetip = tip; } gchar *realarg; E2_Action *action = e2_action_get_with_custom (type, cmd, &realarg); E2_ActionRuntime *actionrt = e2_action_pack_runtime (action, hashkey, g_free); GtkWidget *item = e2_menu_add (menu, uselabel, useicon, usetip, e2_action_run, actionrt); g_object_set_data_full (G_OBJECT (item), "free-callback-data", actionrt, (GDestroyNotify) e2_action_free_runtime); g_free (firstlabel); g_free (firsticon); g_free (firsttip); g_free (realarg); return item; } /** @brief popup menu @a menu @param menu menu widget @param button event button @param time event time @return */ void e2_menu_popup (GtkWidget *menu, gint button, guint32 time) { g_signal_connect (G_OBJECT (menu), "selection-done", G_CALLBACK (e2_menu_destroy_cb), NULL); gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, button, time); } /* * @brief @param menu menu widget @param x UNUSED @param y UNUSED @param button event button @param time event time @return */ /* UNUSED void _e2_menu_popup (GtkWidget *menu, gint x, gint y, gint button, guint32 time) { g_signal_connect (G_OBJECT (menu), "selection-done", G_CALLBACK (e2_menu_destroy_cb), NULL); gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, button, time); } */ /** @brief add items for a series of config options to @a menu @param controller "parent" widget for the created menu @param menu menu widget to populate, or NULL to create a new one @param set first of a NULL-terminated series of option data structs @return the menu widget */ GtkWidget *e2_menu_create_options_menu (GtkWidget *controller, GtkWidget *menu, E2_OptionSet *set, ...) { if (menu == NULL) menu = gtk_menu_new (); va_list args; va_start (args, set); while (set != NULL) { gpointer func = va_arg (args, gpointer); gpointer data = va_arg (args, gpointer); switch (set->type) { case E2_OPTION_TYPE_BOOL: _e2_menu_add_tied_check (menu, set, controller, func, data); break; case E2_OPTION_TYPE_SEL: e2_option_sel_add_menu_widget (controller, menu, set, func, data); break; default: break; } set = va_arg (args, E2_OptionSet *); } va_end (args); return menu; } /** @brief add items to bookmarks menu, recursively if children exist @param action pointer to data for the pseudo-action being processed @param set pointer to bookmarks option data @param menu widget to which the items will be added @param iter pointer to iter to be used for interrogating the treemodel for @a set @return */ void e2_menu_create_bookmarks_menu (E2_Action *action, E2_OptionSet *set, GtkWidget *menu, GtkTreeIter *iter) { //find which pane to use for the path, when adding marks gpointer pane = NULL; //defualt = use current pane if (strstr (action->name, _A(11)) != NULL) pane = (GINT_TO_POINTER (1)); else if (strstr (action->name, _A(12)) != NULL) pane = (GINT_TO_POINTER (2)); do { gchar *name, *icon, *_tip, *_path; //don't support variables in label // gchar *_name, *icon, *_tip, *_path; gtk_tree_model_get (set->ex.tree.model, iter, 0, &name, 1, &icon, 2, &_tip, 3, &_path, -1); // gchar *name = e2_utils_replace_vars (_name); gchar *tip = e2_utils_replace_vars (_tip); gchar *path = e2_utils_replace_vars (_path); // g_free (_name); g_free (_tip); g_free (_path); GtkWidget *item; gboolean child = gtk_tree_model_iter_has_child (set->ex.tree.model, iter); if (child) { //sub-menu items can't show tip, context menu etc GtkWidget *menu2 = gtk_menu_new (); GtkTreeIter iter2; gtk_tree_model_iter_children (set->ex.tree.model, &iter2, iter); e2_menu_create_bookmarks_menu (action, set, menu2, &iter2); //recurse item = e2_menu_add (menu, name, icon, tip, NULL, NULL); gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), menu2); g_free (path); } else { item = e2_menu_add (menu, name, icon, tip, NULL, NULL); g_signal_connect_data (G_OBJECT (item), "button-press-event", G_CALLBACK (e2_bookmark_click_cb), gtk_tree_model_get_path (set->ex.tree.model, iter), (GClosureNotify) gtk_tree_path_free, 0); //gtk sillyness fix, if we don't use connect_after for the activate //signal of the menuitem, the button-press-event will never occur //path is cleaned when art is destroyed E2_ActionRuntime *art = e2_action_pack_runtime (action, path, g_free); g_signal_connect_after (item, "activate", G_CALLBACK (e2_action_run), art); g_object_set_data_full (G_OBJECT (item), "free-callback-data-bookmark", art, (GDestroyNotify) e2_action_free_runtime); g_object_set_data (G_OBJECT (item), "bookmark-path", path); //signal which pane (or default) to use when adding an item g_object_set_data (G_OBJECT (item), "bookmark-pane", pane); } g_free (name); g_free (icon); g_free (tip); } while (gtk_tree_model_iter_next (set->ex.tree.model, iter)); } /** @brief add items for all relevant menu-enabled plugins, to @a menu Assumes app.plugins is in menu-order, for "non-child" plugins at least, and for any "parent" plugin, its embedded list of children is in sub-menu order @param menu widget to which items are added @param is_selection TRUE if one or more items are selected in active pane @return */ void e2_menu_create_plugins_menu (GtkWidget *menu, gboolean is_selection) { gchar *prefix = (is_selection) ? NULL : g_strconcat (_A(5), ".", NULL); GList *tmp; for (tmp = app.plugins; tmp != NULL; tmp = tmp->next) { Plugin *p = tmp->data; if (p->show_in_menu && (is_selection || !g_str_has_prefix (p->action->name, prefix))) { if (p->child_list != NULL) { //create item and child menu GtkWidget *menu2 = e2_menu_add_submenu (menu, (gchar *)p->menu_name, (gchar *)p->icon); GList *member; for (member = p->child_list; member != NULL; member = member->next) { Plugin *pc = (Plugin *)member->data; if (pc->show_in_menu) e2_menu_add (menu2, (gchar *)pc->menu_name, (gchar *)pc->icon, (gchar *)pc->description, e2_plugins_do_action, pc->action); } } else //just 1 action in the plugin, or it's a child if (p->module != NULL) //it's not a child e2_menu_add (menu, (gchar *)p->menu_name, (gchar *)p->icon, (gchar *)p->description, e2_plugins_do_action, p->action); } } if (!is_selection) g_free (prefix); } /** @brief process custom-menu item This is the callback for 'activated' signal for the menu item @param item the activated menu item @param data UNUSED data specified when callback was connected @return */ static void _e2_menu_custom_cb (GtkWidget *item, gpointer data) { //command may be slow, get this out of the way // gtk_widget_hide (item->parent); gchar *cmd = (gchar *) g_object_get_data (G_OBJECT (item), "custom-command"); #ifdef E2_COMMANDQ e2_command_run (cmd, E2_COMMAND_RANGE_DEFAULT, FALSE); #else e2_command_run (cmd, E2_COMMAND_RANGE_DEFAULT); #endif //FIXME some sort of termination process ? } //static void _e2_menu_custom_finished_cb (GtkWidget *menu, gpointer data) //{ //} /** @brief add items to a menu of custom commands @param model config option tree model for custom menus @param iter tree iter to use for interrogating @a model @param menu the menu to which items are to be added @return */ static void _e2_menu_add_custom_items (GtkTreeModel *model, GtkTreeIter *iter, GtkWidget *menu) { GtkTreeIter iter2; GtkWidget *menu_item; gchar *icon, *label, *tip, *command; do { gtk_tree_model_get (model, iter, 1, &icon, 2, &label, 3, &tip, 4, &command, -1); menu_item = e2_menu_add (menu, label, icon, tip, _e2_menu_custom_cb, NULL); if (gtk_tree_model_iter_children (model, &iter2, iter)) { GtkWidget *menu2 = gtk_menu_new (); //recurse _e2_menu_add_custom_items (model, &iter2, menu2); gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), menu2); g_free (command); } else g_object_set_data_full (G_OBJECT (menu_item), "custom-command", command, (GDestroyNotify) g_free); //cleanup except command g_free (icon); g_free (label); g_free (tip); } while (gtk_tree_model_iter_next (model, iter)); } /** @brief construct a menu of custom commands @param name utf8 string, name of custom menu to use @return the menu item widget, or NULL */ GtkWidget *e2_menu_create_custom_menu (gchar *name) { GtkTreeIter iter, iter2; GtkWidget *menu; E2_OptionSet *set = e2_option_get ("custom-menus"); if (e2_tree_find_iter_from_str (set->ex.tree.model, 0, name, &iter, TRUE) && gtk_tree_model_iter_children (set->ex.tree.model, &iter2, &iter)) { menu = gtk_menu_new (); _e2_menu_add_custom_items (set->ex.tree.model, &iter2, menu); } else menu = NULL; return menu; } /** @brief construct a menu of running child processes, with items which callback to @a func This func is usable for button-menu and output context menu We use toggle items (no mnemonics) to indicate which procesess are active @param func void* callback function for handling menu item selection @return the menu item widget, or NULL */ #define CHILDMENUWIDTH 30 GtkWidget *e2_menu_create_child_menu (E2_ChildMenuType type, gpointer func) { gchar *label, *s; gboolean active, empty = TRUE; GtkWidget *menu = gtk_menu_new (); GtkWidget *item; GList *member; pthread_mutex_lock (&task_mutex); member = app.taskhistory; pthread_mutex_unlock (&task_mutex); while (member != NULL) { E2_TaskRuntime *rt = member->data; if (type == E2_CHILD_ALL || (!rt->action && //for this menu, we're not interested in actions ( (type == E2_CHILD_ACTIVE && (rt->status == E2_TASK_RUNNING || rt->status == E2_TASK_PAUSED)) || (type == E2_CHILD_OUTPUT && gtk_text_buffer_get_mark (app.tab.buffer, rt->pidstr) != NULL))) ) { empty = FALSE; //ellipsize the middle of longish commands s = e2_utils_str_shorten (rt->ex.command.command, CHILDMENUWIDTH - 7, E2_DOTS_MIDDLE); label = g_strconcat (rt->pidstr, ": ", s, NULL); g_free (s); if (type == E2_CHILD_ALL) { item = gtk_check_menu_item_new_with_label (label); active = (rt != NULL && (rt->status == E2_TASK_RUNNING || rt->status == E2_TASK_PAUSED)); gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), active); // if (func != NULL) // g_signal_connect (G_OBJECT (item), "toggled", // G_CALLBACK (func), rt); } else { item = gtk_menu_item_new_with_label (label); // if (func != NULL) // g_signal_connect (G_OBJECT (item), "activate", // G_CALLBACK (func), rt); } if (func != NULL) g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (func), rt); gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); gtk_widget_show_all (item); g_free (label); } pthread_mutex_lock (&task_mutex); member = member->next; pthread_mutex_unlock (&task_mutex); } if (empty) { if (type == E2_CHILD_OUTPUT) { //label = _("no matching output"); gtk_widget_destroy (menu); menu = NULL; } else { label = _("no children"); e2_menu_add (menu, label, NULL, NULL, func, NULL); } } if (menu != NULL) g_signal_connect (G_OBJECT (menu), "selection-done", G_CALLBACK (e2_menu_destroy_cb), NULL); return menu; } /** @brief create filters menu for showing in pane to which @a view belongs @param view data struct for view to be changed @return the menu widget */ GtkWidget *e2_menu_create_filter_menu (ViewInfo *view) { GtkWidget *menu = gtk_menu_new (); view->check_name = e2_menu_add_check (menu, _("_Name filter"), view->name_filter.active, e2_name_filter_dialog_create_cb, view); view->check_size = e2_menu_add_check (menu, _("_Size filter"), view->size_filter.active, e2_size_filter_dialog_create_cb, view); view->check_date = e2_menu_add_check (menu, _("_Date filter"), view->date_filter.active, e2_date_filter_dialog_create_cb, view); e2_menu_add_separator (menu); view->check_dirs = e2_menu_add_check (menu, _("_Directories too"), view->filter_directories, e2_fileview_filter_dirs_cb, view); if (view->name_filter.active || view->size_filter.active || view->date_filter.active) { e2_menu_add_separator (menu); e2_menu_add (menu, _("_Remove all filters"), NULL, NULL, e2_fileview_remove_filters_cb, view); } g_signal_connect (G_OBJECT (menu), "selection-done", G_CALLBACK (e2_menu_destroy_cb), NULL); return menu; } #ifdef E2_FS_MOUNTABLE /** @brief handle a selection from a mountpoints menu The state of @a item when it arrives here is opposite to that shown in the menu, when clicked @param item the selected menu item @param user_data UNUSED data specified when the item was constructed @return */ static void _e2_menu_mount_cb (GtkWidget *item, gpointer user_data) { gboolean newstate = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item)); // gint result; gchar *elsewhere = NULL; gchar *point = g_object_get_data (G_OBJECT (item), "_mountpoint_"); #ifdef E2_HAL gboolean remove = e2_fs_mount_is_ejectable (point); //check this before unmount #endif //CHECKME synchronous umount command to mimimize risk of CWD error gchar *cmd = g_strdup_printf ("%s \"%s\"", (newstate) ? "mount" : "|umount", point); //CHECKME _I( if (!newstate //doing an unmount #ifdef E2_VFSTMP //FIXME dir when not mounted local #else && g_str_has_prefix (curr_view->dir, point)) #endif { //if possible, change CWD out of mountpoint so we don't ourself block the unmount gchar *s = strrchr (point, G_DIR_SEPARATOR); //assumes no trailer on mountpoint string if (s > point+1) //not doing root dir elsewhere = g_strndup (point, s - point); else //can't cd elsewhere = g_strdup (G_DIR_SEPARATOR_S); e2_fs_get_valid_path (&elsewhere, TRUE E2_ERR_NONE()); // if (e2_fs_chdir (elsewhere E2_ERR_NONE())) //result = e2_command_run_at (cmd, elsewhere, E2_COMMAND_RANGE_DEFAULT); //has temporary change CWD } else // result = #ifdef E2_COMMANDQ e2_command_run (cmd, E2_COMMAND_RANGE_DEFAULT, FALSE); #else e2_command_run (cmd, E2_COMMAND_RANGE_DEFAULT); #endif g_free (cmd); if (newstate) //doing a mount { #ifdef E2_FAM e2_filelist_request_refresh (curr_view->dir, FALSE); e2_filelist_request_refresh (other_view->dir, TRUE); #else e2_filelist_check_dirty (GINT_TO_POINTER (1)); #endif } else //doing an unmount { #ifdef E2_HAL if (remove) { cmd = g_strdup_printf ("eject \"%s\"", point); //CHECKME _I( # ifdef E2_COMMANDQ e2_command_run_at (cmd, elsewhere, E2_COMMAND_RANGE_DEFAULT, FALSE); # else e2_command_run_at (cmd, elsewhere, E2_COMMAND_RANGE_DEFAULT); # endif g_free (cmd); } #endif if (elsewhere != NULL) { //E2_VFSTMP may change space too // e2_pane_change_dir (curr_pane, point); //not needed, with inofify at least g_free (elsewhere); } } /* if (!(newstate || result == 0)) { //change back to where we started e2_pane_change_dir (curr_pane, olddir); g_free (olddir); } */ } /** @brief add check item to @a menu Can't use e2_menu_add_check() because we don't want mnemonics @param menu menu widget @param label menu item text @param state initial state of the created item, T/F @param func void* callback function for handling menu item selection, or NULL @return the menu item widget */ static GtkWidget *_e2_menu_mount_add_check (GtkWidget *menu, gchar *label, gboolean state, void *func) { GtkWidget *check = gtk_check_menu_item_new_with_label (label); gtk_menu_shell_append (GTK_MENU_SHELL (menu), check); gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (check), state); g_signal_connect (G_OBJECT (check), "toggled", G_CALLBACK (func), NULL); gtk_widget_show (check); return check; } /** @brief create and pop up a mountpoints menu @param from the button that was clicked to popup the menu @param art action runtime data @return TRUE if the action succeeded */ gboolean e2_menu_create_mounts_menu (gpointer from, E2_ActionRuntime *art) { gchar *point; GList *mounts, *mountables, *member, *node; GtkWidget *item; GtkWidget *menu = gtk_menu_new (); mounts = e2_fs_mount_get_mounts_list (); for (member = mounts ; member != NULL; member = member->next) { item = _e2_menu_mount_add_check (menu, (gchar *) member->data, TRUE, _e2_menu_mount_cb); g_object_set_data_full (G_OBJECT (item), "_mountpoint_", member->data, g_free); } mountables = e2_fs_mount_get_mountable_list (); for (member = mountables ; member != NULL; member = member->next) { point = (gchar *) member->data; gboolean mounted = FALSE; for (node = mounts; node != NULL; node = node->next) { if (g_str_equal (point, (gchar *) node->data)) { mounted = TRUE; break; } } if (!mounted) { item = _e2_menu_mount_add_check (menu, (gchar *) member->data, FALSE, _e2_menu_mount_cb); g_object_set_data_full (G_OBJECT (item), "_mountpoint_", g_strdup ((gchar *) member->data), g_free); } } g_list_free (mounts); //its data are cleared with the menu e2_list_free_with_data (&mountables); g_signal_connect (G_OBJECT (menu), "selection-done", G_CALLBACK (e2_menu_destroy_cb), NULL); if (g_str_equal (IS_A (from), "GtkButton")) gtk_menu_popup (GTK_MENU (menu), NULL, NULL, (GtkMenuPositionFunc) e2_toolbar_set_menu_position, from, 1, 0); else //FIXME gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 1, 0); return TRUE; } #endif //def E2_FS_MOUNTABLE /** @brief install default tree options for custom menus This function is called only if the default is missing from the config file This default set data is for example purposes only, the user must provide real data @param set pointer to set data @return */ static void _e2_menu_tree_defaults (E2_OptionSet *set) { e2_option_tree_setup_defaults (set, g_strdup("custom-menus=<"), //internal name g_strdup("fusemounts||||"), //menu lookup name //these are examples only, no translation g_strconcat ("\t|vfs_on_"E2IP".png|","_Mount","|","Mount tip","|","mount command $FTPDIR",NULL), g_strconcat ("\t|vfs_off_"E2IP".png|","_Unmount choices","||",NULL), g_strconcat ("\t\t||","_Unmount","|","Unount tip","|","fusermount -u $FTPDIR",NULL), g_strdup(">"), NULL); } /** @brief setup tree option for custom menus @return */ void e2_menu_custom_option_register (void) { gchar *group_name = g_strconcat(_C(20) ,".",_C(9),NULL); //_("interface.custom menus"; E2_OptionSet *set = e2_option_tree_register ("custom-menus", group_name, _C(9), //no translation NULL, NULL, NULL, E2_OPTION_TREE_UP_DOWN | E2_OPTION_TREE_ADD_DEL, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP | E2_OPTION_FLAG_BUILDBARS); e2_option_tree_add_column (set, _("Menu"), E2_OPTION_TREE_TYPE_STR, 0, "", 0, NULL, NULL); //since this a a new addition to config files, put the icon before the label e2_option_tree_add_column (set, _("Icon"), E2_OPTION_TREE_TYPE_ICON, 0, "", 0, NULL, NULL); e2_option_tree_add_column (set, _("Label"), E2_OPTION_TREE_TYPE_STR, 0, "", 0, NULL, NULL); e2_option_tree_add_column (set, _("Tooltip"), E2_OPTION_TREE_TYPE_STR, 0, "", 0, NULL, NULL); e2_option_tree_add_column (set, _("Command"), E2_OPTION_TREE_TYPE_STR, 0, "", 0, NULL, GINT_TO_POINTER (E2_ACTION_EXCLUDE_TOGGLE)); e2_option_tree_create_store (set); e2_option_tree_prepare_defaults (set, _e2_menu_tree_defaults); } emelfm2-0.4.1/src/utils/e2_widget.c0000600000175000017500000007606010772054116015767 0ustar cairocairo/* $Id: e2_widget.c 841 2008-03-25 01:41:34Z tpgww $ Copyright (C) 2003-2008 tooar Portions copyright (C) 1999 Michael Clark. This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/utils/e2_widget.c @brief miscelleanous GtkWidget utility functions This file contains various miscelleanous GtkWidget utility functions. */ #include "emelfm2.h" #include #include "e2_widget.h" #include "e2_dialog.h" #ifdef USE_GTK2_12TIPS typedef struct { gboolean oneactive; gchar *tip1; gchar *tip2; } E2_TipsToggle; static void _e2_widget_destroy_ttips (E2_TipsToggle *data) { g_free (data->tip1); g_free (data->tip2); DEMALLOCATE (E2_TipsToggle, data); } /** @brief setup for a button to have 2 alternate tooltips @param widget the button widget @param initialtip initially-displayed tip, utf-8, my have markup @param othertip alternate tip, utf-8, my have markup @return */ void e2_widget_set_toggletip (GtkWidget *widget, const gchar *initialtip, const gchar *othertip) { //a tooltip window doesn't exist until the tip is ready to show E2_TipsToggle *data = MALLOCATE (E2_TipsToggle); if (data != NULL) { data->tip1 = g_strdup (initialtip); data->tip2 = g_strdup (othertip); data->oneactive = TRUE; g_object_set_data_full (G_OBJECT (widget), "btn-toggle-data", data, (GDestroyNotify) _e2_widget_destroy_ttips); } gtk_widget_set_tooltip_text (widget, initialtip); } /** @brief toggle the visiblity of 2 tip labels setup by e2_widget_set_toggletip() @param widget the button widget @return */ void e2_widget_swap_tooltip (GtkWidget *widget) { E2_TipsToggle *data = g_object_get_data (G_OBJECT (widget), "btn-toggle-data"); if (data != NULL) { if (data->oneactive) { gtk_widget_set_tooltip_text (widget, data->tip2); data->oneactive = FALSE; } else { gtk_widget_set_tooltip_text (widget, data->tip1); data->oneactive = TRUE; } } } #else void e2_widget_set_tooltip (GtkTooltips *tooltips, GtkWidget *widget, gchar *text) { if (text == NULL) return; if (tooltips == NULL) tooltips = app.tooltips; //use default tooltip group gtk_tooltips_set_tip (tooltips, widget, text, NULL); } void e2_widget_swap_tooltip (GtkWidget *widget) { GtkTooltipsData *tips = gtk_tooltips_data_get (widget); gchar *tmp = tips->tip_text; tips->tip_text = tips->tip_private; tips->tip_private = tmp; } #endif /** @brief get image widget for an icon @param icon gtk-stock-item name, or localised custom-icon filename with or without path @param size enumerator of the desired image size @return Image widget, or NULL */ GtkWidget *e2_widget_get_icon (const gchar *icon, GtkIconSize size) { GtkWidget *image; if (icon == NULL || *icon == '\0') return NULL; #ifdef E2_IMAGECACHE //get cached image, after adding it to the cache if not there already E2_Image *cache_img = e2_cache_image_get (icon, size); image = gtk_image_new_from_pixbuf (cache_img->pixbuf); #else if (e2_utils_check_stock_icon (icon)) image = gtk_image_new_from_stock (icon, size); else { GdkPixbuf *pixbuf; gchar *fullname; if (g_path_is_absolute (icon)) fullname = icon; else { gchar *localpath = e2_utils_get_icons_path (TRUE); fullname = e2_utils_strcat (localpath, icon); g_free (localpath); } if ((pixbuf = gdk_pixbuf_new_from_file (fullname, NULL)) != NULL) { GdkPixbuf *pixbuf2; gint width, height; if (!gtk_icon_size_lookup (size, &width, &height)) { width = 16; height = 16; } pixbuf2 = gdk_pixbuf_scale_simple (pixbuf, width, height, GDK_INTERP_BILINEAR); image = gtk_image_new_from_pixbuf (pixbuf2); g_object_unref (pixbuf2); g_object_unref (pixbuf); } else image = NULL; if (fullname != icon) g_free (fullname); } #endif return image; } /** @brief create a vertically-centred GtkLabel and add it to @a table @param table the table to attach the new label into @param text the text for the new label, may have markup @param xalign the label's horizontal alignment @param left column number to attach the left side of the label to @param right column number to attach the right side of the label to @param top row number to attach the top of the label to @param bottom row number to attach the bottom of the label to @return the label widget */ GtkWidget *e2_widget_add_mid_label_to_table (GtkWidget *table, gchar *text, gfloat xalign, gint left, gint right, gint top, gint bottom) { GtkWidget *label; label = gtk_label_new (NULL); gtk_label_set_markup (GTK_LABEL (label), text); gtk_label_set_use_markup (GTK_LABEL (label), TRUE); gtk_misc_set_alignment (GTK_MISC (label), xalign, 0.5); // gtk_label_set_selectable (GTK_LABEL (label), FALSE); gtk_table_attach_defaults (GTK_TABLE (table), label, left, right, top, bottom); gtk_widget_show (label); return label; } /** @brief create a vertically-centred GtkLabel and if @a box is non-NULL, pack the entry into that @param box a box widget to hold the label, or NULL @param text the text for the new label, may have markup @param xalign the label's horizontal alignment @param exp expandable property for packing the label into @a box @param pad padding for packing the label into @a box @return the label widget */ GtkWidget *e2_widget_add_mid_label (GtkWidget *box, const gchar *text, gfloat xalign, gboolean exp, guint pad) { return e2_widget_add_label (box, text, xalign, 0.5, exp, pad); } /** @brief create a GtkLabel and if @a box is non-NULL, pack the entry into that @param box a box widget to hold the label, or NULL @param text the text for the new label, may have markup @param xalign the label's horizontal alignment @param yalign the label's vertical alignment @param exp expandable property for packing the label into @a box @param pad padding for packing the label into @a box @return the label widget */ GtkWidget *e2_widget_add_label (GtkWidget *box, const gchar *text, gfloat xalign, gfloat yalign, gboolean exp, guint pad) { GtkWidget *label = gtk_label_new (NULL); gtk_label_set_markup (GTK_LABEL (label), text); // gtk_label_set_use_markup (GTK_LABEL (label), TRUE); gtk_misc_set_alignment (GTK_MISC (label), xalign, yalign); gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); gtk_label_set_selectable (GTK_LABEL (label), FALSE); gtk_widget_show (label); if (box != NULL) gtk_box_pack_start (GTK_BOX (box), label, exp, TRUE, pad); return label; } /** @brief callback for entry widget key-press, to shorten string in @a entry Delete removes from cursor to end @param entry the entry widget @param event pointer to event data struct @param data UNUSED data specified when callback was connnected @return TRUE if the key was Delete */ static gboolean _e2_widget_entry_keypress_cb (GtkWidget *entry, GdkEventKey *event, gpointer data) { if (event->keyval == GDK_Delete) { guint modifiers = gtk_accelerator_get_default_mod_mask (); if ((event->state & modifiers) == GDK_SHIFT_MASK) { gint start = gtk_editable_get_position (GTK_EDITABLE (entry)); gtk_editable_delete_text (GTK_EDITABLE (entry), start, -1); return TRUE; } } return FALSE; } /** @brief create a GtkEntry and if @a box is non-NULL, pack the entry into that @param box a box widget to hold the entry, or NULL @param init_text the initial text for the new entry, or NULL @param exp expandable property for packing the entry into @a box @param select_text TRUE to select the initial text in the entry @return the entry widget */ GtkWidget *e2_widget_add_entry (GtkWidget *box, gchar *init_text, gboolean exp, gboolean select_text) { GtkWidget *entry; entry = gtk_entry_new (); if (init_text != NULL) { gtk_entry_set_text (GTK_ENTRY (entry), init_text); if (select_text) gtk_editable_select_region (GTK_EDITABLE (entry), 0, -1); } gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE); g_signal_connect (G_OBJECT (entry), "key-press-event", G_CALLBACK (_e2_widget_entry_keypress_cb), NULL); if (box != NULL) gtk_box_pack_start (GTK_BOX (box), entry, exp, TRUE, E2_PADDING); gtk_widget_grab_focus (entry); gtk_widget_show (entry); return entry; } /* * @brief create a GtkEntry and add it to @a table @param table the table to attach the new entry into @param init_text the initial text for the new entry, or NULL @param select_text TRUE to select the initial text in the entry @param left column number to attach the left side of the entry to @param right column number to attach the right side of the entry to @param top row number to attach the top of the entry to @param bottom row number to attach the bottom of the entry to @return the entry widget */ /*UNUSED GtkWidget *e2_widget_add_entry_to_table (GtkWidget *table, gchar *init_text, gboolean select_text gint left, gint right, gint top, gint bottom) { GtkWidget *entry; entry = gtk_entry_new (); if (init_text != NULL) { gtk_entry_set_text (GTK_ENTRY (entry), init_text); if (select_text) gtk_editable_select_region (GTK_EDITABLE (entry), 0, -1); } gtk_table_attach_defaults (GTK_TABLE (table), entry, left, right, top, bottom); gtk_widget_show (entry); return entry; } */ /** @brief create a horizontal or vertical box widget @param vertical TRUE for a vbox, FALSE for a hbox @param homogen the new box's "homogenous" parameter @param homogen the new box's "spacing" parameter @return the widget, unshown */ GtkWidget *e2_widget_get_box (gboolean vertical, gboolean homogen, gint spacing) { GtkWidget *box = (vertical) ? gtk_vbox_new (homogen, spacing): gtk_hbox_new (homogen, spacing); return box; } /** @brief create a horizontal or vertical box widget, and pack it into @a parentbox @param parentbox the parent box to pack the new box into @param exp expandable property for packing the box into @a parentbox @param pad padding for packing the box into @a parentbox @param vertical TRUE for a vbox, FALSE for a hbox @param homogen the new box's "homogenous" parameter @param homogen the new box's "spacing" parameter @return the new box widget */ GtkWidget *e2_widget_add_box (GtkWidget *parentbox, gboolean exp, guint pad, gboolean vertical, gboolean homogen, gint spacing) { GtkWidget *box = e2_widget_get_box (vertical, homogen, spacing); gtk_box_pack_start (GTK_BOX (parentbox), box, exp, TRUE, pad); gtk_widget_show (box); return box; } /** @brief create a GtkScrolledWindow without a shadow @param h_policy scrolled window horizontal scrolling policy @param v_policy scrolled window vertical scrolling policy @return the scrolled window widget, unshown */ GtkWidget *e2_widget_get_sw_plain (GtkPolicyType h_policy, GtkPolicyType v_policy) { return e2_widget_get_sw (h_policy, v_policy, GTK_SHADOW_NONE); } /** @brief create a GtkScrolledWindow This function creates a GtkScrolledWindow. The parameters @a h_policy and @a v_policy are used to set the scrolling policy of the scrolled window. The shadow type is set to @a shadow. @param h_policy scrolled window horizontal scrolling policy @param v_policy scrolled window vertical scrolling policy @param shadow scrolled window shadow type @return the scrolled window widget, unshown */ GtkWidget *e2_widget_get_sw (GtkPolicyType h_policy, GtkPolicyType v_policy, GtkShadowType shadow) { GtkWidget *scrolled_window = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), h_policy, v_policy); gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), shadow); return scrolled_window; } /** @brief create a GtkScrolledWindow and add it to a GtkBox This function creates a GtkScrolledWindow using e2_widget_get_sw_plain() and adds it to @a box. The parameters @a h_policy and @a v_policy are used as arguments for e2_widget_get_sw_plain(). @a exp and @a pad are used as arguments for gtk_box_pack_start() to adjust the packing of the scrolled window. @param box the parent box to pack the scrolled window to @param h_policy scrolled window horizontal scrolling policy @param v_policy scrolled window vertical scrolling policy @param exp what to do with additional free space in the box @param pad number of pixels to pad the scrolled window in the box @return the scrolled window */ GtkWidget *e2_widget_add_sw (GtkWidget *box, GtkPolicyType h_policy, GtkPolicyType v_policy, gboolean exp, guint pad) { GtkWidget *scrolled_window = e2_widget_get_sw_plain (h_policy, v_policy); gtk_box_pack_start (GTK_BOX (box), scrolled_window, exp, TRUE, pad); gtk_widget_show (scrolled_window); return scrolled_window; } /** @brief add a child with a viewport to a GtkScrolledWindow In contrast to the @a e2_widget_[add|get]* functions, this function does not create a scrolled window. It only adds a viewport to the scrolled window @a sw and then adds @a child to the viewport. his is done by calling gtk_scrolled_window_add_with_viewport(). The shadow type of the viewport is set to GTK_SHADOW_NONE. @param sw the scrolled window @param child the child that should be added to the scrolled window @return */ void e2_widget_sw_add_with_viewport (GtkWidget *sw, GtkWidget *child) { gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), child); gtk_viewport_set_shadow_type (GTK_VIEWPORT (GTK_BIN (sw)->child), GTK_SHADOW_NONE); } /** @brief create a horizontal or vertical separator widget, and pack it into @a box @param box the box to pack the new separator into, may be NULL @param exp expandable property for packing the separator into @a box @param pad padding for packing the separator into @a box @return the separator widget (horizontal-type if @a box is NULL) */ GtkWidget *e2_widget_add_separator (GtkWidget *box, gboolean exp, guint pad) { GtkWidget *sep; if (box != NULL && GTK_IS_HBOX (box)) sep = gtk_vseparator_new (); else if (box == NULL || GTK_IS_VBOX (box)) sep = gtk_hseparator_new (); else return NULL; if (box != NULL) gtk_box_pack_start (GTK_BOX (box), sep, exp, TRUE, pad); gtk_widget_show (sep); return sep; } /** @brief create a table widget, and pack it into @a box @param box the box to pack the new separator into, may be NULL @param rows the no. of rows for the table @param cols the no. of columns for the table @param homogen TRUE if all table cells are to be resized to the size of the cell containing the largest widget @param exp expandable property for packing the separator into @a box @param pad padding for packing the separator into @a box @return the table widget */ GtkWidget *e2_widget_add_table (GtkWidget *box, gint rows, gint cols, gboolean homogen, gboolean exp, guint pad) { GtkWidget *table; table = gtk_table_new (rows, cols, homogen); if (box != NULL) gtk_box_pack_start (GTK_BOX (box), table, exp, TRUE, pad); gtk_widget_show (table); return table; } /*UNUSED GtkWidget *e2_widget_add_framed_table (GtkWidget *box, gchar *title, gint rows, gint cols, gboolean exp, guint pad) { GtkWidget *frame, *table; frame = gtk_frame_new (title); gtk_box_pack_start (GTK_BOX (box), frame, exp, TRUE, pad); gtk_widget_show (frame); table = gtk_table_new (rows, cols, FALSE); gtk_container_set_border_width (GTK_CONTAINER (table), E2_PADDING_XSMALL); gtk_table_set_row_spacings (GTK_TABLE (table), E2_PADDING); gtk_container_add (GTK_CONTAINER (frame), table); gtk_widget_show (table); return table; } UNUSED GtkWidget *e2_widget_add_framed_widget (GtkWidget *box, gchar *title, GtkWidget *widget, gboolean exp, guint pad) { GtkWidget *frame; frame = gtk_frame_new (title); gtk_box_pack_start (GTK_BOX (box), frame, exp, TRUE, pad); gtk_container_add (GTK_CONTAINER (frame), widget); gtk_widget_show (widget); gtk_widget_show (frame); return frame; } */ GtkWidget *e2_widget_add_frame (GtkWidget *box, gboolean fill, guint pad, gchar *title, gboolean stretch) { gchar *real_title = NULL; if (title != NULL) real_title = g_strconcat (" ", title, " ", NULL); GtkWidget *frame = gtk_frame_new (real_title); if (title != NULL) { g_free (real_title); GtkWidget *label = gtk_frame_get_label_widget (GTK_FRAME (frame)); gtk_label_set_use_markup (GTK_LABEL (label), TRUE); } gtk_box_pack_start (GTK_BOX (box), frame, fill, fill, pad); return frame; } GtkWidget *e2_widget_add_eventbox (GtkWidget *box, gboolean fill, guint pad) { GtkWidget *event = gtk_event_box_new (); gtk_box_pack_start (GTK_BOX (box), event, fill, fill, pad); return event; } /** @brief create a new GtkNotebook @param func the switch-page callback function or NULL @param data data pointer for the callback function @return the notebook widget, unshown */ GtkWidget *e2_widget_get_notebook (gpointer func, gpointer data) { GtkWidget *notebook = gtk_notebook_new (); if (func != NULL) g_signal_connect (G_OBJECT (notebook), "switch-page", G_CALLBACK (func), data); return notebook; } /** @brief create a notebook and add it to @a box @param box the box to add the notebook to @param fill fill parameter for the box child @param pad pad parameter for the box child @param func the switch-page callback function for the notebook or NULL @param data data pointer for the callback function @return the notebook widget, unshown */ GtkWidget *e2_widget_add_notebook (GtkWidget *box, gboolean fill, guint pad, gpointer func, gpointer data) { GtkWidget *notebook = e2_widget_get_notebook (func, data); gtk_box_pack_start (GTK_BOX (box), notebook, fill, fill, pad); return notebook; } /** @brief create a notebook page and add it to @a notebook In order to make the initial displayed size of the notebook reflect the size of its largest child, may wish to make @a swpolicy GTK_POLICY_NEVER, and change it after notebook is sized, e.g. somewhere else: g_signal_connect (G_OBJECT (GTK_DIALOG (dialog)), "show", G_CALLBACK (e2_dialog_show_notebook_cb), GTK_NOTEBOOK (book)); @param notebook the notebook widget @param tabname string with name for the tab @param swpolicy scrollbar policy applied to scrolled window in the tab @return vbox widget into which tab content can be packed */ GtkWidget *e2_widget_add_notebook_page (GtkWidget *notebook, gchar *tabname, GtkPolicyType swpolicy) { GtkWidget *outerbox = e2_widget_get_box (TRUE, FALSE, 0); GtkWidget *label = gtk_label_new (tabname); gtk_notebook_append_page (GTK_NOTEBOOK (notebook), outerbox, label); GtkWidget *scrolled = e2_widget_add_sw (outerbox, swpolicy, swpolicy, TRUE, 0); //in case of later sw policy-change, make it accessible g_object_set_data (G_OBJECT (outerbox), "e2-tab-scrolled-window", scrolled); GtkWidget *vbox = e2_widget_get_box (TRUE, FALSE, 0); e2_widget_sw_add_with_viewport (scrolled, vbox); return vbox; } /** @brief set font for @a widget to @a font_string @param widget widget using the font @param font_string string naming the font @return */ void e2_widget_set_font (GtkWidget *widget, const gchar *font_string) { PangoFontDescription *font_desc; font_desc = pango_font_description_from_string (font_string); gtk_widget_modify_font (widget, font_desc); pango_font_description_free (font_desc); } /** @brief get approximate size of a character used in @a widget This retrieves approximate width and/or height of a character in the font used in @a widget. So it should be called only after a font has been assigned @a height may not be the whole distance between lines (e.g. spacing applies) @param widget the widget to be evaluated @param width pointer to int to store the character pixel-width, or NULL @param height pointer to int to store the character pixel-height, or NULL @return */ void e2_widget_get_font_pixels (GtkWidget *widget, gint *width, gint *height) { PangoContext *context = gtk_widget_get_pango_context (widget); PangoFontMetrics *metrics = pango_context_get_metrics (context, widget->style->font_desc, pango_context_get_language (context)); if (width != NULL) *width = PANGO_PIXELS (pango_font_metrics_get_approximate_char_width (metrics)); if (height != NULL) *height = PANGO_PIXELS (pango_font_metrics_get_ascent (metrics) + pango_font_metrics_get_descent (metrics)); pango_font_metrics_unref (metrics); /* PangoFontDescription *font_desc = gtk_widget_get_style (widget)->font_desc; *height = pango_font_description_get_size (font_desc) / PANGO_SCALE * DPI / 72; //(pixels/in/points/in); height = size of the font in points, scaled by PANGO_SCALE, (i.e. a size value of 10 * PANGO_SCALE is a 10 point font. For screen display, a logical DPI of 96 is common, what is it actually ? or, if pango_font_description_get_size_is_absolute(), returned size is pixels * PANGO_SCALE *height = pango_font_description_get_size (font_desc) / PANGO_SCALE PangoLanguage *language = pango_context_get_language (context); const gchar *defstr = pango_language_get_sample_string (language); gint count = g_utf8_strlen (defstr, -1); gint _width, _height; PangoLayout *layout; // layout = gtk_widget_create_pango_layout (widget, "m"); layout = gtk_widget_create_pango_layout (widget, defstr); // layout = gtk_widget_create_pango_layout (widget, _("abcdefghijklmnopqrstuvwxyz.ABCDEFGHIJKLMNOPQRSTUVWXYZ")); // layout = gtk_widget_create_pango_layout (widget, _("abcd efgh ABCD EFGH")); pango_layout_get_pixel_size (layout, &_width, &_height); // g_object_unref (layout); // if (width != NULL) // *width = _width; // *width = _width/53; // *width = _width/19; // if (height != NULL) // *height = _height; _width /= count; layout = gtk_widget_create_pango_layout (widget, ("a\na\n")); pango_layout_set_single_paragraph_mode (layout, FALSE); gint debug = PANGO_PIXELS (pango_layout_get_spacing (layout)); g_object_unref (layout); */ } /** @brief callback upon expander-widget open/close Show/hide the expander child widget, and if not blocked by the controller widget, update the associated set value, if any @param expander the widget that changed @param spec @param boolset pointer to data for boolean set tied to expander state, or NULL @return */ static void _e2_widget_expander_toggled_cb (GtkWidget *expander, GParamSpec *spec, E2_OptionSet *boolset) { printd (DEBUG, "_e2_widget_toggle_expander_cb"); gboolean value = gtk_expander_get_expanded (GTK_EXPANDER (expander)); GtkWidget *show = g_object_get_data (G_OBJECT (expander), "e2-show-widget"); if (show != NULL) { if (value) { if (!GTK_WIDGET_VISIBLE (show)) gtk_widget_show (show); } else if (GTK_WIDGET_VISIBLE (show)) gtk_widget_hide (show); } if (boolset != NULL) { GtkWidget *controller = g_object_get_data (G_OBJECT (expander), "e2-controller-widget"); if (!GPOINTER_TO_INT (g_object_get_data (G_OBJECT (controller), "e2-controller-blocked"))) e2_option_bool_set_direct (boolset, value); } } /** @brief set @a expander state if it's not blocked and not already at the desired state This is a hook-function callack upon change of set data associated with @a expander @param state pointerised TRUE/FALSE, the value to apply to @a expander @param expander the widget to change @return TRUE always */ static gboolean _e2_widget_set_expander (gpointer state, GtkWidget *expander) { GtkWidget *controller = g_object_get_data (G_OBJECT (expander), "e2-controller-widget"); if (!GPOINTER_TO_INT (g_object_get_data (G_OBJECT (controller), "e2-controller-blocked"))) { gboolean value = GPOINTER_TO_INT (state); gboolean current = gtk_expander_get_expanded (GTK_EXPANDER (expander)); if (value != current) gtk_expander_set_expanded (GTK_EXPANDER (expander), value); } return TRUE; } /** @brief add expander and a child widget to @a box @param parentbox the parent box to pack the new expander into @param text content of label for the expander @param boolset pointer to data for boolean set that's tied to the expander state, or NULL @param controller widget which may have "e2-controller-blocked" data to prevent set value being updated when expander toggles @param child widget shown/hidden according to expander state, also packed into @a parentbox @return the expander widget */ GtkWidget *e2_widget_add_expander (GtkWidget *parentbox, gchar *text, E2_OptionSet *boolset, GtkWidget *controller, GtkWidget *child) { GtkWidget *expander = gtk_expander_new (text); g_object_set_data (G_OBJECT (expander), "e2-controller-widget", controller); g_object_set_data (G_OBJECT (expander), "e2-show-widget", child); g_signal_connect (G_OBJECT (expander), "notify::expanded", G_CALLBACK (_e2_widget_expander_toggled_cb), boolset); if (boolset != NULL) e2_option_attach_value_changed_simple (boolset, expander, _e2_widget_set_expander, expander); gtk_container_set_border_width (GTK_CONTAINER (child), E2_PADDING_SMALL); gtk_box_pack_start (GTK_BOX (parentbox), expander, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (parentbox), child, FALSE, FALSE, 0); gtk_widget_show (expander); //some wm's like this to be shown before any "expander-toggle" in the toggle cb if (boolset != NULL && e2_option_bool_get_direct (boolset)) gtk_widget_show (child); return expander; } /** @brief callback upon tied check-button click If not blocked by the controller widget, update the associated set value @param button the widget that changed @param boolset pointer to data for boolean set tied to button state @return */ static void _e2_widget_check_changed_cb (GtkWidget *button, E2_OptionSet *boolset) { GtkWidget *controller = g_object_get_data (G_OBJECT (button), "e2-controller-widget"); if (!GPOINTER_TO_INT (g_object_get_data (G_OBJECT (controller), "e2-controller-blocked"))) e2_option_bool_set_direct (boolset, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))); } /** @brief set @a button state if it's not blocked and not already at the desired state This is a hook-function callack upon change of set data associated with @a button @param state pointerised TRUE/FALSE, the value to apply to @a expander @param button the widget to change @return TRUE always */ static gboolean _e2_widget_set_check_state (gpointer state, GtkWidget *button) { GtkWidget *controller = g_object_get_data (G_OBJECT (button), "e2-controller-widget"); if (!GPOINTER_TO_INT (g_object_get_data (G_OBJECT (controller), "e2-controller-blocked"))) { gboolean value = GPOINTER_TO_INT (state); gboolean current = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)); if (value != current) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), value); } return TRUE; } /** @brief add to @a box a check button whose value is tied to value of @a set @param box the widget to hold the button @param boolset pointer to config data for the tied set @param controller widget which may have "e2-controller-blocked" data to prevent set value being updated when button toggles, can be NULL @return the created button widget */ GtkWidget *e2_widget_add_tied_check_button (GtkWidget *box, E2_OptionSet *boolset, GtkWidget *controller) { boolset->widget = e2_button_add_toggle (box, TRUE, boolset->ival, boolset->desc, NULL, FALSE, 0, _e2_widget_check_changed_cb, boolset); g_object_set_data (G_OBJECT (boolset->widget), "e2-controller-widget", controller); e2_option_attach_value_changed_simple (boolset, boolset->widget, _e2_widget_set_check_state, boolset->widget); #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif boolset->widget, boolset->tip); e2_widget_handle_depends (boolset->widget, boolset); return boolset->widget; } /** @brief change sensitivity of @a menu_item after value-change of dependant set @param state pointerised TRUE/FALSE, the value to use to adjust to @a widget @param widget the widget to change @return TRUE always */ static gboolean _e2_widget_dependent_changed_cb (gpointer state, GtkWidget *widget) { gboolean value = GPOINTER_TO_INT (state); if (value && !GTK_WIDGET_SENSITIVE (widget)) gtk_widget_set_sensitive (widget, TRUE); else if (!value && GTK_WIDGET_SENSITIVE (widget)) gtk_widget_set_sensitive (widget, FALSE); return TRUE; } /** @brief change sensitivity of @a menu_item after value-change of set with inverse dependency @param state pointerised TRUE/FALSE, the value to use to adjust to @a widget @param widget the widget to change @return TRUE always */ static gboolean _e2_widget_invdependent_changed_cb (gpointer state, GtkWidget *widget) { gboolean value = GPOINTER_TO_INT (state); if (value && GTK_WIDGET_SENSITIVE (widget)) gtk_widget_set_sensitive (widget, FALSE); else if (!value && !GTK_WIDGET_SENSITIVE (widget)) gtk_widget_set_sensitive (widget, TRUE); return TRUE; } /** @brief adjust current and future widget sensitivity according to a dependent set's data @param widget the widget to process @param boolset pointer to data for set tied to @a widget @return */ void e2_widget_handle_depends (GtkWidget *widget, E2_OptionSet *boolset) { if (boolset->depends != NULL) { E2_OptionSet *dep; if (boolset->depends[0] == '!') { dep = e2_option_get (boolset->depends + 1); if ((dep != NULL) && (dep->type == E2_OPTION_TYPE_BOOL)) { gtk_widget_set_sensitive (widget, !e2_option_bool_get_direct (dep)); e2_option_attach_value_changed_simple (dep, widget, _e2_widget_invdependent_changed_cb, widget); } } else { dep = e2_option_get (boolset->depends); if ((dep != NULL) && (dep->type == E2_OPTION_TYPE_BOOL)) { gtk_widget_set_sensitive (widget, e2_option_bool_get_direct (dep)); e2_option_attach_value_changed_simple (dep, widget, _e2_widget_dependent_changed_cb, widget); } } } } #ifdef E2_ASSISTED /** @brief set relations on @a label for @a widget and vice-versa This enables accessiblity tools to identify @a label as descriptive item for @a @widget etc @param label the label associated with @a widget @param widget the widget to be tagged @return */ void e2_widget_set_label_relations (GtkLabel *label, GtkWidget *widget) { AtkObject *atklabelob = gtk_widget_get_accessible (GTK_WIDGET (label)); AtkObject *atkob = gtk_widget_get_accessible (widget); if (G_LIKELY (atkob != NULL && atklabelob != NULL)) { AtkRelationSet *relset = atk_object_ref_relation_set (atklabelob); AtkRelation *relation = atk_relation_new (&atkob, 1, ATK_RELATION_LABEL_FOR); atk_relation_set_add (relset, relation); g_object_unref (G_OBJECT (relation)); relset = atk_object_ref_relation_set (atkob); relation = atk_relation_new (&atklabelob, 1, ATK_RELATION_LABELLED_BY); atk_relation_set_add (relset, relation); g_object_unref (G_OBJECT (relation)); } } /** @brief create accessibility object for @a widget @param widget the widget to be tagged @param label the label associated with @a widget @return the atk object */ AtkObject *e2_widget_get_accessible (GtkWidget *widget, const gchar *name, const gchar *desc, AtkRole role) { AtkObject *atkob = gtk_widget_get_accessible (widget); if (G_LIKELY (atkob != NULL)) { // set custom atk properties if (name != NULL) atk_object_set_name (atkob, name); if (desc != NULL) atk_object_set_description (atkob, desc); if (role >= ATK_ROLE_LAST_DEFINED) //no custom roles, for now at least role = ATK_ROLE_INVALID; atk_object_set_role (atkob, role); } return atkob; } #endif emelfm2-0.4.1/src/utils/e2_hook.c0000600000175000017500000000755010723510626015441 0ustar cairocairo/* $Id: e2_hook.c 767 2007-11-29 10:16:54Z tpgww $ Copyright (C) 2004-2007 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/utils/e2_hook.c @brief Hook utility functions This file contains hook utility functions to register hooks in a hook_list and to run the hook list. */ #include "emelfm2.h" #include "e2_hook.h" #include "e2_utils.h" /** @brief remove @a hook from @a hook_list @param hook_list pointer to struct related to list of hook functions @param hook data struct for the hook func to unregister @return TRUE if @a hook != NULL and was destroyed */ static gboolean _e2_hook_unregister (GHookList *hook_list, GHook *hook) { if (hook != NULL) { g_hook_destroy_link (hook_list, hook); return (!G_HOOK_IS_VALID (hook)); } return FALSE; } /** @brief run hook function of @a hook @param hook data struct for the hook func @param data pointer to data supplied to e2_hook_list_run() @return the value returned by the hook func */ static gboolean _e2_hook_list_run (GHook *hook, gpointer data) { // printd (DEBUG, "_e2_hook_list_run (hook:,data:)"); gboolean (*func) (gpointer, gpointer) = hook->func; return func (data, hook->data); } /*********************/ /***** callbacks *****/ /*********************/ /** @brief unattach hook function referred to in @a duo @param duo pointer to data struct with GHookList* and GHook* @return */ void e2_hook_unattach_cb (E2_Duo *duo) { _e2_hook_unregister (duo->b, duo->a); DEMALLOCATE (E2_Duo, duo); } /******************/ /***** public *****/ /******************/ /** @brief add a hook for running @a func to @a hook_list @param hook_list pointer to struct related to list of hook functions @param func pointer to function to run @param data pointer to data for @a func @return the data struct for the hook func */ GHook *e2_hook_register (GHookList *hook_list, gpointer func, gpointer data) { if (!hook_list->is_setup) g_hook_list_init (hook_list, sizeof (GHook)); GHook *hook = g_hook_alloc (hook_list); hook->data = data; hook->func = func; g_hook_append (hook_list, hook); return hook; } /** @brief remove the hook in @a hook_list which runs @a func @param hook_list pointer to struct related to list of hook functions @param func pointer to function to find @param data pointer to data to find if @a use_data is TRUE @param use_data TRUE to find a hook with data matching @a data @return TRUE if the hook was found and destroyed */ gboolean e2_hook_unregister (GHookList *hook_list, gpointer func, gpointer data, gboolean use_data) { GHook *hook = (use_data) ? g_hook_find_func_data (hook_list, TRUE, func, data): g_hook_find_func (hook_list, TRUE, func); return _e2_hook_unregister (hook_list, hook); } /** @brief run functions in @a hook_list, supplying them @a data Currently-running functions in the list are skipped Any hook func that returns FALSE will be removed from the hooklist @param hook_list pointer to struct related to list of hook functions @param data data supplied as 1st argument to each hook func @return */ void e2_hook_list_run (GHookList *hook_list, gpointer data) { // printd (DEBUG, "e2_hook_list_run (hook_list: %x,data: %x)", hook_list, data); if (hook_list->is_setup) g_hook_list_marshal_check (hook_list, FALSE, _e2_hook_list_run, data); } emelfm2-0.4.1/src/utils/e2_tree.c0000600000175000017500000007057010667417100015442 0ustar cairocairo/* $Id: e2_tree.c 639 2007-09-05 03:10:56Z tpgww $ Copyright (C) 2004-2007 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "emelfm2.h" #include #include "e2_tree.h" /** @brief try to find tree iter in @a model, at or after @a iter, which contains string @a search This is a wrapper for the real search function. It performs several checks The scan traverses @a model checking each iter and any children, depth-first @param model treemodel to be interrogated @param column index of the column in @a model to scan @param search the string to be searched for @param iter pointer to treeiter for model interrogation (start and match) @param with_children TRUE if matched iter must have child(ren) @return TRUE if the string was found */ gboolean e2_tree_find_iter_from_str (GtkTreeModel *model, gint column, const gchar *search, GtkTreeIter *iter, gboolean with_children) { if (model == NULL) return FALSE; gint num_columns = gtk_tree_model_get_n_columns (model); if (column > (num_columns - 1)) { printd (WARN, "bad call of e2_tree_find_iter_from_str"); printd (WARN, "requesting column %d of %d columns", column + 1, num_columns); return FALSE; } if (gtk_tree_model_get_column_type (model, column) != G_TYPE_STRING) { printd (WARN, "bad call of e2_tree_find_iter_from_str"); printd (WARN, "column %d is not of type string", column + 1); return FALSE; } if (gtk_tree_model_get_iter_first (model, iter)) return e2_tree_find_iter_from_str_simple (model, column, search, iter, with_children); return FALSE; } /** @brief starting from @a iter, check all iters at the same level as @a iter in @a model, and all their descendants, to find the first iter which contains string @a search @a iter is set to matching iter if any, or is unchanged if no match is found @param model treemodel to be interrogated @param column index of the column in @a model to scan @param search the string to be searched for @param iter pointer to initial treeiter for model interrogation @param with_children TRUE if matched iter must have child(ren) @return TRUE if the string was found */ gboolean e2_tree_find_iter_from_str_simple (GtkTreeModel *model, gint column, const gchar *search, GtkTreeIter *iter, gboolean with_children) { GtkTreeIter backup; backup = *iter; do { GtkTreeIter iter2; gchar *value; gtk_tree_model_get (model, iter, column, &value, -1); if (value == NULL) continue; if (g_str_equal (value, search)) { g_free (value); if (with_children) { if (gtk_tree_model_iter_children (model, &iter2, iter)) return TRUE; else continue; } else return TRUE; } if (gtk_tree_model_iter_children (model, &iter2, iter)) { if (e2_tree_find_iter_from_str_simple (model, column, search, &iter2, with_children)) { *iter = iter2; g_free (value); return TRUE; } } g_free (value); } while (gtk_tree_model_iter_next (model, iter)); *iter = backup; return FALSE; } /** @brief starting from @a iter, check all iters at the same level as @a iter in @a model, to find the first iter which contains string @a search @a iter is unchanged if no match is found @param model treemodel to be interrogated @param column index of the column in @a model to scan @param search the string to be searched for @param iter pointer to initial treeiter for model interrogation, set to matching iter @return TRUE if the string was found */ gboolean e2_tree_find_iter_from_str_same (GtkTreeModel *model, gint column, const gchar *search, GtkTreeIter *iter) { GtkTreeIter backup; backup = *iter; do { gchar *value; gtk_tree_model_get (model, iter, column, &value, -1); if (value != NULL) { if (g_str_equal (value, search)) { g_free (value); return TRUE; } g_free (value); } } while (gtk_tree_model_iter_next (model, iter)); *iter = backup; return FALSE; } /** @brief find tree iter which matches the last "."-separated segment of string @a search @a search is formatted like level1[.level2.level3 ...] Each "." (if any) indicates a child of the previous segment If successful, @a iter is set to the matching iter in the model @param model treemodel to be interrogated @param column index of the column in @a model to scan @param search string describing the iter to be searched for @param iter pointer to treeiter for model interrogation and result @return TRUE if the string was found */ gboolean e2_tree_find_lowest_iter_from_str (GtkTreeModel *model, gint column, const gchar *search, GtkTreeIter *iter) { if (!gtk_tree_model_get_iter_first (model, iter)) return FALSE; gchar **split = g_strsplit (search, ".", -1); if (!e2_tree_find_iter_from_str_simple (model, column, split[0], iter, FALSE)) { g_strfreev (split); return FALSE; } //FIXME this should continue searching at iterative parent levels whenever // a match fails, in case there are multiple values gboolean retval = TRUE; GtkTreeIter child; gint i = 1; while (split[i] != NULL) { if (*split[i] == '\0') { retval = FALSE; //don't have empty items in the store break; } if (!gtk_tree_model_iter_children (model, &child, iter)) { retval = FALSE; break; //FIXME iterate } gboolean found = FALSE; gchar *value; *iter = child; do { gtk_tree_model_get (model, iter, column, &value, -1); if (g_str_equal (value, split[i])) { g_free (value); found = TRUE; break; } g_free (value); } while (gtk_tree_model_iter_next (model, iter)); if (!found) { retval = FALSE; break; //FIXME iterate } i++; } g_strfreev (split); return retval; } #ifdef E2_TREEINCREMENT /** @brief create treestore entry which matches @a name @a name is formatted like level1[.level2.level3 ...] Each "." (if any) indicates a child of the previous segment @a iter is set to the newly created iter in the model @param model treemodel to be interrogated @param column index of the column in @a model to scan @param name segmented name string desribing the iter to be created @param iter pointer to treeiter for model interrogation and result @return */ //this func could be amended a bit to do the job of // e2_tree_find_lowest_iter_from_str() too void e2_tree_create_lowest_iter_from_str (GtkTreeModel *model, gint column, gchar *name, GtkTreeIter *iter) { gchar **split = g_strsplit (name, ".", -1); gint i; GtkTreeIter child; if (gtk_tree_model_get_iter_first (model, iter) && e2_tree_find_iter_from_str_simple (model, column, split[0], iter, FALSE)) { //at least the first level exists //FIXME this should continue searching at iterative parent levels whenever // a match fails, in case there are multiple values i = 1; while (split[i] != NULL) { if (*split[i] == '\0') i++; //ignore it FIXME eliminate it from the array else if (gtk_tree_model_iter_children (model, &child, iter)) { gboolean found = FALSE; gchar *value; *iter = child; do { gtk_tree_model_get (model, iter, column, &value, -1); if (g_str_equal (value, split[i])) { g_free (value); found = TRUE; break; } g_free (value); } while (gtk_tree_model_iter_next (model, iter)); if (!found) break; //FIXME iterate i++; } else break; //we've run out of matching levels FIXME iterate } if (split[i] == NULL) return; //we found everything } else //we need to create all of the segments i = 0; //populate the new row(s) GtkTreeStore *store = GTK_TREE_STORE (model); while (split[i] != NULL) { if (i == 0) gtk_tree_store_append (store, &child, NULL); else gtk_tree_store_append (store, &child, iter); *iter = child; gtk_tree_store_set (store, iter, column, split[i], -1); i++; } g_strfreev (split); } #endif //def E2_TREEINCREMENT /** @brief get string in column @a columm in the last row in @a model @param model treemodel to be interrogated @param column index of the column in @a model from which to get the return value @return pointer to newly-allocated copy of the string from the model, or NULL if there's a problem */ /* UNUSED gchar *e2_tree_get_last_string (GtkTreeModel *model, gint column) { if (gtk_tree_model_get_column_type (model, column) != G_TYPE_STRING) return NULL; GtkTreeIter iter; / *FIXME: don't iterate through the whole model to get the last row GtkTreePath *path = gtk_tree_path_new_first (); while (gtk_tree_model_get_iter (model, &iter, path)) gtk_tree_path_next (path); gtk_tree_path_prev (path); gchar *value = NULL; if (gtk_tree_model_get_iter (model, &iter, path)) gtk_tree_model_get (model, &iter, column, &value, -1); gtk_tree_path_free (path); return value; * / GtkTreeIter *parent = NULL; //initially check root node gint children; while ((children = gtk_tree_model_iter_n_children (model, parent)) > 0) { // go to last child at this level gtk_tree_model_iter_nth_child (model, &iter, parent, children-1); parent = &iter; //after the root node, we use the actual } if (parent == NULL) return NULL; //nothing in the model gchar *value; gtk_tree_model_get (model, &iter, column, &value, -1); return value; } */ /** @brief determine the number of rows in treestore model @a model This is mainly for debugging @param model treemodel to be interrogated @return the no. of rows */ /*guint e2_tree_store_count (GtkTreeModel *model) { GtkTreeIter iter; if (!gtk_tree_model_get_iter_first (model, &iter)) return 0; //CHECKME is there a way to start at the real root of the tree ? guint count = 0; GtkTreePath *path = gtk_tree_path_new_first (); while (gtk_tree_model_get_iter (model, &iter, path)) { GNode *rootnode = iter.user_data; count += g_node_n_nodes (rootnode, G_TRAVERSE_ALL); gtk_tree_path_next (path); } gtk_tree_path_free (path); return count; } */ /** @brief create a tree row reference for @a iter in @a store @param store pointer to treestore @param iter pointer to treeiter to be referenced @return the reference */ GtkTreeRowReference *e2_tree_iter_to_ref (GtkTreeStore *store, GtkTreeIter *iter) { GtkTreePath *path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), iter); GtkTreeRowReference *ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (store), path); gtk_tree_path_free (path); return ref; } /** @brief set @a iter to match @a ref for @a store @param store pointer to treestore @param ref pointer to treerow reference @param iter pointer to treeiter to be set @return TRUE if @a iter is valid */ gboolean e2_tree_ref_to_iter (GtkTreeStore *store, GtkTreeRowReference *ref, GtkTreeIter *iter) { if (gtk_tree_row_reference_valid (ref)) { GtkTreePath *path = gtk_tree_row_reference_get_path (ref); gboolean retval = gtk_tree_model_get_iter (GTK_TREE_MODEL (store), iter, path); gtk_tree_path_free (path); return retval; } return FALSE; } /** @brief tree expand-all callback @param widget UNUSED the widget which activated the callback @param treeview the treeview to be collapsed @return TRUE (to prevent further handlers) */ gboolean e2_tree_expand_all_cb (GtkWidget *widget, GtkTreeView *treeview) { gtk_tree_view_expand_all (treeview); return TRUE; } /** @brief tree collapse-all callback @param widget UNUSED the widget which activated the callback @param treeview the treeview to be collapsed @return TRUE (to prevent further handlers) */ gboolean e2_tree_collapse_all_cb (GtkWidget *widget, GtkTreeView *treeview) { gtk_tree_view_collapse_all (treeview); return TRUE; } /** @brief find previous iter in a tree model This is because gtk doesn't have one. On entry, @a iter is assumed valid Sets @a iter to point to the node preceding it at the current level. @param model the tree model being interrogated @param iter pointer to reference iter @return TRUE if a prior node was found, and iter was set accordingly */ gboolean e2_tree_iter_previous (GtkTreeModel *model, GtkTreeIter *iter) { GtkTreePath *path = gtk_tree_model_get_path (model, iter); gboolean retval = gtk_tree_path_prev (path); if (retval) retval = gtk_tree_model_get_iter (model, iter, path); gtk_tree_path_free (path); return retval; } /** @brief add treerowreferences for row and its descendants to list A depth-first search scan creates references for the specified iter and all its descendants, appending each ref. to a specified glist. It is recursive, to handle descendants @param model the treemodel that is being processed @param iter _pointer_ to tree iter to be processed @param rowrefs **glist that holds the result @return */ static void _e2_tree_reference_treerow (GtkTreeModel *model, GtkTreeIter *iter, GList **rowrefs) { GtkTreePath *path = gtk_tree_model_get_path (model, iter); GtkTreeRowReference *ref; if ((ref = gtk_tree_row_reference_new (model, path)) != NULL) *rowrefs = g_list_append (*rowrefs, ref); gtk_tree_path_free (path); gint children; if ((children = gtk_tree_model_iter_n_children (model, iter)) > 0) { GtkTreeIter iter2; gint n; for (n=0; n 0) { GList *rowrefs = NULL; GtkTreeModel *model; GList *selpaths = gtk_tree_selection_get_selected_rows (sel, &model); GList *tmp; //identify a place to goto after the deletion //(current place, if we're at the start of the tree) GtkTreePath *newpath = gtk_tree_path_copy (selpaths->data); if (!gtk_tree_path_prev (newpath)) if (gtk_tree_path_get_depth (newpath) > 1) gtk_tree_path_up (newpath); GtkTreePath *path; GtkTreeIter iter; //create treerowrefs for all selected rows for (tmp = selpaths; tmp!=NULL; tmp=tmp->next) { if (gtk_tree_model_get_iter (model, &iter, (GtkTreePath *) tmp->data)) _e2_tree_reference_treerow (model, &iter, &rowrefs); } for (tmp = rowrefs; tmp!=NULL; tmp=tmp->next) { if ((path = gtk_tree_row_reference_get_path ((GtkTreeRowReference *) tmp->data)) != NULL) { if (gtk_tree_model_get_iter (model, &iter, path)) { //gtk_tree_selection_unselect_path (sel, path); //needed ? //this deletes all children too gtk_tree_store_remove (GTK_TREE_STORE (model), &iter); } } } //go to the new place if (!gtk_tree_model_get_iter (model, &iter, newpath) && gtk_tree_model_get_iter_first (model, &iter)) { gtk_tree_path_free (newpath); newpath = gtk_tree_model_get_path (model, &iter); } gtk_tree_view_set_cursor (treeview, newpath, gtk_tree_view_get_column (treeview, 0), FALSE); //cleanups g_list_foreach (rowrefs, (GFunc) gtk_tree_row_reference_free, NULL); g_list_free (rowrefs); g_list_foreach (selpaths, (GFunc) gtk_tree_path_free, NULL); g_list_free (selpaths); gtk_tree_path_free (newpath); } } /** @brief add treemodel row, and all its descendants, to list A recursive depth-first copy process puts the path and corresponding column values for @a iter into an array, then appends that array to the list @a rowscopy @param model the treemodel that is being processed @param iter pointer to tree iter for the row to be processed @param columncount no. of columns in @a model @param rowscopy store for pointer to list that holds the result @return */ static void _e2_tree_copy_branch (GtkTreeModel *model, GtkTreeIter *iter, gint columncount, GList **rowscopy) { GValueArray *row_array = g_value_array_new (columncount + 1); //add path of copied node to the start of the buffer entry, to support proper pasting GValue value = {0, }; // GValue *valptr = g_value_init (&value, G_TYPE_STRING); GValue *valptr = g_value_init (&value, G_TYPE_POINTER); GtkTreePath *path = gtk_tree_model_get_path (model, iter); // gchar *pathstring = gtk_tree_path_to_string (path); // g_value_set_static_string (valptr, pathstring); //no pathstring copy created, don't free it here g_value_set_pointer (valptr, path); g_value_array_append (row_array, valptr); g_value_unset (valptr); // gtk_tree_path_free (path); //add store column values to the buffer entry gint i; for (i = 0; i < columncount ; i++) { gtk_tree_model_get_value (model, iter, i, valptr); g_value_array_append (row_array, valptr); g_value_unset (valptr); } //add this row to the buffer list *rowscopy = g_list_append (*rowscopy, row_array); //recurse to handle any child(ren) gint children = gtk_tree_model_iter_n_children (model, iter); if (children > 0) { GtkTreeIter iter2; gint n; for (n=0; n 0) { GtkTreeModel *model; GList *base = gtk_tree_selection_get_selected_rows (sel, &model); GtkTreeIter iter; gint columns = gtk_tree_model_get_n_columns (model); GList *selpaths; //copy the selected rows and their descendants for (selpaths = base; selpaths != NULL; selpaths = selpaths->next) { if (gtk_tree_model_get_iter (model, &iter, (GtkTreePath *) selpaths->data)) _e2_tree_copy_branch (model, &iter, columns, &rowscopied); } g_list_foreach (base, (GFunc) gtk_tree_path_free, NULL); g_list_free (base); printd (DEBUG, "copied %d rows", g_list_length (rowscopied)); } return rowscopied; } /*static gboolean _e2_tree_path_compare (GtkTreePath *a, GtkTreePath *b) { gint path_equal = gtk_tree_path_compare (a, b); return !path_equal; } */ /** @brief paste tree rows into @a treeview Row(s) are inserted after the 1st selected row in @a treeview, as child(ren) if the path depth is appropriate It is assumed here that the buffer hash has pastable data @param rowscopied list of copied value-arrays @param treeview the treeview to be processed @return */ void e2_tree_paste (GList *rowscopied, GtkTreeView *treeview) { GtkTreeSelection *sel = gtk_tree_view_get_selection (treeview); if (gtk_tree_selection_count_selected_rows (sel) > 0) { //get source-view path for the 1st buffered item, the //root of the copied branch (or one of several roots) GValueArray *value_array = rowscopied->data; GValue *value = g_value_array_get_nth (value_array, 0); // gchar *pathstring = g_value_dup_string (value); // GtkTreePath *path = gtk_tree_path_new_from_string (pathstring); GtkTreePath *rootpath = (GtkTreePath *) g_value_get_pointer (value); gint copyrootdepth = gtk_tree_path_get_depth (rootpath); // gtk_tree_path_free (path); //store for references (hence iters) needed to preserve relations //among pasted rows GHashTable *ref_hash = NULL; GtkTreeRowReference *ref; GtkTreeModel *model; GList *selpaths = gtk_tree_selection_get_selected_rows (sel, &model); gint columns = gtk_tree_model_get_n_columns (model); GList *tmp; GtkTreeIter local1; GtkTreeIter local2; GtkTreeIter *iter = &local1; GtkTreeIter *parent = &local2; //find a selected item where it's valid to paste for (tmp = selpaths; tmp!=NULL; tmp=tmp->next) { if (gtk_tree_model_get_iter (model, iter, (GtkTreePath *) tmp->data)) { gint thisdepth = gtk_tree_path_get_depth (tmp->data); if (thisdepth == copyrootdepth) { //the selected row is a valid sibling of the buffered row(s) //paste the root of the copied branch printd (DEBUG, "pasting branch root, at depth %d", thisdepth); /* if (gtk_tree_model_iter_parent (model, parent, iter)) { //pasted row (iter) will be _pre_pended to parent's children gtk_tree_store_insert_after (GTK_TREE_STORE (model), iter, parent, NULL); } else //parent is the root node { */ //pasted row (iter) will be inserted after selected row gtk_tree_store_insert_after (GTK_TREE_STORE (model), parent, NULL, iter); iter = parent; // } break; } else if (thisdepth == copyrootdepth - 1) { //the selected item is a parent of the buffered row(s) //pasted row (iter) will be appended to parent's children printd (DEBUG, "pasting branch root, at depth %d", thisdepth); gtk_tree_store_insert_before (GTK_TREE_STORE (model), parent, iter, NULL); iter = parent; break; } } //keep looking ... } if (tmp != NULL) { //found a place where it's ok to paste //populate the newly-added "root" iter gint i; for (i = 0; i < columns; i++) { value = g_value_array_get_nth (value_array, i+1); gtk_tree_store_set_value (GTK_TREE_STORE (model), iter, i, value); } //remember how to find the parent of each pasted iter //the hash key (a path from the source treeview) is used //determine parent/child relations between pasted rows ref_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) gtk_tree_row_reference_free); // ref_hash = g_hash_table_new_full (g_direct_hash, // (GEqualFunc) _e2_tree_path_compare, // NULL, //we don't want to damage the buffered paths // (GDestroyNotify) gtk_tree_row_reference_free); GtkTreePath *path = gtk_tree_model_get_path (model, iter); ref = gtk_tree_row_reference_new (model, path); gtk_tree_path_free (path); // g_hash_table_insert (ref_hash, pathstring, ref); gchar *rootstring = gtk_tree_path_to_string (rootpath); g_hash_table_insert (ref_hash, rootstring, ref); //now the rest of the list, if any //there may be descendant(s) and/or sibling(s) of the initial item GtkTreePath *src_path; gchar *ppstring; GList *tmp2 = rowscopied; while ((tmp2=tmp2->next) != NULL) { value_array = tmp2->data; //get source-view path and find where it should sit in the new tree value = g_value_array_get_nth (value_array, 0); // pathstring = g_value_dup_string (value); src_path = (GtkTreePath *) g_value_get_pointer (value); path = gtk_tree_path_copy (src_path); //get former parent path string, by truncating the last segment // gchar *s = g_strrstr (pathstring, ":"); // if (s != NULL) // { // *s = '\0'; if (gtk_tree_path_up (path)) { //try to find an already-pasted parent iter ppstring = gtk_tree_path_to_string (path); if (ppstring != NULL) { //we're not at the root node now ref = g_hash_table_lookup (ref_hash, ppstring); gtk_tree_path_free (path); g_free (ppstring); } else ref = NULL; if (ref != NULL) { path = gtk_tree_row_reference_get_path (ref); gtk_tree_model_get_iter (model, parent, path); gtk_tree_path_free (path); //append row (iter) to parent's children gtk_tree_store_insert_before ( GTK_TREE_STORE (model), iter, parent, NULL); } else { //this is probably a sibling ref = g_hash_table_lookup (ref_hash, rootstring); path = gtk_tree_row_reference_get_path (ref); gtk_tree_model_get_iter (model, parent, path); gtk_tree_path_free (path); //append row (iter) to as a sibling gtk_tree_store_insert_after ( GTK_TREE_STORE (model), iter, NULL, parent); } } else { //CHECKME probably can't be at a root already printd (DEBUG, "root node problem"); continue; } //populate row for (i = 0; i < columns; i++) { value = g_value_array_get_nth (value_array, i+1); gtk_tree_store_set_value (GTK_TREE_STORE (model), iter, i, value); } // g_free (pathstring); //remember newly-added iter in case it's a parent path = gtk_tree_model_get_path (model, iter); ref = gtk_tree_row_reference_new (model, path); gtk_tree_path_free (path); ppstring = gtk_tree_path_to_string (src_path); // g_hash_table_insert (ref_hash, g_value_dup_string (value), // ref); g_hash_table_insert (ref_hash, ppstring, ref); } //no change to (single row) selection, as all new rows are after if (ref_hash != NULL) g_hash_table_destroy (ref_hash); } g_list_foreach (selpaths, (GFunc) gtk_tree_path_free, NULL); g_list_free (selpaths); } } #ifdef STORECOPY /** @brief helper function to recursively copy a treestore @param model treemodel for store being copied @param parent pointer to treeiter which has been confirmed to have child(ren) @param ncols no. of columns in @a model @param colnums array of column indices @param types array of GTypes corresponding to columns @param values array in which to store values being transferred @return */ static void _e2_tree_copy_descendants (GtkTreeModel *model, GtkTreeIter *parent, gint ncols, gint colnums[], GType types[], GValue values[]) { gint i; GtkTreeStore *newtstore = GTK_TREE_STORE (model); GtkTreeIter child; gtk_tree_model_iter_children (model, &child, parent); do { if (gtk_tree_model_iter_has_child (model, &child)) _e2_tree_copy_descendants (model, &child, ncols, colnums, types, values); //read iter into pointers array for (i = 0; i < ncols; i++) gtk_tree_model_get_value (model, &child, i, &values[i]); #ifdef USE_GTK2_10 gtk_tree_store_insert_with_valuesv (newtstore, &child, parent, -1, colnums, values, ncols); #else gtk_tree_store_append (newtstore, &child, parent); for (i = 0; i < ncols; i++) gtk_tree_store_set_value (newtstore, &child, i, &values[i]); #endif for (i = 0; i < ncols; i++) g_value_unset (&values[i]); } while (gtk_tree_model_iter_next (model, &child)); } /** @brief copy a liststore or treestore This is for when reffing is not enough e.g. need independent sorting in attached views. Pointers to data are simply copied, so if the "source" data will be cleared before fhe new store, then all such data must be replicated or otherwise preserved by code elsewhere which understands what that data really is. Officially, the new store will be unsorted, though in practice it will be the same as the old store. @param model treemodel of the store being copied @param treetype TRUE for treestore, FALSE for liststore @param newstore store for pointer to new store @a return */ void e2_tree_store_copy (GtkTreeModel *model, gboolean treetype, gpointer *newstore) { GtkTreeModel *newmodel; GtkTreeIter src; GtkTreeIter dest; gint i, ncols = gtk_tree_model_get_n_columns (model); gint colnums[ncols]; GType types[ncols]; GValue values[ncols]; for (i = 0; i < ncols; i++) { colnums[i] = i; types[i] = gtk_tree_model_get_column_type (model, i); memset (&values[i], 0, sizeof (GValue)); } if (treetype) { GtkTreeStore *newtstore; newtstore = gtk_tree_store_newv (ncols, types); if (gtk_tree_model_get_iter_first (model, &src)) { newmodel = GTK_TREE_MODEL (newtstore); do { //read iter into values array for (i = 0; i < ncols; i++) gtk_tree_model_get_value (model, &src, i, &values[i]); #ifdef USE_GTK2_10 gtk_tree_store_insert_with_valuesv (newtstore, &dest, NULL, -1, colnums, values, ncols); #else gtk_tree_store_append (newtstore, &dest, NULL); for (i = 0; i < ncols; i++) gtk_tree_store_set_value (newtstore, &dest, i, &values[i]); #endif for (i = 0; i < ncols; i++) g_value_unset (&values[i]); if (gtk_tree_model_iter_has_child (model, &src)) _e2_tree_copy_descendants (model, NULL, ncols, colnums, types, values); } while (gtk_tree_model_iter_next (model, &src)); } *newstore = newtstore; } else { GtkListStore *newlstore; newlstore = gtk_list_store_newv (ncols, types); if (gtk_tree_model_get_iter_first (model, &src)) { newmodel = GTK_TREE_MODEL (newlstore); do { for (i = 0; i < ncols; i++) gtk_tree_model_get_value (model, &src, i, &values[i]); gtk_list_store_insert_with_valuesv (newlstore, &dest, -1, colnums, values, ncols); for (i = 0; i < ncols; i++) g_value_unset (&values[i]); } while (gtk_tree_model_iter_next (model, &src)); } *newstore = newlstore; } } #endif emelfm2-0.4.1/src/utils/e2_hook.h0000600000175000017500000000237311010340377015436 0ustar cairocairo/* $Id: e2_hook.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelfm2. emelfm2 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 3, or (at your option) any later version. emelfm2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_HOOK_H__ #define __E2_HOOK_H__ #include "e2_utils.h" void e2_hook_unattach_cb (E2_Duo *duo); GHook *e2_hook_register (GHookList *hook_list, gpointer func, gpointer data); gboolean e2_hook_unregister (GHookList *hook_list, gpointer func, gpointer data, gboolean use_data); #define e2_hook_unregister_simple(hook_list, func) \ e2_hook_unregister (hook_list, func, NULL, FALSE) void e2_hook_list_run (GHookList *hook_list, gpointer data); #endif //ndef __E2_HOOK_H__ emelfm2-0.4.1/src/utils/e2_utils.c0000600000175000017500000023676511015105075015646 0ustar cairocairo/* $Id: e2_utils.c 896 2008-05-21 20:46:53Z tpgww $ Copyright (C) 2003-2008 tooar Portions copyright (C) 1999 Michael Clark. This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "emelfm2.h" #include #include #include "e2_utils.h" #include "e2_dialog.h" #include "e2_filelist.h" typedef struct _E2_WildData { gchar **path_elements; //array of strings, from g_strsplit of utf8 path gchar **path_matches; //array of localised strings, with matching path segments, if any guint curr_depth; //count of element currently being matched guint last_depth; //count of the last path element guint first_wild_depth; //first element that has wildcard char(s) guint last_wild_depth; //last element to be matched (maybe 1 more than last depth) struct stat *statptr; //access to statbuf for shared use } E2_WildData; //static gchar *last_trashpath; /*max msec between clicks when checking for a doubleclick maybe replaced by gtk's value in e2_utils_update_gtk_settings()*/ guint click_interval = E2_CLICKINTERVAL; /** @brief setup struct with 3 NULL pointers @return the struct */ /*E2_Trio *e2_utils_trio_new (void) { E2_Trio *t = ALLOCATE0 (E2_Trio); CHECKALLOCATEDWARN (t, ); return t; } */ /** @brief setup struct with 6 NULL pointers @return the struct */ E2_Sextet *e2_utils_sextet_new (void) { E2_Sextet *s = ALLOCATE0 (E2_Sextet); CHECKALLOCATEDWARN (s, ); return s; } /** @brief setup struct with 9 NULL pointers @return the struct */ E2_Nontet *e2_utils_nontet_new (void) { E2_Nontet *n = ALLOCATE0 (E2_Nontet); CHECKALLOCATEDWARN (n, ); return n; } /** @brief de-allocate struct with 3 pointers @param t pointer to the struct to clear @return */ /*void e2_utils_trio_destroy (E2_Trio *t) { if (t != NULL) DEALLOCATE (E2_Trio, t); } */ /** @brief de-allocate struct with 6 pointers @param s pointer to the struct to clear @return */ void e2_utils_sextet_destroy (E2_Sextet *s) { if (s != NULL) DEALLOCATE (E2_Sextet, s); } /** @brief de-allocate struct with 9 pointers @param n pointer to the struct to clear @return */ void e2_utils_nontet_destroy (E2_Nontet *n) { if (n != NULL) DEALLOCATE (E2_Nontet, n); } /** @brief display error message about insufficient memory Expects BGL to be closed @return */ void e2_utils_show_memory_message (void) { e2_output_print_error (_("Not enough memory! Things may not work as expected"), FALSE); } /** @brief handle fatal lack of memory Expects BGL to be open @return never does */ void e2_utils_memory_error (void) { g_critical ("Not enough memory"); gdk_threads_enter (); //called func expects BGL closed e2_main_closedown (TRUE, TRUE, TRUE); //no user-cancellation } /** @brief show user help at the heading @a title Expects BGL to be on/closed @param title heading string (NO []) to search for in the main help doc, undefined encoding @return */ void e2_utils_show_help (gchar *title) { gchar *helpfile = e2_option_str_get ("usage-help-doc"); if (helpfile != NULL) { gchar *local = F_FILENAME_TO_LOCALE (helpfile); #ifdef E2_VFS VPATH ddata = { local, NULL }; //local helpfiles only if (!e2_fs_access (&ddata, R_OK E2_ERR_NONE())) #else if (!e2_fs_access (local, R_OK E2_ERR_NONE())) #endif { //setup help doc name and heading name, as parameters for command gchar *filepath = g_strdup_printf ("%s [%s]", local, title); #ifdef E2_VFS ddata.localpath = filepath; //possibly mixed encoding e2_view_dialog_create_immediate (&ddata); #else e2_view_dialog_create_immediate (filepath); #endif g_free (filepath); } else helpfile = NULL; F_FREE (local); } if (helpfile == NULL) { gchar *msg = g_strdup_printf (_("Cannot read USAGE help document")); e2_output_print_error (msg, TRUE); } } /** @brief convert color data to string form @param color Gdk color data struct @return newly-allocated string with the color data */ gchar *e2_utils_color2str (GdkColor *color) { return g_strdup_printf ("#%.2X%.2X%.2X", color->red/256, color->green/256, color->blue/256); } /** @brief replace all occurrences of @a old in @a str with @a new This uses strsplit and strjoinv. No utf8 consideration. @param str the 'haystack' string in which to search for @a old @param old the 'needle' string which is to be repaced @param new the replacement string for @a old @return newly allocated string with the repacements made */ gchar *e2_utils_str_replace (const gchar *str, const gchar *old, const gchar *new) { gchar **split = g_strsplit (str, old, -1); gchar *join = g_strjoinv (new, split); g_strfreev (split); return join; } /** @brief put spaces between all characters in string @a str @param str utf-8 string which is to be expanded @return newly-allocated expanded string */ gchar *e2_utils_str_stretch (gchar *str) { if (!g_utf8_validate (str, -1, NULL)) return (g_strdup (str)); gchar *retval; glong len; gunichar *conv = g_utf8_to_ucs4_fast (str, -1, &len); //the last ' ' is replaced by \0 so don't need memory for that #ifdef __USE_GNU gunichar stretch [len * 2]; #else gunichar *stretch = NEW (gunichar, len * 2); if (stretch != NULL) #endif { glong i, j; for (i = 0, j = 0; i < len; i++, j++) { stretch[j] = conv[i]; stretch[++j] = ' '; } stretch[--j] = '\0'; retval = g_ucs4_to_utf8 (stretch, -1, NULL, NULL, NULL); } #ifndef __USE_GNU else retval = g_strdup (str); g_free (stretch); #endif g_free (conv); return retval; } /** @brief ellipsize the start of string @a string if it's longer than desired "..." is substituted for the relevant part of @a string, if that's longer than @a length Note - for middle-position dots, @a limit >= 8 If @a limit is < 3, the returned string may be 1 or 2 chars longer than @a length @param string utf string which may need to be shortened @param limit the threshold length (chars, not bytes) for ellipsizing @param position enumerator for dots at start, middle or end @return newly-allocated string, shortened or the same as @a string */ gchar *e2_utils_str_shorten (gchar *string, gint limit, E2_DotMode position) { g_strchug (string); //just in case string is a path with trailing space glong len = g_utf8_strlen (string, -1); if (len > limit) { gchar *p, *s, *copy; glong trim = (limit > 2) ? len - limit + 3 : len - 3 ; if (trim > 0) { switch (position) { case E2_DOTS_START: p = g_utf8_offset_to_pointer (string, trim); return g_strconcat ("...", p, NULL); case E2_DOTS_END: s = g_strdup (string); p = g_utf8_offset_to_pointer (s, len - trim); *p = '\0'; p = g_strconcat (s, "...", NULL); g_free (s); return p; default: p = s = string; glong gapoffset, chroffset = 0; //-8 allows for 3 dots and 5 trailing chars //FIXME assumes limit >= 8 while (s != NULL) { s = e2_utils_find_whitespace (s); if (s != NULL) { gapoffset = g_utf8_pointer_to_offset (string, s); if (gapoffset >= limit-8) break; p = s; //p = start of last usable whitespace chroffset = gapoffset; s = e2_utils_pass_whitespace (s); } } if (p == string) { //no suitable gap found, break toward the middle chroffset = limit/2 - 2; p = g_utf8_offset_to_pointer (string, chroffset); } copy = g_strndup (string, (p-string)); s = g_utf8_offset_to_pointer (string, chroffset+len-limit+3); p = g_strconcat (copy, "...", s, NULL); g_free (copy); return p; } } } return g_strdup (string); } /** @brief convert from a character-offset to a byte-offset within @a utf8_string @param utf8_string the string in which we want the offset @param charoffset 0-based character-position in @a utf8_string, may be < 0 to indicate position relative to end @return the byte-offset, or -1 upon error */ gint e2_utils_get_byte_position (const gchar *utf8_string, gint charoffset) { if (charoffset == 0) return 0; //glib doesn't do any bounds check, so we do that here gint len = g_utf8_strlen (utf8_string, -1); if (charoffset > 0) { if (charoffset >= len) return -1; } else #ifdef USE_GLIB2_10 { if (-charoffset >= len) return -1; } #else return -1; #endif const gchar *s = g_utf8_offset_to_pointer (utf8_string, charoffset); return (s - utf8_string); } /** @brief if necessary, replace "non-UNIX" line-separtors in @a text with LF In-place conversion is performed. Assumes CR always occurs before LF, and no embedded 0 in @a text @param text string to be processed @return value indicating the type of separator, CR or LF or CR+LF */ gint e2_utils_LF_line_ends (gchar *text) { gint retval; gchar *i = text, *j; //quick check for whether conversion is needed while (*i != '\0') { if (*i == LF || *i == CR) break; i++; } if (*i == CR && *(i+1) == LF) retval = CR + LF; else if (*i != '\0') retval = *i; else retval = LF; //default to UNIX-style if we find no break //replace or remove CR's if (*i == CR) { for (i = text, j = text; *j != '\0'; i++, j++) { if (*i == CR) { *j = LF; if (*(i + 1) == LF) i++; } else *j = *i; } } return retval; } /** @brief Convert "non-UNIX" line-separtors in @a text to @a separator @param text string to be processed, must be freeable @param linecount no. of extra spaces needed when reverting to CR+LF @param separator desired line-break = CR or LF or CR+LF @return pointer to adjusted text, maybe different from the original */ gchar *e2_utils_revert_line_ends (gchar *text, guint linecount, gint separator) { gchar c; gchar *s, *d; switch (separator) { case CR: //same size of text, simple replacement s = text; while ((c = *s) != '\0') { if (c == LF) *s = CR; s++; } break; case CR+LF: { gint textlen = strlen (text) + 1; //+1 = \0 gchar *newtext = (gchar *) g_try_realloc (text, (textlen + linecount)); if (newtext != NULL) { text = newtext; s = text+linecount; d = text; memmove (s, d, textlen); // *d = *s; //ensure no bad finish at 1st byte for (; *d != '\0'; s++, d++) { if (*s == LF) { *d = CR; d++; } *d = *s; } } } break; //case LF: default: //nothing to do break; } return text; } /** @brief get name of current locale's default character encoding, with fallback @param encoding store for pointer to name string @return */ void e2_utils_get_charset (const gchar **encoding) { g_get_charset (encoding); if (*encoding == NULL) //might as well use fs fallback encoding as any other ! *encoding = e2_cl_options.fallback_encoding; } /** @brief convert utf8 string @a string to lower case @param string the string which is to be processed @return newly allocated lc string: must be freed */ gchar *e2_utils_str_to_lower (gchar *string) { return g_utf8_strdown (string, -1); } /** @brief append @a string to the full path of the dir shown in @a view @param view pointer to view data struct @param string the string which will be appended, localised or utf-8, in accord with @a localised @param localised TRUE to localise dir path before joining @return newly allocated joined localised or utf8 string, or NULL on error */ gchar *e2_utils_dircat (ViewInfo *view, const gchar *string, gboolean localised) { gchar *dir, *joined; dir = (localised) ? F_FILENAME_TO_LOCALE (view->dir) : view->dir; joined = e2_utils_strcat (dir, string); if (localised) F_FREE (dir); return joined; } /** @brief join strings @a string1 and @a string2 This is for localised strings, for which g_strconcat() (maybe?) shouldn't be used @param string1 the string which will start the returned string @param string2 the string which will be appended @return newly allocated joined string, or NULL */ gchar *e2_utils_strcat (const gchar *string1, const gchar *string2) { gint len1 = strlen (string1); gint len2 = strlen (string2) + sizeof (gchar); //include the trailing 0 gchar *result = g_try_malloc (len1 + len2); if (result != NULL) { #ifdef __USE_GNU gchar *next = mempcpy (result, string1, len1); next = mempcpy (next, string2, len2); #else memcpy (result, string1, len1); memcpy (result + len1, string2, len2); #endif } return result; } /** @brief Like strsplit() but retains the delimiter as part of the string @param string string to be split @param delimiter separator string @param max_tokens maximum number of separated parts, or -1 for no limit @return NULL-terminated array of strings, or NULL if error occurred */ gchar **e2_utils_str_breakup (const gchar *string, const gchar *delimiter, gint max_tokens) { GSList *string_list = NULL, *slist; gchar *s, *casefold, *new_string; gchar **str_array; guint i, n = 1; g_return_val_if_fail (string != NULL, NULL); g_return_val_if_fail (delimiter != NULL, NULL); if (max_tokens < 1) max_tokens = G_MAXINT; s = strstr (string, delimiter); if (s != NULL) { guint delimiter_len = strlen (delimiter); do { guint len = s - string + delimiter_len; new_string = NEW (gchar, len + 1); if (new_string == NULL && string_list != NULL) { g_slist_foreach (string_list, (GFunc) g_free, NULL); g_slist_free (string_list); } g_return_val_if_fail (new_string != NULL, NULL); g_strlcpy (new_string, string, len); new_string[len] = 0; casefold = g_utf8_casefold (new_string, -1); g_free (new_string); new_string = g_utf8_normalize (casefold, -1, G_NORMALIZE_ALL); g_free (casefold); string_list = g_slist_prepend (string_list, new_string); n++; string = s + delimiter_len; s = strstr (string, delimiter); } while (--max_tokens && s); } if (*string) { n++; casefold = g_utf8_casefold (string, -1); new_string = g_utf8_normalize (casefold, -1, G_NORMALIZE_ALL); g_free (casefold); string_list = g_slist_prepend (string_list, new_string); } str_array = NEW (gchar*, n); if (str_array == NULL && string_list != NULL) { g_slist_foreach (string_list, (GFunc) g_free, NULL); g_slist_free (string_list); } g_return_val_if_fail (str_array != NULL, NULL); i = n - 1; str_array[i--] = NULL; for (slist = string_list; slist; slist = slist->next) str_array[i--] = slist->data; g_slist_free (string_list); return str_array; } /** @brief find the next (if any) "normal" occurrence of @a c in @a string A normal occurrence is outside quotes, and not escaped. This is intended for simple chars like ' ' or ';', and so uses ascii scanning @param string the string which is to be processed @param c the character to scan for @return pointer to the char, or NULL if not found */ gchar *e2_utils_bare_strchr (gchar *string, gchar c) { gchar *p = string; gint cnt1 = 0; //counter for ' chars gint cnt2 = 0; //counter for " chars while (*p != '\0') { if (*p == '\'') { if (p == string || *(p-1) != '\\') cnt1++; } else if (*p == '"') { if (p == string || *(p-1) != '\\') cnt2++; } else if (*p == c) { //check if separator seems to be outside parentheses if (cnt1 % 2 == 0 && cnt2 % 2 == 0) return p; //found one } p++; } return NULL; } /** @brief find the first (if any) space or tab from the start of @a string This uses ascii scanning @param string the string which is to be processed @return pointer to the whitespace char, or NULL if not found */ gchar *e2_utils_find_whitespace (gchar *string) { register gchar c; gchar *s = string; while ((c = *s) != '\0') { if (c == ' ' || c == '\t') return s; s++; } return NULL; } /** @brief find the first (if any) non-space-or-tab from the start of @a string This uses ascii scanning @param string the string which is to be processed @return pointer to the non-whitespace char, or NULL if not found */ gchar *e2_utils_pass_whitespace (gchar *string) { register gchar c; gchar *s = string; while ((c = *s) != '\0') { if (!(c == ' ' || c == '\t')) return s; s++; } return NULL; } /** @brief get a copy of @a utf8string without quotes if any @param utf8string the string which is to be processed Quotes may be ' or " @return newly-allocated string, or NULL if string is empty */ gchar *e2_utils_unquote_string (const gchar *utf8string) { gboolean quoted; gunichar uc1; gchar *p; p = e2_utils_pass_whitespace ((gchar *)utf8string); if (p == NULL) return NULL; uc1 = g_utf8_get_char (p); quoted = ((gchar)uc1 == '"' || (gchar)uc1 == '\''); if (quoted) { gint len; gunichar uc2; gchar *q; len = g_utf8_strlen (p, -1); q = g_utf8_offset_to_pointer (p, len - 1); getlast: uc2 = g_utf8_get_char (q); if (uc1 == uc2) { p = g_utf8_next_char (p); p = g_strndup (p, q - p); } else if ((gchar)uc2 == ' ' || (gchar)uc2 == '\t') { q = g_utf8_prev_char (q); goto getlast; } else p = g_strdup (p); } else p = g_strdup (p); return p; } /** @brief find the first (if any) argument-substring in @a string If @a quoted is TRUE, but @a string has no "", then quotes are added only if @a string contains whitespace. AND then, the whole of @a string is treated as one, as we don't know which gap is intended as the break. This uses ascii scanning to find quotes and whitespace. @param string the string which is to be processed @param quoted TRUE to return the argument with surrounding "" @return newly-allocated string, or NULL if no argument found */ gchar *e2_utils_get_first_arg (gchar *string, gboolean quoted) { gchar *s1, *s2; gchar c; gboolean quotednow; s1 = e2_utils_pass_whitespace (string); if (s1 == NULL) return NULL; quotednow = (*s1 == '"'); // || *s1 == '\''); if (quoted) { if (quotednow) { s2 = s1+1; while ((s2 = strchr (s2, '"')) != NULL) { if (*(s2-1) != '\\') break; } if (s2 != NULL) { s2++; c = *s2; *s2 = '\0'; s1 = g_strdup (s1); *s2 = c; } else s1 = g_strconcat (s1, "\"", NULL); } else { //add quotes if appropriate s2 = e2_utils_find_whitespace (s1); s1 = (s2 != NULL) ? //quote whole string as we don't know which gap is relevant g_strconcat ("\"", s1, "\"", NULL) : g_strdup (s1); //no need to quote if no gap exists } } else //want arg without quotes { if (quotednow) { //strip quotes s1++; s2 = s1; while ((s2 = strchr (s2, '"')) != NULL) { if (*(s2-1) != '\\') break; } if (s2 != NULL) { *s2 = '\0'; s1 = g_strdup (s1); *s2 = '"'; } else s1 = g_strconcat (s1, "\"", NULL); } else { //get arg without quotes s2 = e2_utils_find_whitespace (s1); if (s2 != NULL) { c = *s2; *s2 = '\0'; } s1 = g_strdup (s1); if (s2 != NULL) *s2 = c; } } return s1; } /*gchar *get_key_name (gint keyval) { return gdk_keyval_name (gdk_keyval_to_lower (keyval)); } */ static gchar *prefix = NULL; static gulong savecount = 0; //so initial used value defaults to 1 /** @brief Replace macro(s) in string @a text with appropriate value(s) Supported macro codes are %c, [%]%d, [%]%D, [%]%f, [%]%F, [%]%p, [%]%P(= F), * %t, %{...} %$...$ These 'letters' are hard-coded CHECKME should the letters be translatable, non-ascii ? (%c macro is also processed in rename plugin, so that counter(s) can be recorded for incrementation purposes) Expects BGL on/closed @param text utf-8 (or ascii) string, possibly with macros to be expanded @param for_each utf8 string with name(s) or path(s) of item(s) to substitute for any "%f" or "%p", or NULL to use selected items @return newly allocated string, or NULL if failure, or 1 if prompt macro cancelled */ gchar *e2_utils_expand_macros (gchar *text, gchar *for_each) { GString *command_string = g_string_new (""); gchar *s, *free_this, *command_copy, *utf; FileInfo *info; #ifdef E2_REFRESH_DEBUG printd (DEBUG, "disable refresh, expand macros"); #endif command_copy = g_strdup (text); free_this = s = command_copy; while ((s = strchr (command_copy, '%')) != NULL) //if always ascii %, don't need g_utf8_strchr() { *s = '\0'; s++; //NCHR(s); g_string_append (command_string, command_copy); gboolean with_quotes = TRUE; command_copy = s+1; //NCHR(command_copy); if (*s == '%') { s++; //NCHR(s); command_copy++; //NCHR(command_copy); with_quotes = FALSE; } switch (*s) { case 'f': case 'p': { if (for_each != NULL) //use specified item name { gchar *name, *p; //the supplied name may or may not have a path name = g_path_get_basename (for_each); if (g_str_equal (name, for_each)) //no path in for_each p = (*s == 'f') ? for_each : e2_utils_dircat (curr_view, for_each, FALSE); else //path in for_each p = (*s == 'f') ? name : for_each; if (with_quotes) g_string_append_printf (command_string, "\"%s\"", p); else command_string = g_string_append (command_string, p); if (p != for_each && p != name) g_free (p); g_free (name); } else //use selected items { GList *base, *tmp; e2_filelist_disable_refresh (); //prevent any change to the selected items ? base = tmp = e2_fileview_get_selected_local (curr_view); #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, expand macros"); #endif e2_filelist_enable_refresh(); if (tmp == NULL) { e2_output_print_error (_("No item selected"), FALSE); //CHECKME continue parsing instead of aborting g_free (free_this); g_string_free (command_string, TRUE); return NULL; } else { for (; tmp != NULL; tmp = tmp->next) { info = tmp->data; utf = F_FILENAME_FROM_LOCALE (info->filename); if (with_quotes) command_string = g_string_append_c (command_string, '"'); if (*s == 'f') g_string_append_printf (command_string, "%s%s", (prefix == NULL) ? "" : prefix, utf); else g_string_append_printf (command_string, "%s%s%s", (prefix == NULL) ? "" : prefix, curr_view->dir, utf); if (with_quotes) command_string = g_string_append_c (command_string, '"'); if (tmp->next != NULL) command_string = g_string_append_c (command_string, ' '); F_FREE (utf); } } g_list_free (base); } } break; case 'F': case 'P': { GList *base, *tmp; e2_filelist_disable_refresh (); base = tmp = e2_fileview_get_selected_local (other_view); #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, expand macros 2"); #endif e2_filelist_enable_refresh(); if (tmp == NULL) { e2_output_print_error (_("No item selected in other pane"), FALSE); //FIXME continue parsing instead of aborting g_free (free_this); g_string_free (command_string, TRUE); return NULL; } else { for (; tmp != NULL; tmp = tmp->next) { info = tmp->data; utf = F_FILENAME_FROM_LOCALE (info->filename); if (with_quotes) { g_string_append_printf (command_string, "\"%s%s%s\"%s", (prefix == NULL) ? "" : prefix, other_view->dir, utf, tmp->next != NULL ? " " : ""); } else { g_string_append_printf (command_string, "%s%s%s%s", (prefix == NULL) ? "" : prefix, other_view->dir, utf, tmp->next != NULL ? " " : ""); } F_FREE (utf); } } g_list_free (base); } break; case 'd': case 'D': { gchar *s1, *s2; s1 = (*s == 'd') ? curr_view->dir : other_view->dir; s1 = g_strdup (s1); s2 = s1 + strlen (s1) - 1; if (*s2 == G_DIR_SEPARATOR) *s2 = '\0'; s2 = (with_quotes) ? "\"%s\"" : "%s" ; g_string_append_printf (command_string, s2, s1); g_free (s1); } break; case 'c': { gchar numfmt[20]; gulong count, width; //parse count and width s++; count = strtoul (s, &command_copy, 10); if (command_copy == s) //no number provided count = ++savecount; //use stored value else { savecount = count; s = command_copy; } if (*s == ',') //no whitespace check { s++; width = strtoul (s, &command_copy, 10); if (command_copy == s) width = 1; //no number provided else s = command_copy; } else width = 1; numfmt[0] = '%'; //create count string using value and width if (width > 1) g_snprintf (numfmt+1, sizeof(numfmt)-1, "0%uu", (guint) width); else g_strlcpy (numfmt+1, "u", sizeof(numfmt)-1); g_string_append_printf (command_string, numfmt, count); command_copy = s; } break; case 't': { gchar *tmp = e2_utils_get_temp_path (NULL); g_string_append (command_string, tmp); //no quoting g_free (tmp); } break; case '{': if ((s = strchr (command_copy, '}')) == NULL) //always ascii }, don't need g_utf8_strchr() { e2_output_print_error (_("No matching '}' found in action text"), FALSE); g_free (free_this); g_string_free (command_string, TRUE); return NULL; } else { *s = '\0'; //end of bracketed text gchar *user_input, *cend, *sep, *cleaned; DialogButtons result; //tag PASSWORDINPUT gboolean hidden = FALSE; gboolean has_history = FALSE; GList *thishistory = NULL; sep = command_copy; //FIXME a better syntax (but | separator makes command look like a pipe) while ((sep = strchr (sep, '@')) != NULL) { if (*(sep-1) == '\\' || *(sep+1) == '@' || *(sep-1) == '@') sep++; else break; } if (sep != NULL) { while (command_copy < sep) { if (*command_copy == '(') { command_copy++; *sep = '\0'; if ((cend = strchr (command_copy, ')')) != NULL) { *cend = '\0'; has_history = TRUE; break; } } /*tag PASSWORDINPUT else if (*command_copy == '*') { hidden = TRUE; break; } */ command_copy++; //keep looking } } if (//!hidden && !has_history) cleaned = e2_utils_str_replace (command_copy, "\\@", "@"); else cleaned = e2_utils_str_replace (sep+1, "\\@", "@"); /*tag PASSWORDINPUT if (hidden) result = e2_dialog_password_input (NULL, cleaned, &user_input); else */ if (has_history) { e2_cache_list_register (command_copy, &thishistory); result = e2_dialog_combo_input (NULL, cleaned, NULL, 0, &thishistory, &user_input); e2_cache_unregister (command_copy); //backup the history list e2_list_free_with_data (&thishistory); } else //default result = e2_dialog_line_input (NULL, cleaned, "", 0, FALSE, &user_input); g_free (cleaned); command_copy = s+1; if (result == OK) { //a blank entry will not return OK //re-enter to expand %f etc in input gchar *expinput = e2_utils_expand_macros (user_input, for_each); if (expinput > (gchar *)1) //no check for 1 (no nested inputs) { g_string_append (command_string, expinput); g_free (expinput); } g_free (user_input); } else { g_free (free_this); g_string_free (command_string, TRUE); return GINT_TO_POINTER (1); //1 is cancel signal } } break; case '$': if ((s = strchr (command_copy, '$')) == NULL) //if always ascii }, don't need g_utf8_strchr() { e2_output_print_error (_("No matching '$' found in action text"), FALSE); g_free (free_this); g_string_free (command_string, TRUE); return NULL; } else if (s > command_copy) //ignore $$ { prefix = command_copy; //store prefix for use in rest of expansion *s = '\0'; gboolean freepfx; if (strchr (prefix, '%') != NULL) { prefix = e2_utils_expand_macros (prefix, NULL); freepfx = TRUE; } else freepfx = FALSE; command_copy = s+1; //re-enter to expand %f etc in rest of string gchar *expinput = e2_utils_expand_macros (command_copy, for_each); if (freepfx) g_free (prefix); prefix = NULL; if (expinput > (gchar *)1) { g_string_append (command_string, expinput); s = command_string->str; g_free (expinput); g_free (free_this); g_string_free (command_string, FALSE); return s; } } break; default: g_string_append_c (command_string, '%'); g_string_append_c (command_string, *s); break; } } g_string_append (command_string, command_copy); g_free (free_this); #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, expand macros7"); #endif s = command_string->str; g_string_free (command_string, FALSE); return s; } /** @brief replace all instances of macros [%]%f and [%]%p in @a string, with @a eachitem This differs from e2_utils_expand_macros(), due to no distinction between %f and %p, and a slightly different approach to quoting @param text string to be processed @param eachitem replacement string @return newly-allocted replacement string, or NULL if no replacement done */ gchar *e2_utils_replace_name (const gchar *text, const gchar *eachitem) { gchar *retval, *withquotes, *freeme; withquotes = (gchar *) eachitem + strlen (eachitem) - 1; gboolean quoted = ((*eachitem == '"' && *withquotes == '"') || (*eachitem == '\'' && *withquotes == '\'')); withquotes = (quoted) ? (gchar *)eachitem : g_strconcat ("\"", eachitem, "\"", NULL); retval = g_strdup (text); if (strstr (retval, "%%p") != NULL) { freeme = retval; retval = e2_utils_str_replace (retval, "%%p", eachitem); g_free (freeme); } if (strstr (retval, "%p") != NULL) { freeme = retval; retval = e2_utils_str_replace (retval, "%p", withquotes); g_free (freeme); } if (strstr (retval, "%%f") != NULL) { freeme = retval; retval = e2_utils_str_replace (retval, "%%f", eachitem); g_free (freeme); } if (strstr (retval, "%f") != NULL) { freeme = retval; retval = e2_utils_str_replace (retval, "%f", withquotes); g_free (freeme); } if (withquotes != eachitem) g_free (withquotes); if (g_str_equal (text, retval)) { g_free (retval); return NULL; } return retval; } /** @brief replace any instance(s) of macros [%]%f and [%]%p in @a string, with @a eachitem Any [%]%p will prevail over any [%]%f in @a text @param text utf8 string to be processed @param path utf8 path, or NULL to use active pane dir @param array array of selected items (localised) @param single TRUE to use only the first item of @a array @return newly-allocted replacement string, or NULL if no replacement done */ gchar *e2_utils_replace_name2 (gchar *text, gchar *path, GPtrArray *array, gboolean single) { gboolean quoted, withpath; gchar *retval, *usepath, *utf; guint count; GString *joined; if (array->len == 0) return NULL; if (strstr (text, "%%p") != NULL) { quoted = FALSE; withpath = TRUE; } else if (strstr (text, "%p") != NULL) { quoted = TRUE; withpath = TRUE; } else if (strstr (text, "%%f") != NULL) { quoted = FALSE; withpath = FALSE; } else if (strstr (text, "%f") != NULL) { quoted = TRUE; withpath = FALSE; } else return NULL; //get path - from supplied parameter, or active pane if (withpath) { if (path != NULL) usepath = g_strdup (path); else usepath = g_strdup (curr_view->dir); gchar *s = usepath + strlen (usepath) - 1; if (*s == G_DIR_SEPARATOR) *s = '\0'; } else usepath = NULL; //warning prevention joined = g_string_sized_new (256); E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) array->pdata; for (count=0; count < array->len; count++, iterator++) { if (quoted) joined = g_string_append_c (joined, '"'); if (withpath) { joined = g_string_append (joined, usepath); joined = g_string_append_c (joined, G_DIR_SEPARATOR); } utf = F_FILENAME_FROM_LOCALE ((*iterator)->filename); joined = g_string_append (joined, utf); F_FREE (utf); if (quoted) joined = g_string_append_c (joined, '"'); joined = g_string_append_c (joined, ' '); if (single && (count == 0)) break; } joined = g_string_truncate (joined, joined->len - 1); //clear last ' ' if (withpath) g_free (usepath); retval = e2_utils_replace_name (text, joined->str); g_string_free (joined, TRUE); return retval; } /** @brief construct a temporary local itemname by adding a suffix to @a localpath @param localpath path of item to be processed, localised string @return newly-allocated, localised, path string comprising the temp name */ gchar *e2_utils_get_tempname (const gchar *localpath) { #ifdef E2_VFS VPATH ddata; ddata.spacedata = NULL; #endif gchar *temppath; guint i = 0; while (TRUE) { temppath = g_strdup_printf ("%s.tmp~%d", localpath, i); //no translation or utf-8 conversion needed if (i == 0) { //first attempt has no "~N" suffix gchar *s = strrchr (temppath, '~'); *s = '\0'; } E2_ERR_DECLARE #ifdef E2_VFS ddata.localpath = temppath; if (e2_fs_access2 (&ddata E2_ERR_PTR()) && E2_ERR_IS (ENOENT)) #else if (e2_fs_access2 (temppath E2_ERR_PTR()) && E2_ERR_IS (ENOENT)) #endif { E2_ERR_CLEAR break; } E2_ERR_CLEAR g_free (temppath); i++; } return temppath; } /** @brief check whether the dir shown in pane related to @a rt is accessible This expects BGL closed @param rt pointer to file-pane data struct @return TRUE if rt->path was opened, FALSE if an alternative was opened */ gboolean e2_utils_goto_accessible_path (E2_PaneRuntime *rt) { printd (DEBUG, "check that %s is accessible", rt->path); #ifdef E2_VFSTMP //FIXME path for non-native dirs #else gchar *checkpath = g_strdup (rt->path); //don't clobber the rt->path string #endif #ifdef E2_VFSTMP //FIXME path check for non-mounted dirs not relevant ? //what about incomplete mount of fuse-dir #endif gboolean retval = e2_fs_get_valid_path (&checkpath, TRUE E2_ERR_NONE()); if (!retval) { gchar *msg = g_strdup_printf (_("Cannot access %s, going to %s instead"), #ifdef E2_VFSTMP //FIXME path for non-mounted dirs #else rt->path, checkpath); #endif e2_output_print_error (msg, TRUE); } e2_pane_change_dir (rt, checkpath); g_free (checkpath); return retval; } /** @brief truncate @a path at the right-most separator, if any Multiple adjacent separators are treated as one. @param path path string to be processed @param ignore_trailer TRUE to strip any trailing separator before checking @return TRUE if parent path was created */ gboolean e2_utils_get_parent_path (gchar *path, gboolean ignore_trailer) { gchar *s; gint len = strlen (path); if (ignore_trailer) { s = path + len - sizeof (gchar); if (s > path && *s == G_DIR_SEPARATOR) { *s = '\0'; len -= sizeof (gchar); } } #ifdef E2_VFSTMP //FIXME handle going past namespace root #endif if (len == sizeof (gchar)) return FALSE; //path this short may be root or not, but can't have a parent s = strrchr (path, G_DIR_SEPARATOR); if (s == NULL) return FALSE; if (s > path) { while (*(--s) == G_DIR_SEPARATOR && s > path) {} } *(s + sizeof (gchar)) = '\0'; return TRUE; } /** @brief ensure path string @a path has appropriate separators and embedded relativities Removes superfluous separators, and "./", "../" from inside @a path, but doesn't change any leading instances of "../". Appends separator if not one there already. Unless @a path is just "/", the returned string will be newly allocated, and the old one will have been freed, so the returned string may be assigned to the supplied path argument. @param path absolute or relative path string to be checked (UTF-8), must be freeable @return pointer to cleaned absolute path string (UTF-8), may be same as @a path */ gchar *e2_utils_path_clean (gchar *path) { g_strchug (path); //trailing whitespace may be deliberate if (path[0] == G_DIR_SEPARATOR && e2_utils_pass_whitespace (path + sizeof (gchar)) == NULL) return path; //path is not just the file system root //(what a clunker this is, with utf parsing !!) FIXME gunichar c; gchar *pn, *po, *pp, *s; gint path_len = strlen (path); gchar clean[path_len + 4]; //space for an extra trailer if needed po = pp = path; pn = clean; //remove any superfluous separator from within it while ((pp = strchr (pp, G_DIR_SEPARATOR)) != NULL ) //if always ascii /, don't need g_utf8_strchr() { //check for a repeated separator, or 0 pp++; //NCHR(pp); c = g_utf8_get_char (pp); if (c == '\0') break; if (c != G_DIR_SEPARATOR) { NCHR (pp); continue; } //found at least 2 separators, //copy old to clean, with the first separator while (po < pp) *(pn++) = *(po++); //skip any more separators while ((c = g_utf8_get_char (pp)) == G_DIR_SEPARATOR) NCHR (pp); po = pp; //point to the next start for copying } //copy rest, to the terminator while (*po != '\0') *(pn++) = *(po++); //check for trailing / PCHR (pn); c = g_utf8_get_char (pn); NCHR (pn); if (c != G_DIR_SEPARATOR) { //we need to add a trailer //g_strlcpy (pn, G_DIR_SEPARATOR_S, path_len-pn+4); //since separator is ascii, just shove it into place *pn = G_DIR_SEPARATOR; NCHR (pn); } *(pn) = '\0'; //remove any superfluous . from within it pp = clean; while ((pp = strchr (pp, '.')) != NULL) //always ascii '.', don't need g_utf8_strchr() { s = pp + sizeof (gchar); //s = pp; NCHR (s); if (*s == G_DIR_SEPARATOR && (pp == clean || *(pp - sizeof (gchar)) == G_DIR_SEPARATOR)) { //eliminate "./" from path pn = pp; po = s + sizeof (gchar); while (*po != '\0') *(pn++) = *(po++); *(pn) = '\0'; } else if (*s == '.' && (*(s + sizeof (gchar)) == G_DIR_SEPARATOR || *(s+1) == '\0') && (pp == clean || *(pp - sizeof (gchar)) == G_DIR_SEPARATOR)) { //backup or ignore "../" in path *pp = '\0'; if (e2_utils_get_parent_path (clean, TRUE)) { if (*(s + sizeof (gchar)) == G_DIR_SEPARATOR) { pp = pn = clean + strlen (clean); if (pp == clean || *(pp - sizeof (gchar)) == G_DIR_SEPARATOR) po = s + 2 * sizeof (gchar); //don't want duplicate separator else po = s + sizeof (gchar); while (*po != '\0') *(pn++) = *(po++); *(pn) = '\0'; } else //".." is at end of path string break; //finished now } else //no parent { if (*(s + sizeof (gchar)) == G_DIR_SEPARATOR) { pn = pp; if (pp == clean || *(pp - sizeof (gchar)) == G_DIR_SEPARATOR) po = s + 2 * sizeof (gchar); //don't want duplicate separator else po = s + sizeof (gchar); while (*po != '\0') *(pn++) = *(po++); *(pn) = '\0'; } else //".." is at end of path string { if (pp == clean) *pp = '.'; //reinstate ".." is the whole path break; } } } else pp++; } g_free (path); //cleanup the one supplied return (strdup (clean)); //send back a copy } /** @brief translate relative path Creates an absolute path string from a relative one, or if the supplied path is already absolute, just makes sure it is 'clean'. Works with UTF and with non-UTF path strings. @param current_dir current-path string, UTF-8 @param new_dir absolute or relative new-path string, UTF-8 * @return newly-allocated, cleaned, absolute path with trailing separator */ gchar *e2_utils_translate_relative_path (gchar *current_dir, gchar *new_dir) { gchar *s = (g_path_is_absolute (new_dir)) ? g_strdup (new_dir): //copy so that e2_utils_path_clean() can free it g_strconcat (current_dir, G_DIR_SEPARATOR_S, new_dir, NULL); return (e2_utils_path_clean (s)); } /** @brief create relative path of @a src relative to @a dest If @a src is not an absolute path, it is returned unchanged This only works for ascii single-byte separator-characters in filepaths @param src string containing the path to be relativised @param dest string containing the reference path @return newly-allocated path string */ gchar *e2_utils_create_relative_path (const gchar *src, const gchar *dest) { if (*src != G_DIR_SEPARATOR) //not an absolute path, we don't want, or don't know how, to relativise return g_strdup (src); const gchar *sp = src, *dp = dest, *tsp = NULL; GString *rel = g_string_sized_new (PATH_MAX); // strip common path while (*sp && (*sp == *dp)) { if (*sp == G_DIR_SEPARATOR) { NCHR(sp); tsp = sp; // save as latest short path } else NCHR(sp); NCHR(dp); } // insert non-common parent dirs (if any) as uplinks while (*dp) { if (*dp == G_DIR_SEPARATOR) g_string_append (rel, ".."G_DIR_SEPARATOR_S); //.. applies in all languages ? NCHR(dp); } // if not an updir ref. make it relative to current // if (rel->len == 0) // g_string_append (rel, "."G_DIR_SEPARATOR_S); // and add short src path if (tsp != NULL) g_string_append (rel, tsp); return (g_string_free (rel, FALSE)); } /** @brief get unique temp dir name @param id localised string to embed in returned basename, or NULL @return newly-allocated path, utf8 string, no trailing "/" */ gchar *e2_utils_get_temp_path (const gchar *id) { gchar *tmp = (id == NULL) ? "":(gchar *)id; const gchar *systmp = g_get_tmp_dir (); if (g_str_has_prefix (systmp, g_get_home_dir ())) tmp = g_strdup_printf ("%s"G_DIR_SEPARATOR_S"%s%s", systmp, BINNAME, tmp); else //in shared space, make user-identifiable temp dir tmp = g_strdup_printf ("%s"G_DIR_SEPARATOR_S"%d-%s%s", systmp, getuid (), BINNAME, tmp); //systmp, BINNAME and uid no. are all localised gchar *retval = e2_utils_get_tempname (tmp); g_free (tmp); tmp = retval; retval = D_FILENAME_FROM_LOCALE (tmp); g_free (tmp); return retval; } /** @brief get path of relevant trash directory Returned string should NOT be freed elsewhere @return pointer to utf8 pathstring (with trailing /), or NULL if there is none */ const gchar *e2_utils_get_trash_path (void) { /* #ifdef E2_VFSTMP //FIXME dir when not mounted local #else if (g_str_has_suffix (curr_view->dir, "Trash"G_DIR_SEPARATOR_S)) //already at a trash place, just use that return curr_view->dir; #endif //check if there is a local trash directory //needs separator at end, for comparisons when simply open a trash dir #ifdef E2_VFSTMP //FIXME dir when not mounted local #else gchar *trash = g_build_filename (curr_view->dir, ".Trash"G_DIR_SEPARATOR_S, NULL); #endif gchar *local = F_FILENAME_TO_LOCALE (trash); if (!e2_fs_is_dir3 (local E2_ERR_PTR())) { //if not, use the default, set at session start g_free (trash); if (app.trashpath != NULL) trash = g_strdup (app.trashpath); else { e2_output_print_error(_("No trash directory"), FALSE); return NULL; } } F_FREE (local); //remember, so it can be cleaned later, as a convenience if (last_trashpath != NULL) g_free (last_trashpath); last_trashpath = trash; return trash; //pointer to last-used topdir trash, freed here for convenience static gchar *last_trashpath; static gchar *suffix; struct stat sb; if (suffix == NULL) { gint myuid = getuid (); suffix = g_strdup_printf ("-%d", myuid); } gchar *tlocal; #ifdef E2_VFSTMP //FIXME dir when not mounted local #else gchar *local = D_FILENAME_TO_LOCALE (curr_view->dir); #endif if (e2_fs_stat (local, &sb E2_ERR_NONE())) { FIXME handle error; } dev_t curr_dev = sb.st_dev; if (e2_cl_options.trash_dir != NULL) { tlocal = F_FILENAME_TO_LOCALE (e2_cl_options.trash_dir); if (e2_fs_stat (tlocal, &sb E2_ERR_NONE())) { //FIXME handle error } dev_t t_dev = sb.st_dev; F_FREE (tlocal); if (curr_dev == t_dev) { //CWD is on same device as default trash g_free (local); return (e2_cl_options.trash_dir); } } //find the top dir of the device where CWD is gchar *s; while ((s = strrchr (local, G_DIR_SEPARATOR)) != NULL) { *s = '\0'; if (e2_fs_stat (local, &sb E2_ERR_NONE())) { FIXME handle error; } if (sb.st_dev != curr_dev) { *s = G_DIR_SEPARATOR; break; } } if (s == NULL) { //all CDW is on same device *local = G_DIR_SEPARATOR; //work from the root dir *(local+1) = '\0'; } //try to access (but NOT create) a valid trash dir there s = e2_utils_strcat (".Trash", suffix); tlocal = g_build_filename (local, s, "files", NULL); //no translation g_free (s); if (e2_fs_access3 (tlocal, W_OK | X_OK)) { g_free (tlocal); tlocal = g_build_filename (local, ".Trash", "files", NULL); if (e2_fs_access3 (tlocal, W_OK | X_OK)) { // if (last_trashpath != NULL) // { // g_free (last_trashpath); // last_trashpath = NULL; // } g_free (local); g_free (tlocal); //no deal, just use the default return (e2_cl_options.trash_dir); } } s = e2_utils_strcat (tlocal, G_DIR_SEPARATOR_S); if (last_trashpath != NULL) g_free (last_trashpath); last_trashpath = D_FILENAME_FROM_LOCALE (s); g_free (local); g_free (tlocal); g_free (s); return last_trashpath; */ return e2_cl_options.trash_dir; } /** @brief get path of directory nominated to contain emelfm2 custom icons No checking of the directory's accessibilty is done @param withtrailer TRUE if the returned string needs to have a trailing path-separator @return newly-allocated, localised, absolute path string */ gchar *e2_utils_get_icons_path (gboolean withtrailer) { const gchar *path; gchar *localpath, *freeme; if (e2_option_bool_get ("use-icon-dir")) { path = e2_option_str_get ("icon-dir"); localpath = D_FILENAME_TO_LOCALE (path); if (!g_path_is_absolute (localpath)) { freeme = localpath; localpath = g_build_filename (ICON_DIR, localpath, NULL); g_free (freeme); } /* //default icons in config dir if that's usable if (!e2_fs_is_dir3 (localpath E2_ERR_NONE())) openpath = g_strconcat ( #ifdef E2_FAM e2_cl_options.config_dir, #else g_get_home_dir (), G_DIR_SEPARATOR_S, #endif _("icons"), NULL); */ if (g_str_has_suffix (localpath, G_DIR_SEPARATOR_S)) { if (!withtrailer) *(localpath + strlen (localpath) - sizeof(gchar)) = '\0'; } else if (withtrailer) { freeme = localpath; localpath = e2_utils_strcat (freeme, G_DIR_SEPARATOR_S); g_free (freeme); } } else { path = (withtrailer) ? ICON_DIR G_DIR_SEPARATOR_S : ICON_DIR; //localised localpath = g_strdup (path); } return localpath; } #ifdef E2_IMAGECACHE gint iconsizes [GTK_ICON_SIZE_DIALOG+1]; /** @brief determine all icon pixel-sizes assumes width = height @return */ void e2_utils_init_icon_sizes (void) { GtkIconSize size; /* 0 invalid/theme, 1 menu, 2 toolbar small, 3 toolbar large, 4 button, 5 dnd, 6 dialog GTK_ICON_SIZE_INVALID, GTK_ICON_SIZE_MENU, GTK_ICON_SIZE_SMALL_TOOLBAR, GTK_ICON_SIZE_LARGE_TOOLBAR, GTK_ICON_SIZE_BUTTON, GTK_ICON_SIZE_DND, GTK_ICON_SIZE_DIALOG */ gint wide, high; gint defsizes[] = {18, 16, 18, 24, 20, 32, 48}; //default sizes GtkSettings* defs = gtk_settings_get_default (); gchar *sizestr, *this; g_object_get (G_OBJECT (defs), "gtk-icon-sizes", &sizestr, NULL); //sizestr = NULL or "gtk-menu=16,16;gtk-button=20,20 ..." /* if (sizestr == NULL) { //this is one way to work around strange behaviour of gtk_icon_size_get_name (); gchar *names[] = { "", "menu", "toolbar-small", "toolbar-large", "button", "dnd", "dialog" }; //CHECKME } */ //get the pixel-size corresponding to each GtkIconSize for (size = GTK_ICON_SIZE_MENU; size <= GTK_ICON_SIZE_DIALOG; size++) { if (sizestr == NULL) { if (gtk_icon_size_lookup_for_settings (defs, size, &wide, &high)) iconsizes [size] = high; else iconsizes [size] = defsizes [size]; } else { gchar *s, *p; // this = g_strconcat ("gtk-", names [size], "=", NULL); this = (gchar *) gtk_icon_size_get_name (size); if (this == NULL) { //could be a malformed theme-descriptor string //and for gtk 2.6.7/8 at least - strange behaviour - GTK_ICON_SIZE_MENU //doesn't match here, but does match if we call the fn later! if (gtk_icon_size_lookup_for_settings (defs, size, &wide, &high)) iconsizes [size] = high; else iconsizes [size] = defsizes [size]; } else { s = strstr (sizestr, this); if (s != NULL) { //the theme sets this icon size s += strlen (this) + 1; //skip the descriptor and "=" p = strchr (s, ','); //always ascii s = g_strndup (s, (p-s)); iconsizes [size] = atoi (s); g_free (s); } else { if (gtk_icon_size_lookup_for_settings (defs, size, &wide, &high)) iconsizes [size] = high; else iconsizes [size] = defsizes [size]; } // g_free (this); } } } //now set the default /* gtk 2.6.8 spits warning about gtk-toolbar-icon-size property not existing, tho' API doco says it does ! size = GTK_ICON_SIZE_INVALID; g_object_get (G_OBJECT (defs), "gtk-toolbar-icon-size", &size, NULL); if (size == GTK_ICON_SIZE_INVALID) size = GTK_ICON_SIZE_LARGE_TOOLBAR; iconsizes [0] = iconsizes [size]; */ iconsizes [0] = iconsizes [GTK_ICON_SIZE_LARGE_TOOLBAR]; } /** @brief get icon pixel size for icon size enumerator @a size @param size GtkIconSize value @return icon pixelsize */ gint e2_utils_get_icon_size (GtkIconSize size) { return iconsizes [size]; } /** @brief get icon size enumerator for icon which is closest-below @a psize @param psize icon pixel size @return iconsize */ GtkIconSize e2_utils_get_best_icon_size (gint psize) { GtkIconSize i, isz = GTK_ICON_SIZE_MENU; gint psz = 0; for (i = GTK_ICON_SIZE_MENU; i <= GTK_ICON_SIZE_DIALOG; i++) { if (iconsizes [i] > psz && iconsizes [i] <= psize) { isz = i; psz = iconsizes [i]; } } return isz; } #endif //def E2_IMAGECACHE /** @brief determine whether @a name represents a gtk stock icon @param name string with icon name, a file path/name or "gtk-*" @return TRUE if @a name represents a stock-icon */ gboolean e2_utils_check_stock_icon (const gchar *name) { /* gtk_stock_lookup is badly unreliable !! GtkStockItem item; return (gtk_stock_lookup (rt->icon, &item)) */ //quick n dirty check // return (g_str_has_prefix (name, "gtk-")); gboolean retval = FALSE; if (g_str_has_prefix (name, "gtk-")) { GSList *tmp, *ids = gtk_stock_list_ids (); for (tmp = ids; tmp != NULL; tmp = g_slist_next (tmp)) { if (!retval && g_str_equal ((gchar *)tmp->data, name)) retval = TRUE; g_free (tmp->data); } g_slist_free (ids); } return retval; } /** @brief get output-pane font name @return string with the name */ const gchar *e2_utils_get_output_font (void) { gchar *fntname; if (e2_option_bool_get ("custom-output-font")) { fntname = e2_option_str_get ("output-font"); if (*fntname == '\0') fntname = NULL; } else fntname = NULL; if (fntname == NULL) { GtkSettings* defs = gtk_settings_get_default (); g_object_get (G_OBJECT (defs), "gtk-font-name", &fntname, NULL); if (fntname == NULL) //CHECKME needed ? { printd (WARN, "No default font detected"); fntname = "Sans 10"; } } return fntname; } /** @brief update gtk properties like menu delays update gtk internal properties. these are currently only menu popup and popdown delays. this function is usually called after configuration changes to update the gtk properties. @return */ void e2_utils_update_gtk_settings (void) { GtkSettings *defs = gtk_settings_get_default (); gint delay_up = e2_option_int_get ("submenu-up-delay"); gint delay_down = e2_option_int_get ("submenu-down-delay"); //be on the safe side if (delay_up < 0) delay_up = 0; if (delay_down < 0) delay_down = 0; gtk_settings_set_long_property (defs, "gtk-menu-popup-delay", delay_up, "XProperty"); gtk_settings_set_long_property (defs, "gtk-menu-popdown-delay", delay_down, "XProperty"); gtk_settings_set_long_property (defs, "gtk-menubar-popup-delay", delay_up, "XProperty"); //set doubleclick interval threshold to gtk's value g_object_get (G_OBJECT (defs), "gtk-double-click-time", &click_interval, NULL); if (click_interval < E2_CLICKINTERVAL) { click_interval = E2_CLICKINTERVAL; g_object_set (G_OBJECT (defs), "gtk-double-click-time", E2_CLICKINTERVAL, NULL); } // gtk_rc_parse_string ("style \"e2_default\" { GtkComboBoxEntry::appears-as-list = 1 } class \"*\" style \"e2_default\""); } /** @brief helper function to find matches for last or only path segment Arrives here with BGL off Note: to eliminate BGL-racing, no UI-change from here @param parent absolute path of dir being processed, localised string with trailer @param itemname name of discovered item, localised string @param found pointer to store for list of data items for @a parent @param pair pointer to struct with parameters for use here @return TRUE to signal the read has not been aborted */ static gboolean _e2_utils_drcb_match_wild_last (VPATH *parent, const gchar *itemname, GList **found, E2_Duo *pair) { if (!g_str_equal (itemname, "..")) { GPatternSpec *pattern = (GPatternSpec *)pair->b; gchar *utfname = F_FILENAME_FROM_LOCALE (itemname); if (g_pattern_match_string (pattern, utfname)) { gchar *escname; gboolean all = GPOINTER_TO_INT (pair->a); if (all) { escname = e2_utf8_escape (utfname, ' '); *found = g_list_append (*found, escname); } else { gchar *freeme = e2_utils_strcat (VPSTR (parent), itemname); #ifdef E2_VFS VPATH ddata = { freeme, parent->spacedata }; if (e2_fs_is_dir3 (&ddata E2_ERR_NONE())) #else if (e2_fs_is_dir3 (freeme E2_ERR_NONE())) #endif { escname = e2_utf8_escape (utfname, ' '); *found = g_list_append (*found, escname); } g_free (freeme); } } F_FREE (utfname); } return TRUE; } /** @brief find matches for last or only path segment of @a arg, if that contains * and/or ? Searching is in $PATH directories @a arg may include an absolute or relative path. Relative path is assumed relative to curr_view->dir If @a arg does include any path, that is expected to be all non-wild. "." and ".." items are excluded Expects BGL to be on/closed on arrival here @param arg path or itemname which may have wildcard(s) in its only or last segment, utf-8 string @param all TRUE to match any type of item, FALSE to match dirs only @return list of utf8 names which match, or 0x1 if if no match was found, or NULL if there is no wildcard in @a arg or an error occurred */ static GList *_e2_utils_match_wild_last (gchar *arg, gboolean all) { gboolean freepath, freename; E2_FSType dirtype; gchar *name, *path, *localpath, *freeme; #ifdef E2_VFSTMP //FIXME relevant path is ? dirtype = ?; #else path = g_path_get_dirname (arg); dirtype = FS_LOCAL; //FIXME #endif if (g_str_equal (path, ".")) { //no path in arg g_free (path); if (strchr (arg, '*') == NULL && strchr (arg, '?') == NULL) return NULL; if (g_str_has_prefix (arg, "$")) //some shell or language variables can be ignored return GINT_TO_POINTER (1); #ifdef E2_VFSTMP //FIXME dir when not mounted local #else path = curr_view->dir; #endif name = arg; freepath = freename = FALSE; } else { //path was found name = g_path_get_basename (arg); if (g_str_equal (name, ".") || (strchr (name, '*') == NULL && strchr (name, '?') == NULL)) { g_free (path); g_free (name); return NULL; } //ensure trailing separator freeme = path; path = g_strconcat (path, G_DIR_SEPARATOR_S, NULL); g_free (freeme); if (!g_path_is_absolute (path)) { freeme = path; #ifdef E2_VFSTMP //FIXME dir when not mounted local #else path = e2_utils_dircat (curr_view, path, FALSE); #endif g_free (freeme); } freepath = freename = TRUE; } gdk_threads_leave (); //needed for dirforeach localpath = F_FILENAME_TO_LOCALE (path); GPatternSpec *pattern = g_pattern_spec_new (name); E2_Duo pair = { GINT_TO_POINTER (all), pattern }; #ifdef E2_VFS #ifdef E2_VFSTMP FIXME placedata #endif VPATH ddata = { localpath, NULL }; GList *matches = (GList *)e2_fs_dir_foreach (&ddata, #else GList *matches = (GList *)e2_fs_dir_foreach (localpath, #endif E2_DIRWATCH_NO, //$PATH is local places only _e2_utils_drcb_match_wild_last, &pair, NULL E2_ERR_NONE()); //conform results to API if (E2DREAD_FAILED (matches)) matches = NULL; else if (matches == NULL) matches = (GList *) 1; g_pattern_spec_free (pattern); if (freepath) g_free (path); F_FREE (localpath); if (freename) g_free (name); gdk_threads_enter (); return matches; } /** @brief helper function to match path which has wildcard(s) in its element(s) Arrives with BGL open/off Note: to eliminate BGL-racing, no UI-change from here Reentrant use of _e2_utils_match_wild_path() assmumes BGL is still off here @param parent UNUSED absolute path of dir being processed, localised string with or without trailer @param itemname name of discovered item, localised string @param found pointer to store for list of data items for @a parent @param pattern the matcher for desired items @return TRUE to signal the read has not been aborted */ static gboolean _e2_utils_drcb_match_wild_path (VPATH *parent, const gchar *itemname, GList **found, GPatternSpec *pattern) { if (!g_str_equal (itemname, "..")) //"." entries are filtered at source { gchar *utfname = F_FILENAME_FROM_LOCALE (itemname); if (g_pattern_match_string (pattern, utfname)) *found = g_list_append (*found, g_strdup (itemname)); F_FREE (utfname); } return TRUE; } /** @brief recursively find a path that is a descendant of @a parent and otherwise matches wildcard data in @a wdata This scans @a parent depth-first, until either a complete match is found, or no match is possible, in which case it backs up a level and tries the next match at that level, and so on. Matching path segments are stored in @a wdata Expects BGL to be closed on arrival here @param parent absolute path, no wildcard char(s) or redundant separators or trailer, localised string @param wdata pointer to struct with parameters for use here @return TRUE if a matching path was found */ static gboolean _e2_utils_match_wild_path (gchar *parent, E2_WildData *wdata) { gboolean retval; guint i, here; gchar *format; GString *checker; GPatternSpec *pattern; GList *matches, *member; #ifdef E2_VFS VPATH ddata; #ifdef E2_VFSTMP FIXME valid spacedata for parent #endif ddata.spacedata = NULL; #endif gdk_threads_leave (); //needed for dirforeach here = wdata->curr_depth; checker = g_string_sized_new (256); checker = g_string_append (checker, parent); pattern = g_pattern_spec_new (wdata->path_elements[here]); #ifdef E2_VFS ddata.localpath = checker->str; matches = (GList *)e2_fs_dir_foreach (&ddata, #else matches = (GList *)e2_fs_dir_foreach (checker->str, #endif E2_DIRWATCH_NO, //$PATH is local places only _e2_utils_drcb_match_wild_path, pattern, NULL E2_ERR_NONE()); retval = !(matches == NULL || E2DREAD_FAILED (matches)); if (retval) { if (here < wdata->last_wild_depth) { i = checker->len; //for truncating format = (i == sizeof (gchar)) ? "%s" : G_DIR_SEPARATOR_S"%s"; wdata->curr_depth++; for (member = matches; member != NULL; member = member->next) { g_string_append_printf (checker, format, (gchar *)member->data); #ifdef E2_VFS ddata.localpath = checker->str; retval = !e2_fs_stat (&ddata, wdata->statptr E2_ERR_NONE()) #else retval = !e2_fs_stat (checker->str, wdata->statptr E2_ERR_NONE()) #endif && S_ISDIR (wdata->statptr->st_mode) && _e2_utils_match_wild_path (checker->str, wdata); //recurse into dir at next level if (retval) break; //everything afterward is matched //not a dir or not completely matched downwards //prepare to try again at this level checker = g_string_truncate (checker, i); } if (!retval) //preserve success pointer if all was successful wdata->curr_depth--; } else member = matches; if (retval) wdata->path_matches [here] = g_strdup ((gchar *)member->data); e2_list_free_with_data (&matches); } g_pattern_spec_free (pattern); g_string_free (checker, TRUE); gdk_threads_enter (); return retval; } /** @brief replace any wildcard character(s) '*' and '?' in @a string If it's quoted (" or ') nothing is done. If it includes path separator(s), any wildcard in path element(s) before the last one is replaced by the fist-found valid match, or if there's no match, the expansion fails. Any wildcard in the last (or only) path segment is expanded to all matches, with prepended path if appropriate Downstream code assumes BGL is closed, here @param string a whitespace-separated "segment" of a commandline utf-8 string maybe with wildcard(s) to replace @return newly-allocated string, copy of @a string or string with wildcards replaced */ static gchar *_e2_utils_match_wild_segment (gchar *string) { if (strchr (string, '*') == NULL && strchr (string, '?') == NULL) return (g_strdup (string)); gint len = strlen (string); if (*string == '"' && *(string + len - 1) == '"') return (g_strdup (string)); if (*string == '\'' && *(string + len - 1) == '\'') return (g_strdup (string)); guint last_wild_depth, last_depth; gchar *freeme, *path, *parent_path, *expanded; GList *matches; //trailing separator confuses things if (g_str_has_suffix (string, G_DIR_SEPARATOR_S)) *(string + len - 1) = '\0'; #ifdef E2_VFSTMP //FIXME path when not mounted local #endif path = g_path_get_dirname (string); //no trailing separator if (!g_str_equal (path, ".")) { //the string has a path gboolean abs = g_path_is_absolute (path); if (strchr (path, '*') != NULL || strchr (path, '?') != NULL) { guint i, count; gchar *s; E2_WildData wdata = {0}; g_free (path); //make sure processed string is absolute #ifdef E2_VFSTMP //FIXME dir when not mounted local #else path = (abs) ? g_strdup (string): //use whole string in case basename is also wild e2_utils_dircat (curr_view, string, FALSE); #endif //making GPatternSpec's requires utf-8 patterns wdata.path_elements = g_strsplit (path, G_DIR_SEPARATOR_S, -1); //for the matched elements ... setup for strfreev later count = g_strv_length (wdata.path_elements); wdata.path_matches = (gchar **) #ifdef USE_GLIB2_8 g_try_malloc0 ((count + 1) * sizeof (gchar *)); #else calloc (count + 1, sizeof (gchar *)); #endif if (wdata.path_matches == NULL) { g_free (path); g_strfreev (wdata.path_elements); return (g_strdup (string)); } //find the first reportable segment of the path /* if (!abs) { #ifdef E2_VFSTMP //FIXME dir when not mounted local #else s = curr_view->dir; #endif while (*s != '\0') { if (*s == G_DIR_SEPARATOR) wdata.first_depth++; s++; } } */ last_wild_depth = 0; //warning prevention //find the bounds of the scan, skipping empty fictitious segment //from before leading separator for (i = 1; i < count; i++) { s = wdata.path_elements[i]; if (strchr (s, '*') != NULL || strchr (s, '*') != NULL) { if (wdata.first_wild_depth == 0) wdata.first_wild_depth = i;//highest level that has a wildcard char last_wild_depth = i;//lowest level that has a wildcard char } } last_depth = i - 1; //level of the last path segment //extra match needed when path extends after the last wild segment if (last_wild_depth < last_depth) last_wild_depth++; /* else { //special treatment if 2nd last segment of string is wild and last is explicit gchar *name = g_path_get_basename (string); if (strchr (name, '*') != NULL || strchr (name, '*') != NULL) { //FIXME allocate a replacement array with 1 more pointer local = F_FILENAME_TO_LOCALE (name); wdata.path_segments[wdata.last_depth] = local; wdata.path_segments[wdata.last_depth+1] = NULL; F_FREE (local); wdata.last_wild_depth++; } g_free (name); } */ if (last_wild_depth == count - 1) //the last item also is wild last_depth--; //later, we don't want to use the last matched element wdata.curr_depth = wdata.first_wild_depth; wdata.last_wild_depth = last_wild_depth; wdata.last_depth = last_depth; //construct non-wild localised parent path, to start the scan (no trailing separator) //and fill corresponding elements of the matches array wdata.path_matches [0] = g_strdup (""); if (wdata.first_wild_depth == 1) s = g_strdup (G_DIR_SEPARATOR_S); else { s = g_strdup (""); for (i = 1; i < wdata.first_wild_depth; i++) { wdata.path_matches[i] = D_FILENAME_TO_LOCALE (wdata.path_elements[i]); freeme = s; s = g_strconcat (freeme, G_DIR_SEPARATOR_S, wdata.path_matches[i], NULL); g_free (freeme); } } //scan the filesystem to match the full path struct stat statbuf; wdata.statptr = &statbuf; if (_e2_utils_match_wild_path (s, &wdata)) { g_free (s); //create "real" parent path string from the matched segments, with trailer #ifdef E2_VFSTMP //CHECKME dir when not mounted local #endif //FIXME strip any prepended curr_view->dir //FIXME ensure this array is fully populated s = g_strdup (""); // for (i = wdata.first_depth; i <= last_depth; i++) for (i = 0; i <= last_depth; i++) { freeme = s; s = g_strconcat (freeme, wdata.path_matches[i], G_DIR_SEPARATOR_S, NULL); g_free (freeme); } parent_path = D_FILENAME_FROM_LOCALE (s); } else //no match found for the path { if (!abs) { //we want the supplied path only g_free (path); path = g_path_get_dirname (string); } freeme = g_path_get_dirname (string); parent_path = g_strconcat (freeme, G_DIR_SEPARATOR_S, NULL); g_free (freeme); } g_free (s); g_strfreev (wdata.path_elements); g_strfreev (wdata.path_matches); } else //the path is all explicit { if (!abs) { //we want the supplied path only g_free (path); path = g_path_get_dirname (string); } parent_path = g_strconcat (path, G_DIR_SEPARATOR_S, NULL); } //allocated parent_path has no prepended cwd, and has trailing separator //now expand the last segment in the supplied path string gchar *name = g_path_get_basename (string); if (strchr (name, '*') != NULL || strchr (name, '*') != NULL) { //last path segment is wild freeme = g_strconcat (parent_path, name, NULL); matches = _e2_utils_match_wild_last (freeme, TRUE); g_free (freeme); if (matches == NULL //error || matches == GINT_TO_POINTER (0x1)) //no match found //send back is what we have now expanded = g_strconcat (parent_path, name, NULL); else { //append each matched item to matched path expanded = g_strdup (""); GList *tmp; for (tmp = matches; tmp != NULL; tmp = tmp->next) { freeme = expanded; expanded = g_strconcat (freeme, " ", parent_path, (gchar*)tmp->data, NULL); g_free (freeme); } e2_list_free_with_data (&matches); } } else //last path segment is explicit expanded = g_strconcat (parent_path, name, NULL); g_free (parent_path); g_free (name); } else { //no path in the string matches = _e2_utils_match_wild_last (string, TRUE); if (matches == NULL) //no wildcard in the name (or error) expanded = g_strdup (string); else if (matches == GINT_TO_POINTER (0x1)) //no match expanded = g_strdup (""); else { expanded = g_strdup (""); GList *tmp; for (tmp = matches; tmp != NULL; tmp = tmp->next) { freeme = expanded; expanded = g_strconcat (freeme, " ", (gchar*)tmp->data, NULL); g_free (freeme); } e2_list_free_with_data (&matches); } } g_free (path); return expanded; } /** @brief replace wildcard character(s) '*' and '?' in commandline utf-8 string @a raw @a raw may include whitespace gap(s), in which case each gap-separated "element" is separately handled. If any element is quoted (by " or ') no wildcard is expanded in that element. If any element includes path separator(s), any wildcarded path segment(s) before the last one are replaced by the first-found match, or if there's no match, the whole expansion fails. Any wildcard in the last (or only) path segment is expanded to _all_ matches, with prepended path if appropriate. @param raw freeable string maybe with wildcard(s) to replace @return @a raw, or a replacement string with wildcards replaced */ gchar *e2_utils_replace_wildcards (gchar *raw) { //quick check if (strchr (raw, '*') == NULL //if always ascii ;, don't need g_utf8_strchr() && strchr (raw, '?') == NULL) //if always ascii ;, don't need g_utf8_strchr() return raw; gchar *p, *s, *freeme, *expanded = g_strdup (""); gchar sep[2] = {'\0', '\0'}; gint cnt1 = 0; //counter for ' chars gint cnt2 = 0; //counter for " chars s = p = raw; while (*p != '\0') { if (*p == '\'') { if (p == raw || *(p-1) != '\\') cnt1++; } else if (*p == '"') { if (p == raw || *(p-1) != '\\') cnt2++; } else if (*p == ' ' || *p == '\t') { //check if separator seems to be outside parentheses if (cnt1 % 2 == 0 && cnt2 % 2 == 0) { //found a gap in the command string sep[0] = *p; *p = '\0'; s = _e2_utils_match_wild_segment (s); freeme = expanded; expanded = g_strconcat (freeme, s, sep, NULL); g_free (s); g_free (freeme); *p = sep[0]; //resume scanning s = e2_utils_pass_whitespace (p+1); if (s == NULL) //irrelevant trailing whitespace break; p = s; continue; } } p++; } if (s != NULL && *s != '\0') { //process last (or only) command_element s = _e2_utils_match_wild_segment (s); //append string to buffer freeme = expanded; expanded = g_strconcat (freeme, s, NULL); g_free (s); g_free (freeme); } g_free (raw); return expanded; } //no. of single-quote characters found in the replacement string, //up to the end of the current segment static gint p_count; //and correspondingly for double-quotes static gint p2_count; static gchar *dollar = "$"; /** @brief add segment to the replacement string Empty strings are ignored. The index for the next segment is updated. The count of single-parentheses is updated. This uses single-byte ascii scanning. @param string string to record @param join pointer to the array of segments of the replacement string @param join_count pointer to index of the next segment @return */ static void _e2_utils_replace_vars_add (gchar *string, gchar *join[], gint *join_count) { if (*string == '\0') return; //no point in adding empty strings join[*join_count] = string; (*join_count)++; //update count of ' characters sofar detected gchar *p = string; while (*p != '\0') { if (*p != '\\') { if (*p++ == '\'') p_count++; if (*p == '"') p2_count++; } else p++; } } /** @brief add original segment to the replacement string To avoid leaks, this adds 2 segments - one with just the separator "$" and the second with the ignored segment of the original string. The count of single-parentheses is updated. This uses single-byte ascii scanning. @param string string to record @param join pointer to the array of segments of the replacement string @param join_count pointer to index of the next segment @return */ static void _e2_utils_replace_vars_ignore (gchar *string, gchar *join[], gint *join_count) { join[*join_count] = dollar; (*join_count)++; _e2_utils_replace_vars_add (string, join, join_count); } /** @brief replace variables in a string Replaces all relevant '~' characters, and all valid environment variables and option variables, in @a raw. ~ will be replaced if preceeded by nothing or whitespace, and followed by nothing or whitespace or '/' Environment references and internal variables must have the form @c \$VAR or @c \${VAR} where @c VAR is the name of the variable. Internal variables get precedence over environment variable with the same name. As a special case, $$ will be replaced by active directory (without trailing /), in effect = `pwd` Option references must have the form @c \$[VAR] where @c VAR is the 'internal' name of the option. Unrecognised variables are ignored. Environment variables inside single parentheses are ignored. Strings prefaced by "\$" are ignored. @code gchar *str = e2_utils_replace_vars ("my home is \${HOME} and my e2 terminal is \$[command-xterm]"); @endcode @param raw utf string maybe with variable(s) etc to replace @return newly allocated string, with variables (if any) replaced as appropriate */ gchar *e2_utils_replace_vars (gchar *raw) { // printd (DEBUG, "e2_utils_replace_vars (raw:%s)", raw); //replace all relevant occurrences of ~ with ${HOME} gchar *s = g_strdup (raw), *p = s, *h = "${HOME}", *freeme; gint cnt1 = 0; //counter for ' chars gint cnt2 = 0; //counter for " chars while (*p != '\0') { if (*p == '\'') { if (p == s || *(p-1) != '\\') cnt1++; } else if (*p == '"') { if (p == s || *(p-1) != '\\') cnt2++; } else if (*p == '~') //always ascii ~, don't need utf8 handler { //check if tilde seems to be outside parentheses if (cnt1 % 2 == 0 && cnt2 % 2 == 0) { //found a candidate for replacement if ( (p == s || *(p-1) == ' ' || *(p-1) == '\t' ) //preceeded by nothing or whitespace && (*(p+1) == G_DIR_SEPARATOR //and followed by '/' || *(p+1) == '\0' || *(p+1) == ' ' || *(p+1) == '\t' ) //or lone char in argument ) { *p = '\0'; freeme = s; s = g_strconcat (s, h, p+1, NULL); p = s + (p - freeme); g_free (freeme); continue; } } } p++; } //replace $... occurrences, taking quoting into account if (strchr (s, '$') != NULL) //if always ascii $, don't need g_utf8_strchr() { //break into pieces and count them gchar **split = g_strsplit (s, "$", -1); gint split_count = 0; while (split[split_count] != NULL) split_count++; //init the stack-store for the pieces of the replacement string gint join_count = 0; gchar *join[split_count * 2 + 1]; //init the counts of quote-chars p_count = p2_count = 0; gchar *rest; _e2_utils_replace_vars_add (split[0], join, &join_count); //save the segment before the 1st '$' gint i; //scan from after 1st '$' for (i = 1; i < split_count; i++) { if (*split[i] == '\0') //2 '$' chars in a row, or trailing '$' { if (split[i+1] != NULL) { #ifdef E2_VFSTMP //FIXME dir when not mounted local #else gchar *cwd = g_strdup (curr_view->dir); #endif //generally strip trailer gint len = strlen (cwd); if (len > 1) *(cwd + len - 1) = '\0'; _e2_utils_replace_vars_add (cwd, join, &join_count); } else //trailing $ _e2_utils_replace_vars_add ("$", join, &join_count); } else if (g_str_has_suffix (split[i-1], "\\") //escaped '$' || *split[i] == '(') //shell command _e2_utils_replace_vars_ignore (split[i], join, &join_count); else if (*split[i] == '[') //option to replace { //no protection check for internal vars gchar *rest = strchr (split[i], ']'); //if always ascii ], don't need g_utf8_strchr() if (rest != NULL) { *rest++ = '\0'; E2_OptionSet *set = e2_option_get (split[i]+1); if (set != NULL) { join[join_count++] = e2_option_str_get_direct (set); _e2_utils_replace_vars_add (rest, join, &join_count); } else { *(--rest) = ']'; _e2_utils_replace_vars_ignore (split[i], join, &join_count); } } else _e2_utils_replace_vars_ignore (split[i], join, &join_count); } else if (*split[i] == '{') //variable to replace { //make sure separator was outside ' ' if (p_count % 2 == 1) _e2_utils_replace_vars_ignore (split[i], join, &join_count); else { rest = strchr (split[i], '}'); //if always ascii }, don't need g_utf8_strchr() if (rest != NULL) { *rest++ = '\0'; const gchar *env = e2_command_get_variable_value (split[i]+1); if (env == NULL) env = g_getenv (split[i]+1); if (env != NULL) { join[join_count++] = (gchar *) env; _e2_utils_replace_vars_add (rest, join, &join_count); } else { *(--rest) = '}'; _e2_utils_replace_vars_ignore (split[i], join, &join_count); } } else _e2_utils_replace_vars_ignore (split[i], join, &join_count); } } else //something else (maybe a variable) { //make sure separator was outside ' ' if (p_count % 2 == 1) _e2_utils_replace_vars_ignore (split[i], join, &join_count); else { gchar c = 0; gchar *st; rest = e2_utils_find_whitespace (split[i]); if (p2_count % 2 == 1) { //inside double-quote st = strchr (split[i], '"'); if (st != NULL && (rest == NULL || st < rest)) rest = st; } if (rest != NULL) { c = *rest; *rest = '\0'; } st = e2_utils_strcat ("$", split[i]); const gchar *env = e2_command_get_variable_value (st); g_free (st); if (env == NULL) env = g_getenv (split[i]); //WHAT IS THIS FOR? if (rest == NULL) { if (env != NULL) join[join_count++] = (gchar *) env; else _e2_utils_replace_vars_ignore (split[i], join, &join_count); } else //rest != NULL { *rest = c; if (env != NULL) { join[join_count++] = (gchar *) env; _e2_utils_replace_vars_add (rest, join, &join_count); } else _e2_utils_replace_vars_ignore (split[i], join, &join_count); } } } } join[join_count] = NULL; g_free (s); s = g_strjoinv (NULL, join); g_strfreev (split); } return s; } /** @brief get coordinates of @a widget relative to its current screen @param widget the activated widget whose position is to be calculated @param x pointer to gint storage for the x (left) coordinate @param y pointer to gint storage for the y (top) coordinate @return */ void e2_utils_get_abs_pos (GtkWidget *widget, gint *x, gint *y) { if (GTK_WIDGET_TOPLEVEL (widget)) gdk_window_get_position (widget->window, x, y); else { GdkWindow *window; GtkWidget *current = widget; while (GTK_WIDGET_NO_WINDOW (current) && (current = gtk_widget_get_parent(current)) != NULL) {} if (current != NULL) window = current->window; else //should never happen window = app.main_window->window; gdk_window_get_origin (window, x, y); *x += widget->allocation.x; *y += widget->allocation.y; } } /** @brief get current modifiers mask @return */ GdkModifierType e2_utils_get_modifiers (void) { GdkDisplay *display = gdk_display_manager_get_default_display (gdk_display_manager_get()); GdkModifierType mask; gdk_display_get_pointer (display, NULL, NULL, NULL, &mask); // guint modifiers = gtk_accelerator_get_default_mod_mask (); // return mask & modifiers; return mask; } /** @brief emit beep sound @return */ void e2_utils_beep (void) { GdkDisplay *display = gdk_display_manager_get_default_display (gdk_display_manager_get()); gdk_display_beep (display); } /** @brief check whether more than 1 item is selected @param srclist glist of selected items @return TRUE if more than 1 is selected */ gboolean e2_utils_multi_src (GList *srclist) { gint ctr=0; for (; srclist != NULL ; srclist = srclist->next) { ctr++; if (ctr > 1) break; } return (ctr > 1); } /** @brief get char (if any) used as mnemonic in translated @a label @param label translated, utf8-compatible string which may include an '_' indicating the following char is a mnemonic @return lower-case char, if there's a mnemonic, otherwise (gunichar)0 */ gunichar e2_utils_get_mnemonic_char (gchar *label) { gunichar c; gchar *uscore = g_utf8_strchr (label, -1, (gunichar)'_'); if (uscore == NULL) c = (gunichar)'\0'; else { uscore = g_utf8_next_char (uscore); if (*uscore == '\0') c = (gunichar)'\0'; else { c = g_utf8_get_char_validated (uscore, -1); if (c == (gunichar)-1 || c == (gunichar)-2) c = (gunichar)'\0'; else c = g_unichar_tolower (c); } } return c; } /** @brief get gdk key code which matches the char (if any) used as mnemonic in translated @a label @param label translated, utf8-compatible string which may include an '_' indicating the following char is a mnemonic @return gdk keycode, if there's a mnemonic, otherwise 0 */ guint e2_utils_get_mnemonic_keycode (gchar *label) { gunichar c = e2_utils_get_mnemonic_char (label); guint retval = (c == (gunichar)'\0') ? 0 : gdk_unicode_to_keyval (c); return retval; } /** @brief block all relevant signals to a thread Posix doesn't specify which thread receives signals. So this func is generally called inside newly-created threads to prevent signals (esp. SIGCHILD etc for running commands) being delivered to the wrong thread. @return */ void e2_utils_block_thread_signals (void) { sigset_t set; sigfillset (&set); //block all allowed signals // sigemptyset (&set); //block SIGCHILD signals // sigaddset (&set, SIGCHLD); pthread_sigmask (SIG_BLOCK, &set, NULL); } emelfm2-0.4.1/src/utils/e2_tree.h0000600000175000017500000000460011010340377015430 0ustar cairocairo/* $Id: e2_tree.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelfm2. emelfm2 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 3, or (at your option) any later version. emelfm2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_TREE_H__ #define __E2_TREE_H__ #include "emelfm2.h" gboolean e2_tree_find_iter_from_str (GtkTreeModel *model, gint column, const gchar *search, GtkTreeIter *iter, gboolean with_children); gboolean e2_tree_find_iter_from_str_simple (GtkTreeModel *model, gint column, const gchar *search, GtkTreeIter *iter, gboolean with_children); gboolean e2_tree_find_iter_from_str_same (GtkTreeModel *model, gint column, const gchar *search, GtkTreeIter *iter); gboolean e2_tree_find_lowest_iter_from_str (GtkTreeModel *model, gint column, const gchar *search, GtkTreeIter *iter); #ifdef E2_TREEINCREMENT void e2_tree_create_lowest_iter_from_str (GtkTreeModel *model, gint column, gchar *name, GtkTreeIter *iter); #endif //gchar *e2_tree_get_last_string (GtkTreeModel *model, gint column); //guint e2_tree_store_count (GtkTreeModel *model); GtkTreeRowReference *e2_tree_iter_to_ref (GtkTreeStore *store, GtkTreeIter *iter); gboolean e2_tree_ref_to_iter (GtkTreeStore *store, GtkTreeRowReference *ref, GtkTreeIter *iter); gboolean e2_tree_expand_all_cb (GtkWidget *widget, GtkTreeView *treeview); gboolean e2_tree_collapse_all_cb (GtkWidget *widget, GtkTreeView *treeview); gboolean e2_tree_iter_previous (GtkTreeModel *model, GtkTreeIter *iter); GList *e2_tree_copy (GtkTreeView *treeview); void e2_tree_paste (GList *rowscopied, GtkTreeView *treeview); void e2_tree_delete (GtkTreeView *treeview); gchar *e2_tree_row_to_string (GtkTreeModel *model, GtkTreeIter *iter, gint columns, gint level); #ifdef E2_VFS void e2_tree_store_copy (GtkTreeModel *model, gboolean treetype, gpointer *newstore); #endif #endif //ndef __E2_TREE_H__ emelfm2-0.4.1/src/e2_filetype.h0000600000175000017500000000262211010340377015154 0ustar cairocairo/* $Id: e2_filetype.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2003-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_FILETYPE_H__ #define __E2_FILETYPE_H__ #include "emelfm2.h" void e2_filetype_config_show (gchar *extension); void e2_filetype_apply_allnew (void); const gchar **e2_filetype_get_actions (gchar *ext); //gchar **e2_filetype_get_extensions (gchar *category); gchar *e2_filetype_get_default_action (gchar *ext); // UNUSED gchar **e2_filetype_get (gchar *ext); // OLD void add_filetype(gchar *ext, gchar *prog, gchar *desc); void e2_filetype_add_all (void); void e2_filetype_exec_action (gchar *action); void e2_filetype_actions_register (void); void e2_filetype_options_register (void); #endif //ndef __E2_FILETYPE_H__ emelfm2-0.4.1/src/e2_dnd.c0000600000175000017500000006651310720722665014117 0ustar cairocairo/* $Id: e2_dnd.c 739 2007-11-21 03:27:49Z tpgww $ Copyright (C) 2006-2007 tooar . This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/e2_dnd.c @brief drag and drop functions This file contains functions related to selection and drag'ndrop. Relevant button-events are handled in e2_fileview.c */ #include "e2_dnd.h" #include #include "e2_fileview.h" #include "e2_filelist.h" #include "e2_task.h" //order of table entries matters ! GtkTargetEntry target_table[] = { { "text/uri-list", 0, TARGET_URI }, { "text/plain", 0, TARGET_STRING }, // { "XdndDirectSave0", 0, TARGET_XDS } //this last target is supported for drops only }; guint n_targets = sizeof(target_table) / sizeof(GtkTargetEntry); //mouse pointer height, used for getting the path at the _top_ of the pointer static gint pointer_height; //default pointer height, used when we can't find the runtime value //NOTE: setting the height too small eg 16 causes problems for //un-lighting a row when the mouse moves slowly #define POINTER_HEIGHT 26 extern gboolean btn2_released; #ifdef E2_ALTLEFTMOUSE /*this provides a quick check whether the left btn has been pressed set and cleared in button-press and -release callbacks, respectively*/ extern gboolean left_pressed; /*flag for whether selection-by-dragging is underway set TRUE in drag-begin cb, cleared in drag-data-get cb*/ extern gboolean drag_sel_now; #endif #ifdef E2_ALTLEFTMOUSE static GdkModifierType mask; #endif //for DnD autoscrolling #include //c.f POINTER_HEIGHT #define AUTO_SCROLL_MARGIN 20 //delay before autoscrolling starts is approximately the sum of //AUTO_SCROLL_INITIAL_DELAY plus AUTO_SCROLL_STEP_INTERVAL //c.f the default menu-popdown-delay (400 msec) #define AUTO_SCROLL_INITIAL_DELAY 360 //msec between autoscroll steps 25 Hz seems ok to give reasonably smooth effect #define AUTO_SCROLL_STEP_INTERVAL 40 //only ever 1 scroll at a time, so this can be static (for dynamic, add to ViewInfo) //guint auto_scroll_timer_id = 0; /** @brief callback for "drag-begin" signal emitted on drag source when drag is started For normal drag, sets dnd pointer height & multiple-item drag icon For drag-selection, just sets item-counter=1 @param treeview widget where drag started unused @param context dragcontext @param view rt data for view where drag started unused @return */ void e2_dnd_drag_begin_cb (GtkWidget *treeview, GdkDragContext *context, ViewInfo *view) { printd (DEBUG, "drag begin"); GtkSettings *s = gtk_settings_get_default (); gint pointer_width; if (!gtk_icon_size_lookup_for_settings (s, GTK_ICON_SIZE_DND, //? is too big ! &pointer_width, &pointer_height)) pointer_height = POINTER_HEIGHT; //can't find useful size, use the default pointer_height--; //effectively make the 'y hotspot' 1-down from cursor top //remember where we began, for later drop-check // dragged_sel = view->selection; #ifdef E2_ALTLEFTMOUSE mask = e2_utils_get_modifiers (); if (left_pressed && (mask & GDK_CONTROL_MASK) && !(mask & GDK_SHIFT_MASK)) { drag_sel_now = TRUE; //signal that a drag-sel has started //custom (blank) icon for drag-sel, if the relevant mod keys are ok gchar *iconsdir = e2_utils_get_icons_path (TRUE); gchar *localpath = e2_utils_strcat (iconsdir, "dragsel.png"); E2_Image *cached = e2_cache_image_get (localpath, GTK_ICON_SIZE_LARGE_TOOLBAR); gtk_drag_set_icon_pixbuf (context, cached->pixbuf, 10, 0); //x offset is ineffective ... g_free (iconsdir); g_free (localpath); } else { #endif //setup dnd cursor icon guint count = gtk_tree_selection_count_selected_rows (view->selection); if (count > 1) gtk_drag_set_icon_stock (context, GTK_STOCK_DND_MULTIPLE, 10, 0); //y was 1, x makes no diff. // else //no point in checking for > 0 as default will be used, anyway // gtk_drag_set_icon_stock (dc, GTK_STOCK_DND, 10, 0); //y was 1 #ifdef E2_ALTLEFTMOUSE } #endif return; } /** @brief pair of timer callback functions which together, scroll treeview as appropriate during a DnD operation @param view pointer to view data struct @return TRUE to continue timer, FALSE to cancel it */ static gboolean _e2_dnd_auto_scroll2 (ViewInfo *view) { static gdouble prior_scroll = 0.0; //for matching and timer cancellation //autoscroll "drivers" -AUTO_SCROLL_MARGIN .. -1 for backward scroll, 1 .. AUTO_SCROLL_MARGIN for forward gint y_scr; //,x_scr; //must check mouse position here, as e2_dnd_drag_motion_cb() may not be called //often enough when auto-scrolling a large treeview //my and my are for bottom-left of pointer FIXME make it work for top-left gint my; gdk_window_get_pointer (view->treeview->window, NULL, &my, NULL); if (my >= AUTO_SCROLL_MARGIN && my <= (view->treeview->allocation.height - AUTO_SCROLL_MARGIN)) { //mouse out of scroll zones now app.timers[ASCROLL_T] = 0; return FALSE; } else if (my < AUTO_SCROLL_MARGIN) y_scr = my - AUTO_SCROLL_MARGIN; //-AUTO_SCROLL_MARGIN to -1 else y_scr = my + AUTO_SCROLL_MARGIN - view->treeview->allocation.height; //1 to AUTO_SCROLL_MARGIN GtkAdjustment *vadj = gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (view->treeview)); gdouble scrollnow = gtk_adjustment_get_value (vadj); gdouble scroll_factor; //accelerate the scroll by increasing the stepsize when we are further into //the scroll margin. TrialnError fudge factor scroll_factor = vadj->upper / 500.0 * (exp (ABS (y_scr) * 2.0 / (gdouble) AUTO_SCROLL_MARGIN) - 0.8); if (y_scr < 0) scrollnow -= scroll_factor; else scrollnow += scroll_factor; //clamp the new setting within relevant range if (scrollnow < 0) scrollnow = 0; else if (scrollnow > vadj->upper - vadj->page_size) scrollnow = vadj->upper - vadj->page_size - 0.1; //no movement, turn off the scrolling CHECKME more iterations before turnoff ? if (ABS (scrollnow-prior_scroll) < 0.1) //0.001*vadj->upper) { app.timers[ASCROLL_T] = 0; return FALSE; } if (scrollnow >= 0) { gdk_threads_enter (); gtk_adjustment_set_value (vadj, scrollnow); gdk_threads_leave (); prior_scroll = scrollnow; } return TRUE; } static gboolean _e2_dnd_auto_scroll (ViewInfo *view) { //substitute shorter loop for the initial one app.timers[ASCROLL_T] = g_timeout_add (AUTO_SCROLL_STEP_INTERVAL, (GSourceFunc) _e2_dnd_auto_scroll2, view); return FALSE; } /** @brief "drag-motion" signal callback This is called when the user moves the cursor during a drag For a normal drag it handles the highlighting of drop-target rows, and manages treeview scrolling when cursor is near top or bottom of treeview For a drag-selection it selects item if needed and bumps counter @param treeview widget ptr @param context drag context @param x the left coordinate of the current cursor position @param y the BOTTOM coordinate of the current cursor position @param time the timestamp of the motion event @param view rt data for the view where the drag [started?] @return TRUE unless drag-sel is happening (i.e. everywhere is considered a drop-zone) */ gboolean e2_dnd_drag_motion_cb (GtkWidget *treeview, GdkDragContext *context, gint x, gint y, guint time, ViewInfo *view) { // printd (DEBUG, "callback: drag motion"); static ViewInfo *last_view; GtkTreeIter iter; GtkTreePath *path; GtkTreeModelFilter *fmodel = GTK_TREE_MODEL_FILTER (view->model); //same as fmodel - gtk_tree_view_get_model (GTK_TREE_VIEW (treeview)); // GtkTreeModel *model = gtk_tree_model_filter_get_model (fmodel); //same as model - GTK_TREE_MODEL (view->store); GtkTreeModel *model = GTK_TREE_MODEL (view->store); //get the path of the item under the cursor TOP ie y-pointer_height if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (treeview), x, y-pointer_height, &path, NULL, NULL, NULL)) { #ifdef E2_ALTLEFTMOUSE if (drag_sel_now //a shift-left drag was started // && (mask & GDK_CONTROL_MASK) && !(mask & GDK_SHIFT_MASK))//mod keys still correct ) { GtkTreeSelection *sel = view->selection; if (!gtk_tree_selection_path_is_selected (sel, path) //over an un-selected item && (treeview == gtk_drag_get_source_widget (context))) //in the original treeview { //select it gtk_tree_selection_select_path (sel, path); gtk_tree_path_free (path); return FALSE; } } #endif GtkTreePath *childpath = gtk_tree_model_filter_convert_path_to_child_path (fmodel, path); //list store path depth is 1, so row no. is 1st index gint row = *gtk_tree_path_get_indices (childpath); if (view == last_view) { if (row != view->drop_row //mouse has moved to another row && view->lit // && view->drop_row != -1 && gtk_tree_model_iter_nth_child (model, &iter, NULL, view->drop_row)) { // printd (DEBUG, "restored color of row %d in same pane", view->drop_row); e2_fileview_clear_row_background (view, &iter); } } else { //1st use, or changed pane // the drag-leave callback handles color-restoration & drop_row = -1 last_view = view; } if (row != view->drop_row && gtk_tree_model_get_iter (model, &iter, childpath)) { view->drop_row = row; FileInfo *info; gtk_tree_model_get (model, &iter, FINFO, &info, -1); //only highlight dirs (either pane) //FIXME also highlight executable items if (e2_fs_is_dir (info, view)) { // printd (DEBUG, "CHANGED color of row %d in same pane", row); e2_fileview_set_row_background (view, &iter, e2_option_color_get ("color-highlight")); } } gtk_tree_path_free (path); gtk_tree_path_free (childpath); } else //there is no row under the mouse-top { // printd (DEBUG, "No row under mouse now"); if (view == last_view) //still in same pane { if (view->lit // && view->drop_row != -1 && gtk_tree_model_iter_nth_child (model, &iter, NULL, view->drop_row)) { // printd (DEBUG, "restored color of row %d in same pane", view->drop_row); //restore the color of the last row that was lit e2_fileview_clear_row_background (view, &iter); view->drop_row = -1; } } else //now in the other file-pane { // the drag-leave callback andles color restoration last_view = view; } } //arrange auto-scrolling when mouse pointer is near top or bottom of treeview if (y < (pointer_height + AUTO_SCROLL_MARGIN) || y > (view->treeview->allocation.height - AUTO_SCROLL_MARGIN)) { if (app.timers[ASCROLL_T] == 0) { //setup initial delay app.timers[ASCROLL_T] = g_timeout_add (AUTO_SCROLL_INITIAL_DELAY, (GSourceFunc) _e2_dnd_auto_scroll, view); } } else if (app.timers[ASCROLL_T] > 0) { g_source_remove (app.timers[ASCROLL_T]); app.timers[ASCROLL_T] = 0; } return TRUE; } /** @brief revert a drop target directory to default background This is the callback for "drag-leave" signals, and is called when changing file panes, and for exits from the app window @param treeview widget which was departed @param context dragcontext unused @param time unused @param view rt data for the view where the drop ocurred @return TRUE if the conversion completed successfully, else FALSE */ gboolean e2_dnd_drag_leave_cb (GtkWidget *treeview, GdkDragContext *context, guint time, ViewInfo *view) { // printd (DEBUG, "callback: drag leave"); //turn off any autoscrolling due to departure if (app.timers[ASCROLL_T] > 0) { g_source_remove (app.timers[ASCROLL_T]); app.timers[ASCROLL_T] = 0; } gboolean retval; GtkTreeIter iter; //always use the underlying model, here if (!view->lit || view->drop_row == -1 || ! gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (view->store), &iter, NULL, view->drop_row)) { retval = FALSE; } else { e2_fileview_clear_row_background (view, &iter); retval = TRUE; } view->drop_row = -1; //make sure any re-entry will trigger a highlight return retval; } /* * @brief UNUSED "drag-data-delete" signal callback This is called when a drag of type GDK_ACTION_MOVE was successfully completed. @param treeview widget where drag started unused @param dc dragcontext @param view rt data for view where drag started @return */ /*void e2_dnd_drag_delete_cb (GtkWidget *treeview, GdkDragContext *context, ViewInfo *view) { printd (DEBUG, "drag data delete"); e2_task_delete (NULL, NULL, NULL); //delete active-pane selected items return; } */ /** @brief callback for "drag-data-get" signal This is the emitted on the drag source when the drop site requests the data which has been dragged. For a drag-selection it just cleans up some related flags. For a normal drag it builds a URI list of the selected filenames to set as the drag data. Any directory item will not have a trailing separator, as the names are freshly converted from the relevant FileInfo field @param treeview UNUSED the treeview which received the signal @param context UNUSED the drag context @param data the GtkSelectionData to be filled with the dragged data @param info_arg the info that has been registered with the target in the GtkTargetList. @param time the timestamp at which the data was requested @param view data structure for the view @return */ void e2_dnd_drag_data_get_cb (GtkWidget *treeview, GdkDragContext *context, GtkSelectionData *data, guint info_arg, guint time, ViewInfo *view) { printd (DEBUG, "callback: drag data get"); #ifdef E2_ALTLEFTMOUSE if (drag_sel_now) { drag_sel_now = FALSE; return; } #endif #ifdef E2_REFRESH_DEBUG printd (DEBUG, "disable refresh, drag data get"); #endif e2_filelist_disable_refresh(); /* this is the emerging standard for uri list entries ? \r\n \r\n \r\n \r\n \r\n \r\n \r\n for now, at least, in accord with old w3c standard (which is currently under review) /foo/bar => file:///foo/bar OR /foo/bar => file://host/foo/bar (this is the xdg preferred style) NOTE some apps omit the leading "//" from the first example, resulting in file:/foo/bar */ GList *base, *tmp; base = e2_fileview_get_selected_local (view); if (base != NULL) { //there is actually a selection to process ... gchar *uri, *local_path, *local_dir; #ifdef E2_VFSTMP //FIXME dir when not mounted local #else local_dir = F_FILENAME_TO_LOCALE (view->dir); #endif GPtrArray *uri_array = g_ptr_array_sized_new (g_list_length (base) + 1); //FIXME vfs /* some apps don't yet support URI's with a host, we'll use worst-case #ifdef USE_GLIB2_8 const gchar *host = g_get_host_name (); #else const gchar *host = "localhost"; #endif */ // GError *error = NULL; for (tmp = base; tmp != NULL; tmp = tmp->next) { local_path = e2_utils_strcat (local_dir, ((FileInfo *)tmp->data)->filename); //see above uri = g_filename_to_uri (local_path, host, NULL); //&error); uri = g_filename_to_uri (local_path, NULL, NULL); //&error); g_free (local_path); if (uri != NULL) g_ptr_array_add (uri_array, uri); /* else { //FIXME warn the user g_error_free (error); } */ } g_ptr_array_add (uri_array, NULL); //NULL-terminate the array gtk_selection_data_set_uris (data, (gchar **)uri_array->pdata); F_FREE (local_dir); g_ptr_array_free (uri_array, TRUE); g_list_free (base); } #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, drag data get"); #endif e2_filelist_enable_refresh (); } /* * @brief UNUSED callback for "drag-drop" signal emitted on the drop site when the user drops data there This is called for only XDS protocol ?? @param treeview widget where drag occured @param context source? drag context @param x the x coordinate of the current cursor position @param y the y coordinate of the current cursor position @param time the timestamp of the motion event @param view rt data for view where drag started ? @return TRUE always (the cursor is always in a drop zone) */ /* THIS DOES NOT WORK gboolean e2_dnd_drag_drop_cb (GtkWidget *treeview, GdkDragContext *context, gint x, gint y, guint time, ViewInfo *view) { printd (DEBUG, "callback: drag drop"); if (context->protocol == GDK_DRAG_PROTO_XDND) { gint srcformat, srclen; guchar *srcitem; gchar *dest_dir, *destpath = NULL; if (gdk_property_get (context->source_window, atom_XdndDirectSave0, atom_text_plain, 0, PATH_MAX, FALSE, NULL, &srcformat, &srclen, &srcitem)) { if (srcitem != NULL) { //terminate the path|name string srcitem [srclen] = '\0'; //CHECKME convert to utf8 ? //FIXME get host, destpath) #ifdef E2_VFSTMP //FIXME dir when not mounted local #else dest_dir = F_FILENAME_TO_LOCALE (view->dir); //localised string with trailing / #endif GtkTreePath *path; if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (treeview), x, y-pointer_height, &path, NULL, NULL, NULL)) { GtkTreeIter iter; GtkTreeModelFilter *fmodel = GTK_TREE_MODEL_FILTER (view->model); //same as fmodel - gtk_tree_view_get_model (GTK_TREE_VIEW (treeview)); GtkTreeModel *model = gtk_tree_model_filter_get_model (fmodel); //same as model - GTK_TREE_MODEL (view->store); GtkTreePath *childpath = gtk_tree_model_filter_convert_path_to_child_path (fmodel, path); if (gtk_tree_model_get_iter (model, &iter, childpath)) { FileInfo *info; gtk_tree_model_get (model, &iter, FINFO, &info, -1); //NICE to add check for executable, here, and do a different drop menu //check if dropping onto a dir if (e2_fs_is_dir (info, view)) { //prevent dropping into a dir that's part of the selection if ((treeview == gtk_drag_get_source_widget (context)) //we're in the same selection as we started with && gtk_tree_selection_path_is_selected (view->selection, path)) { gtk_tree_path_free (path); gtk_tree_path_free (childpath); gtk_drag_finish (context, FALSE, FALSE, time); return FALSE; } //append target dir's name to the pane dir path //view->dir, filename both utf, both have trailing / #ifdef E2_VFSTMP //FIXME dir when not mounted local #else destpath = g_strconcat (dest_dir, info->filename, G_DIR_SEPARATOR_S, srcitem, NULL); #endif } } gtk_tree_path_free (path); gtk_tree_path_free (childpath); } if (destpath == NULL) destpath = g_strconcat (dest_dir, srcitem, NULL); //FIXME get correct hostname gchar *desturi = g_filename_to_uri (destpath, NULL, NULL); gdk_property_change (context->source_window, atom_XdndDirectSave0, atom_text_plain, srcformat, GDK_PROP_MODE_REPLACE, (guchar *)desturi, strlen (desturi)); F_FREE (dest_dir); g_free (destpath); g_free (desturi); gtk_drag_get_data (treeview, context, atom_XdndDirectSave0, time); return TRUE; } } } / * else { GList *member; for (member = context->targets; member != NULL; member = member->next) { if ((GdkAtom) member->data == atom_text_uri_list) break; } if (member == NULL) goto badexit; } * / //failure gtk_drag_finish (context, FALSE, FALSE, time); return FALSE; } */ /** @brief get drag op menu item @param menuitem the clicked menu item widget @param chosen store for the chosen item @return */ static void _e2_dnd_drag_op_selected_cb (GtkWidget *menuitem, GtkWidget **chosen) { *chosen = menuitem; gtk_main_quit (); } /** @brief execute drop This is the callback for "drag-data-received" signals, emitted on @a treeview when dragged data has been received. It initiates action for the dropped items @param treeview widget for pane where the drop is to occur @param context the dragcontext @param x where the drop happened, mouse-cursor-left @param y where the drop happened, mouse-cursor-BOTTOM @param data pointer to selected items data struct @param drag_info the info that has been registered with the target in the GtkTargetList @param time the timestamp at which the data was received @param view pointer to ViewInfo data struct for the pane shown in @a treeview @return */ void e2_dnd_drag_data_received_cb (GtkWidget *treeview, GdkDragContext *context, gint x, gint y, GtkSelectionData *data, guint drag_info, guint time, ViewInfo *view) { printd (DEBUG, "callback: drag data received"); /* if (*(data->data) == '\0') goto badexit; */ #ifdef E2_ALTLEFTMOUSE left_pressed = FALSE; //flag must be cleared at end of drag #endif gchar **uris = gtk_selection_data_get_uris (data); if (uris == NULL) goto badexit; gchar *dest_dir; //create relevant drop-directory path string //default is to just drop to the pane dir #ifdef E2_VFSTMP //FIXME dir when not mounted local dest_dir = view->dir; //utf-8 string with trailing / #else dest_dir = view->dir; //utf-8 string with trailing / #endif GtkTreePath *path; if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (treeview), x, y-pointer_height, &path, NULL, NULL, NULL)) { GtkTreeIter iter; GtkTreeModelFilter *fmodel = GTK_TREE_MODEL_FILTER (view->model); //same as fmodel - gtk_tree_view_get_model (GTK_TREE_VIEW (treeview)); GtkTreeModel *model = gtk_tree_model_filter_get_model (fmodel); //same as model - GTK_TREE_MODEL (view->store); GtkTreePath *childpath = gtk_tree_model_filter_convert_path_to_child_path (fmodel, path); if (gtk_tree_model_get_iter (model, &iter, childpath)) { gchar *filename; FileInfo *info; gtk_tree_model_get (model, &iter, FILENAME, &filename, FINFO, &info, -1); //NICE to add check for executable, here, and do a different drop menu //check if dropping onto a dir if (e2_fs_is_dir (info, view)) { //now that we're finishing, un-light the row e2_fileview_clear_row_background (view, &iter); //prevent dropping into a dir that's part of the selection if ((treeview == gtk_drag_get_source_widget (context)) //we're in the same selection as we started with && gtk_tree_selection_path_is_selected (view->selection, path)) { g_free (filename); gtk_tree_path_free (path); gtk_tree_path_free (childpath); goto cleanbadexit; } //append target dir's name to the pane dir path //view->dir, filename both utf, both have trailing / #ifdef E2_VFSTMP //FIXME dir when not mounted local #else dest_dir = e2_utils_dircat (view, filename, FALSE); #endif } g_free (filename); } gtk_tree_path_free (path); gtk_tree_path_free (childpath); } gchar *dest_local = D_FILENAME_TO_LOCALE (dest_dir); #ifdef E2_VFSTMP //FIXME dir when not mounted local #else if (dest_dir != view->dir) #endif g_free (dest_dir); //get the sender's or user's choice on what to do with the dragged items gboolean menu_wanted; if (btn2_released) menu_wanted = TRUE; else { //need to get fresh mask here, the one set in drag-begin doesn't work GdkModifierType mask = e2_utils_get_modifiers (); // guint modifiers = gtk_accelerator_get_default_mod_mask (); // menu = ((mask & modifiers) == GDK_MOD1_MASK); //only alt key pressed menu_wanted = (mask & GDK_MOD1_MASK); //alt key pressed } GdkDragAction action = context->actions; if (menu_wanted && (action & GDK_ACTION_ASK)) { GtkWidget *drag_op_menu = gtk_menu_new(); GtkWidget *item1, *item2, *item3, *item4, *chosen; if (action & GDK_ACTION_COPY) item1 = e2_menu_add (drag_op_menu, _("_Copy"), NULL, NULL, _e2_dnd_drag_op_selected_cb, &chosen); else item1 = NULL; if (action & GDK_ACTION_MOVE) item2 = e2_menu_add (drag_op_menu, _("_Move"), NULL, NULL, _e2_dnd_drag_op_selected_cb, &chosen); else item2 = NULL; if (action & GDK_ACTION_LINK) item3 = e2_menu_add (drag_op_menu, _("_Link"), NULL, NULL, _e2_dnd_drag_op_selected_cb, &chosen); else item3 = NULL; /* if (?) //drop target is an executable item item4 = e2_menu_add (drag_op_menu, _("_SendTo"), NULL, NULL, _e2_dnd_drag_op_selected_cb, &chosen); else item4 = NULL; */ item4 = e2_menu_add (drag_op_menu, _("C_ancel"), NULL, NULL, _e2_dnd_drag_op_selected_cb, &chosen); chosen = item4; gtk_menu_popup (GTK_MENU (drag_op_menu), NULL, NULL, NULL, NULL, 0, (guint32) time); gtk_main (); gtk_widget_destroy (drag_op_menu); if (chosen == item1) action = GDK_ACTION_COPY; else if (chosen == item2) action = GDK_ACTION_MOVE; else if (chosen == item3) action = GDK_ACTION_LINK; /* else if (chosen == item4) setup to handle the sendto request */ else //the user cancelled goto cleanbadexit; } else //don't ask action = context->suggested_action; //convert uris vector to dest path and names array, for processing //FIXME vfs GPtrArray *names_array = g_ptr_array_new (); gchar *basename, *path_local, *check; gchar *src_local = NULL; //path of each dropped item, localised with trailing / gint path_len = -1; // GError *error = NULL; gchar **tmp = uris; while (*tmp != NULL) { path_local = g_filename_from_uri (*tmp, NULL, NULL); //&error); if (path_local == NULL) { //check if we have a bad file URI check = g_filename_to_uri (*tmp, NULL, NULL); if (check != NULL) { path_local = g_filename_from_uri (check, NULL, NULL); g_free (check); } } if (path_local != NULL) { if (path_len == -1) { //once-only, setup some things //assumes at least 1 separator and no trailing separator CHECKME ok ? basename = strrchr (path_local, G_DIR_SEPARATOR); basename++; path_len = basename - path_local; src_local = g_strndup (path_local, path_len); } //scrub the path, want only the basename in the array g_strlcpy (path_local, path_local + path_len, strlen (path_local + path_len) + 1); g_ptr_array_add (names_array, path_local); } /* else { //FIXME warn the user g_error_free (error); } */ tmp++; } g_strfreev (uris); //check for malformed uris if (names_array->len == 0 || src_local == NULL) { g_ptr_array_free (names_array, TRUE); goto badexit; } //do it //FIXME handle sendto E2_TaskType droptask; if (action & GDK_ACTION_COPY) droptask = E2_TASK_COPY; else if (action & GDK_ACTION_MOVE) droptask = E2_TASK_MOVE; else if (action & GDK_ACTION_LINK) droptask = E2_TASK_LINK; else droptask = -1; gboolean result = (droptask != -1) ? e2_task_drop (droptask, src_local, dest_local, names_array): FALSE; context->action = action; gboolean move = (action & GDK_ACTION_MOVE); //paths and names are not copied in the handler, so may not be cleared here // g_free (src_local); // g_free (dest_local); // g_ptr_array_free (names_array, TRUE); goto exit; cleanbadexit: g_strfreev (uris); badexit: result = FALSE; move = FALSE; exit: gtk_drag_finish (context, result, move, time); //CHECKME filtermodel ... /* Must override the default 'drag_data_received' handler on GtkTreeView when using models that don't support the GtkTreeDragDest interface and enabling drag-and-drop. The simplest way to do this is to connect to 'drag_data_received' and call g_signal_stop_emission_by_name() in your signal handler to prevent the default handler from running. Look at the source code for the default handler in gtktreeview.c to get an idea what your handler should do. */ //CHECKME nothing else needs doing here ? g_signal_stop_emission_by_name (G_OBJECT (view->treeview), "drag-data-received"); return; } emelfm2-0.4.1/src/e2_filetype.c0000600000175000017500000006372510776374631015204 0ustar cairocairo/* $Id: e2_filetype.c 853 2008-04-07 10:38:17Z tpgww $ Copyright (C) 2003-2008 tooar Portions copyright (C) 1999 Michael Clark. This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "emelfm2.h" #include #include "e2_filetype.h" #ifdef OLDFTDLG #include "e2_config_dialog.h" #else #include "e2_dialog.h" #endif /** @brief get extensions array for filetype category @a category Not much use - in the config data, the categories are user-editable !! @param category i18n utf8 string containing category to be matched @return newly-allocated, NULL-terminated string array of utf-8 extensions for @a category, or NULL if none found */ /*gchar **e2_filetype_get_extensions (gchar *category) { E2_OptionSet *set; GtkTreeIter iter, iter2, iter3; //3 tree levels to scan gchar *extension; GPtrArray *e_ray; if ( ((set = e2_option_get_simple ("filetypes")) != NULL) && gtk_tree_model_get_iter_first (set->ex.tree.model, &iter) ) { e_ray = g_ptr_array_sized_new (5); while (e2_tree_find_iter_from_str_simple (set->ex.tree.model, 0, category, &iter, FALSE)) //must do children check later, anyhow { //category loop = level 1 if (gtk_tree_model_iter_children (set->ex.tree.model, &iter2, &iter)) { do { // extension or command loop = level 2 if (gtk_tree_model_iter_children (set->ex.tree.model, &iter3, &iter2)) { gtk_tree_model_get (set->ex.tree.model, &iter2, 1, &extension, -1); if (g_str_equal (extension, _C(13))) //extensions node found { g_free (extension); do { //extension loop = level 3, build the extensions array gtk_tree_model_get (set->ex.tree.model, &iter3, 1, &extension, -1); g_ptr_array_add (e_ray, extension); } while (gtk_tree_model_iter_next (set->ex.tree.model, &iter3)); } else g_free (extension); //not an extensions branch, just cleanup } } while (gtk_tree_model_iter_next (set->ex.tree.model, &iter2)); } } if (e_ray->len > 0) { g_ptr_array_add (e_ray, NULL); //add end marker (also to support g_strfreev) return ((gchar **)g_ptr_array_free (e_ray, FALSE)); } g_ptr_array_free (e_ray, TRUE); } printd (WARN, "no config filetype information for %s", category); return NULL; } */ /** @brief helper function to get actions list for specified file extension without regard to string case @param key an extension logged in the filetypes hash table @param value UNUSED @param ext the filetype extension being souht @return TRUE when a match is found */ gboolean _e2_filetype_find_any (gpointer key, gpointer value, gchar *ext) { return (e2_utf8_caseless_match ((gchar *)key, ext, -1, -1)); } /** @brief get actions 'list' for specified file extension Returns the actions array (in the file types hash) for the extension. The returned array must NOT be altered or freed. Used only by the context-menu creator. @param ext utf8 string containing file extension to be matched @return null-terminated string array of actions for @a ext, or NULL if none found */ const gchar **e2_filetype_get_actions (gchar *ext) { const gchar **actions; if ( g_str_equal (ext, _("")) || g_str_equal (ext, _("")) || g_str_equal (ext, _("")) || (!e2_option_bool_get ("anycase-filetypes") && e2_fs_mount_is_cased (curr_view))) actions = (const gchar **) g_hash_table_lookup (app.filetypes, ext); else actions = (const gchar **) g_hash_table_find (app.filetypes, (GHRFunc)_e2_filetype_find_any, ext); if (!(actions == NULL || *actions == '\0')) return actions; else return NULL; } /** @brief get default action for specified file extension Allocates and returns the first action from the actions array (in the file types hash) for the specified extension. The returned string needs to be freed. Used only by callback open_cb. @param ext utf8 string containing extension to be matched * @return newly-allocated string which is the default action for ext, or NULL if none found */ gchar *e2_filetype_get_default_action (gchar *ext) { const gchar **actions = e2_filetype_get_actions (ext); if (!(actions == NULL || *actions == '\0')) return g_strdup (*actions); else return NULL; } /** @brief load all filetype info into hash Converts config tree data into filetype runtime data hash by means of a depth-first search of 3-level tree (graph) For each 'category' of filetypes, the order of level-2 'commands' and 'extensions' nodes is irrelevant, and multiple 'commands' and/or 'extensions' nodes are allowed. The hash entry for each file extension is an arrray of command strings. If a (level-3) command has a separate menu name, the command string is stored as menuname"@"commandstring (so menu names can't have a @ in them) NB in the previous line, the "" around the @ are to fix doxygen, the string doesn't really have them @return */ void e2_filetype_add_all (void) { E2_OptionSet *set; GtkTreeIter iter, iter2, iter3; //3 tree levels to scan gchar *category, *extension, *cmd_string; GPtrArray *e_ray, *c_ray; app.filetypes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); app.typelist = NULL; if ( ((set = e2_option_get_simple ("filetypes")) != NULL) && gtk_tree_model_get_iter_first (set->ex.tree.model, &iter) ) { e_ray = g_ptr_array_sized_new (5); do { //category loop = level 1 //re-use the extensions array if (e_ray->len > 0) g_ptr_array_remove_range (e_ray, 0, e_ray->len); //setup a fresh commands array for each category c_ray = g_ptr_array_sized_new (5); //note the category, in case error msg is needed gtk_tree_model_get (set->ex.tree.model, &iter, 0, &category, -1); if (gtk_tree_model_iter_children (set->ex.tree.model, &iter2, &iter)) { do { // extension or command loop = level 2 gtk_tree_model_get (set->ex.tree.model, &iter2, 1, &extension, -1); if (gtk_tree_model_iter_children (set->ex.tree.model, &iter3, &iter2)) { if (g_str_equal (extension, _C(13))) //extensions node found { g_free (extension); do { //extension loop = level 3, build the extensions array gtk_tree_model_get (set->ex.tree.model, &iter3, 1, &extension, -1); g_ptr_array_add (e_ray, extension); } while (gtk_tree_model_iter_next (set->ex.tree.model, &iter3)); } else if (g_str_equal (extension, _C(6))) //commands node found { g_free (extension); do { //commands loop also = level 3, build the commands array //this time, extension is really a command name gtk_tree_model_get (set->ex.tree.model, &iter3, 1, &extension, 2, &cmd_string, -1); if (*cmd_string != '\0') { //concatenate with separator gchar *freeme = extension; extension = g_strconcat (extension, "@", cmd_string, NULL); g_free (freeme); } g_free (cmd_string); g_ptr_array_add (c_ray, extension); } while (gtk_tree_model_iter_next (set->ex.tree.model, &iter3)); } else { //OOPS printd (WARN, "un-recognised node in filetypes config for %s", category); } } else g_free (extension); //no children, just cleanup } while (gtk_tree_model_iter_next (set->ex.tree.model, &iter2)); g_ptr_array_add (c_ray, NULL); //add end marker (for detecting end, also to support g_strfreev) //register the new filetype(s) guint i; gchar **iterator = (gchar **) e_ray->pdata; for (i = 0; i < e_ray->len; i++, iterator++) g_hash_table_insert (app.filetypes, *iterator, c_ray->pdata); //one copy of the commands array, so it can't be freed when hash is destroyed, //so note where it is, for separate cleanup purposes app.typelist = g_slist_append (app.typelist, c_ray->pdata); g_ptr_array_free (c_ray, FALSE); //cleanup, ready for next category } g_free (category); } while (gtk_tree_model_iter_next (set->ex.tree.model, &iter)); g_ptr_array_free (e_ray, FALSE); //no need to free any strings, that is done when they are added to hash } else { printd (WARN, "no config filetype information"); } } /** @brief run filetype action on selected items when %f missing This is like a regular command, but if there is no %f in the action given, it appends the selected item(s) to the end of the action string This is so the user can just enter like "xmms" as an action and not have to remember to append the %f @param command command to be run, utf8 string * @return */ void e2_filetype_exec_action (gchar *command) { #ifdef E2_COMMANDQ e2_command_run (command, E2_COMMAND_RANGE_FILE_ACTION, FALSE); #else e2_command_run (command, E2_COMMAND_RANGE_FILE_ACTION); #endif } /** @brief change filetype config data for a selected item @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if something was selected in the active pane */ static gboolean _e2_filetype_config (gpointer from, E2_ActionRuntime *art) { //find current filetype FileInfo *info = e2_fileview_get_selected_first_local (curr_view, FALSE); if (info == NULL) return FALSE; gchar *ext; if (e2_fs_is_dir (info, curr_view)) ext = _(""); else if (e2_fs_is_executable (info, curr_view)) ext = _(""); else { //maybe extra '.'(s) in the name eg version no so ext needs to be reparsed ext = strchr (info->filename, '.'); //localised text, assumes '.' is ascii if (ext == NULL || *(ext+1) == '\0') ext = _(""); else ext++; } //open the config dialog at the appropriate place gchar *utf = F_FILENAME_FROM_LOCALE (ext); //DISPLAY ?? e2_filetype_config_show (utf); F_FREE (utf); return TRUE; } /** @brief open filetypes config dialog This may be used in a callback from the filetype dialog, in which case @a extension == NULL OR this may be called with a specific extension, in which case @a extension != NULL @param extension pointer to extension to change, or NULL @return */ void e2_filetype_config_show (gchar *extension) { /*#ifdef OLDFTDLG //create dialog, but don't show it yet E2_SpecificConfDialogRuntime *rt = e2_config_dialog_single ("filetypes", e2_filetype_apply_allnew, FALSE); //internal name, no translation if (rt == NULL) HANDLE ERROR //get path to filetype's node GtkTreeModel *model = rt->set->ex.tree.model; GtkTreeIter iter; if (!gtk_tree_model_get_iter_first (model, &iter)) { printd (WARN, "e2_filetype_config_show: can't find config data for filetypes"); gtk_widget_destroy (rt->dialog); DEALLOCATE (E2_SpecificConfDialogRuntime, rt); return; } GtkTreeView *treeview = GTK_TREE_VIEW (rt->set->widget); gboolean found = FALSE; if (extension != NULL && *extension != '\0') { //if this is a specific-extension edit, find it in the tree gchar *tmp = extension; while (!found) { found = e2_tree_find_iter_from_str_simple (model, 1, tmp, &iter, FALSE); //make sure there's no 'smaller' valid extension if (!found) { tmp = strchr (tmp, '.'); //always ascii if (tmp == NULL) break; tmp++; //pass the '.' gtk_tree_model_get_iter_first (model, &iter); } } } gtk_tree_view_collapse_all (treeview); //this fn causes dialog window to be shown ! if (found) { //path to extension GtkTreePath *path = gtk_tree_model_get_path (model, &iter); GtkTreePath *path2 = gtk_tree_path_copy (path); gtk_tree_path_up (path2); //extensions node gtk_tree_path_up (path2); //category node //show it // gtk_tree_view_expand_to_path (treeview, path); gtk_tree_view_expand_row (treeview, path2, TRUE); GtkTreeViewColumn *col = gtk_tree_view_get_column (treeview, 0); gtk_tree_view_set_cursor (treeview, path, col, FALSE); gtk_tree_view_scroll_to_cell (treeview, path, col, TRUE, 0.5, 0.5); gtk_tree_path_free (path); gtk_tree_path_free (path2); } // else // { //non-recognised extension, just show all the categories // gtk_tree_view_collapse_all (treeview); // gtk_tree_view_expand_row (treeview, path, FALSE); // } //go there // GtkTreeViewColumn *col = gtk_tree_view_get_column (treeview, 0); // gtk_tree_view_set_cursor (treeview, path, col, FALSE); // gtk_tree_view_scroll_to_cell (treeview, path, col, TRUE, 0.5, 0.5); // gtk_tree_path_free (path); //now we're ready to show this dialog // gtk_widget_show_all (rt->dialog); gtk_widget_show_all (rt->set->widget); #else //use new form of dialog */ E2_OptionSet *set = e2_option_get_simple ("filetypes"); if (set == NULL) { printd (WARN, "e2_filetype_config_show: can't find config data for filetypes"); return; } //get filetype's node, if we can GtkTreeModel *model = set->ex.tree.model; GtkTreeIter iter; if (!gtk_tree_model_get_iter_first (model, &iter)) { printd (WARN, "e2_filetype_config_show: can't find config data for filetypes"); return; } gboolean found = FALSE; gchar *category = NULL; if (extension != NULL && *extension != '\0') { //if this is a specific-extension edit, find it in the tree gchar *tmp = extension; while (!found) { found = e2_tree_find_iter_from_str_simple (model, 1, tmp, &iter, FALSE); if (found) { GtkTreeIter iter2; gtk_tree_model_iter_parent (model, &iter2, &iter); //extensions node gtk_tree_model_iter_parent (model, &iter, &iter2); //category node gtk_tree_model_get (model, &iter, 0, &category, -1); break; } else //make sure there's no 'smaller' valid extension { tmp = strchr (tmp, '.'); //always ascii if (tmp == NULL) break; tmp++; //pass the '.' gtk_tree_model_get_iter_first (model, &iter); } } } e2_filetype_dialog_edit_create (category); if (category != NULL) g_free (category); //#endif } /** @brief update filetypes information This is the 'apply' fn for the edit filetypes dialog, also used in general config dialog The entries in app.typelist are NULL-terminated string arrays, but are created as pointer arrays, not with g_strsplit @return */ void e2_filetype_apply_allnew (void) { g_hash_table_destroy (app.filetypes); g_slist_foreach (app.typelist, (GFunc) g_strfreev, NULL); g_slist_free (app.typelist); e2_filetype_add_all (); } /** @brief register filetype action data @return */ void e2_filetype_actions_register (void) { gchar *action_name = g_strconcat(_A(2),".",_C(15),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_filetype_config, NULL, FALSE); } /** @brief install default tree options for filetypes This function is called only if the default is missing from the config file @param set pointer to set data @return */ static void _e2_filetype_tree_defaults (E2_OptionSet *set) { e2_option_tree_setup_defaults (set, g_strdup ("filetypes=<"), g_strconcat (_("directories"),"||",NULL), g_strconcat ("\t|",_C(13),"|",NULL), // _("extensions") g_strconcat ("\t\t|",_(""),"|",NULL), //_I( g_strconcat ("\t|",_C(6),"|",NULL), // _("commands") g_strconcat ("\t\t|",_("_Open"),"|cd %%f",NULL), //must be no quotes in path! g_strconcat ("\t\t|",_("O_pen in other"),"|",_A(5),".",_A(61),NULL), //< g_strconcat ("\t\t|",_("_Mount"),"|mount %p",NULL), //CHECKME quotes in path ok? g_strconcat ("\t\t|",_("_Unmount"),"|umount %p",NULL), g_strconcat (_("executables"),"||",NULL), g_strconcat ("\t|",_C(13),"|",NULL), g_strconcat ("\t\t|",_(""),"|",NULL), //_I( g_strconcat ("\t\t|",_(""),"|",NULL), //_I( //no extension g_strdup ("\t\t|sh|"), g_strconcat ("\t|",_C(6),"|",NULL), g_strconcat ("\t\t|",_("_Run"),"|%p",NULL), // g_strconcat ("\t\t|",_("Edit with _vi"),"|x vi",NULL), // g_strconcat ("\t\t|",_("Edit with _emacs"),"|x emacs",NULL), g_strconcat ("\t\t|",_("Edit _with.."),"|%{(editors)@",_("Editor command:"),"} %p",NULL), g_strconcat ("\t\t|",_("_Edit"),"|",_A(5),".",_A(39),NULL), //_("file.edit") //< g_strconcat (_("office documents"),"||",NULL), g_strconcat ("\t|",_C(13),"|",NULL), g_strdup ("\t\t|doc|"), g_strdup ("\t\t|odt|"), g_strdup ("\t\t|sxw|"), g_strconcat ("\t|",_C(6),"|",NULL), g_strconcat ("\t\t|",_("_Openoffice"),"|oowriter",NULL), g_strconcat (_("HTML documents"),"||",NULL), g_strconcat ("\t|",_C(13),"|",NULL), g_strdup ("\t\t|htm|"), g_strdup ("\t\t|html|"), g_strconcat ("\t|",_C(6),"|",NULL), g_strconcat ("\t\t|",_("_Firefox"),"|mozilla-firefox %$file://$%p",NULL), g_strconcat ("\t\t|",_("_Mozilla"),"|mozilla %$file://$%p",NULL), g_strconcat ("\t\t|",_("_Lynx"),"|x lynx",NULL), g_strconcat ("\t\t|",_("_Opera"),"|opera %p",NULL), g_strconcat (_("PDF documents"),"||",NULL), g_strconcat ("\t|",_C(13),"|",NULL), g_strdup ("\t\t|pdf|"), g_strconcat ("\t|",_C(6),"|",NULL), g_strdup ("\t\t|xpdf|xpdf"), g_strdup ("\t\t|acroread|acroread"), g_strconcat (_("postscript documents"),"||",NULL), g_strconcat ("\t|",_C(13),"|",NULL), g_strdup ("\t\t|ps|"), g_strconcat ("\t|",_C(6),"|",NULL), g_strdup ("\t\t|gv|gv"), g_strconcat (_("text documents"),"||",NULL), g_strconcat ("\t|",_C(13),"|",NULL), g_strdup ("\t\t|txt|"), g_strconcat ("\t|",_C(6),"|",NULL), //not loaded g_strconcat ("\t\t|",PLUGIN,":",_("View"),"|",_A(5),".",_("view"),NULL), //conformed name g_strconcat ("\t\t|",_("View"),"|",_A(5),".",_A(101),NULL), //_("file.view") //< g_strconcat ("\t\t|",_("_Edit"),"|",_A(5),".",_A(39),NULL), //_("file.edit") //< //this removed in favour of the default context menu item open-with // g_strconcat ("\t\t|",_("Edit _with.."),"|%{(editors)@",_("Editor command:"),"} %p",NULL), // g_strconcat ("\t\t|",_("Edit with vi"),"|x vi",NULL), g_strconcat (_("spreadsheets"),"||",NULL), g_strconcat ("\t|",_C(13),"|",NULL), g_strdup ("\t\t|xls|"), g_strdup ("\t\t|ods|"), g_strdup ("\t\t|sxc|"), g_strconcat ("\t|",_C(6),"|",NULL), g_strconcat ("\t\t|",_("_Openoffice"),"|oocalc",NULL), g_strconcat (_("audio files"),"||",NULL), g_strconcat ("\t|",_C(13),"|",NULL), g_strdup ("\t\t|mp3|"), g_strdup ("\t\t|m3u|"), g_strdup ("\t\t|wav|"), g_strdup ("\t\t|ogg|"), g_strdup ("\t\t|mp2|"), g_strconcat ("\t|",_C(6),"|",NULL), g_strdup ("\t\t|_Muine|muine"), g_strdup ("\t\t|_Xmms|xmms"), g_strdup ("\t\t|xmms _queue|xmms -e"), g_strconcat (_("image files"),"||",NULL), g_strconcat ("\t|",_C(13),"|",NULL), g_strdup ("\t\t|jpeg|"), g_strdup ("\t\t|jpg|"), g_strdup ("\t\t|gif|"), g_strdup ("\t\t|svg|"), g_strdup ("\t\t|png|"), g_strdup ("\t\t|xpm|"), // g_strdup ("\t\t|sxd|"), // g_strdup ("\t\t|sxi|"), // g_strdup ("\t\t|sxp|"), g_strconcat ("\t|",_C(6),"|",NULL), g_strdup ("\t\t|_gview|gview"), g_strdup ("\t\t|G_Qview|gqview"), // g_strdup ("\t\t|GImage_View|gimageview"), // g_strconcat ("\t\t|",_("_Oo impress"),"|ooimpress",NULL), // g_strconcat ("\t\t|",_("Oo _draw"),"|oodraw",NULL), g_strdup ("\t\t|gi_mp|gimp"), g_strdup ("\t\t|i_nkscape|inkscape"), g_strdup ("\t\t|_xv|xv"), g_strconcat (_("video files"),"||",NULL), g_strconcat ("\t|",_C(13),"|",NULL), g_strdup ("\t\t|avi|"), g_strdup ("\t\t|divx|"), g_strdup ("\t\t|mpg|"), g_strdup ("\t\t|mpeg|"), g_strdup ("\t\t|mov|"), g_strconcat ("\t|",_C(6),"|",NULL), g_strdup ("\t\t|_Totem|totem"), g_strdup ("\t\t|_mplayer|mplayer"), g_strconcat (_("plain tarballs"),"||",NULL), g_strconcat ("\t|",_C(13),"|",NULL), g_strdup ("\t\t|tar|"), g_strconcat ("\t|",_C(6),"|",NULL), g_strconcat ("\t\t|",_("Unpack"),"|tar -xf %f",NULL), g_strconcat ("\t\t|",_("Unpack in other pane"),"|",_A(13),".",_A(72),";x tar -C %D -xf %f;",_A(13),".",_A(71),NULL), //<*2 #ifdef E2_VFS g_strconcat ("\t\t|",_("_Open"),"|",_A(5),".",_A(100),NULL), g_strconcat ("\t\t|",_("O_pen in other"),"|",_A(5),".",_A(118),NULL), #else g_strconcat ("\t\t|",PLUGIN,":",_("Unpack"),"|",_A(5),".",_A(100),NULL), //conformed name #endif g_strconcat ("\t\t|",_("_List contents"),"|>tar -tf %f \\| less",NULL), g_strconcat (_("gzip tarballs"),"||",NULL), g_strconcat ("\t|",_C(13),"|",NULL), g_strdup ("\t\t|tar.gz|"), g_strdup ("\t\t|tgz|"), g_strconcat ("\t|",_C(6),"|",NULL), g_strconcat ("\t\t|",_("_Unpack"),"|tar -zxf %f",NULL), g_strconcat ("\t\t|",_("Unpack in _other pane"),"|",_A(13),".",_A(72),";tar -C %D -zxf %f;",_A(13),".",_A(71),NULL), //<*2 #ifdef E2_VFS g_strconcat ("\t\t|",_("_Open"),"|",_A(5),".",_A(100),NULL), g_strconcat ("\t\t|",_("O_pen in other"),"|",_A(5),".",_A(118),NULL), #else g_strconcat ("\t\t|",PLUGIN,":",_("Unpack"),"|",_A(5),".",_A(100),NULL), //conformed name #endif g_strconcat ("\t\t|",_("_List contents"),"|>tar -ztf %f \\| less",NULL), g_strconcat (_("bzip2 tarballs"),"||",NULL), g_strconcat ("\t|",_C(13),"|",NULL), g_strdup ("\t\t|tar.bz2|"), g_strdup ("\t\t|tbz2|"), g_strdup ("\t\t|tbz|"), g_strconcat ("\t|",_C(6),"|",NULL), g_strconcat ("\t\t|",_("_Unpack"),"|>bzip2 -d -c %f \\| tar -xf -",NULL), g_strconcat ("\t\t|",_("Unpack in _other pane"),"|",_A(13),".",_A(72),";>bzip2 -d -c %f \\| tar -C %D -xf -;",_A(13),".",_A(71),NULL), //<*2 #ifdef E2_VFS g_strconcat ("\t\t|",_("_Open"),"|",_A(5),".",_A(100),NULL), g_strconcat ("\t\t|",_("O_pen in other"),"|",_A(5),".",_A(118),NULL), #else g_strconcat ("\t\t|",PLUGIN,":",_("Unpack"),"|",_A(5),".",_A(100),NULL), //conformed name #endif g_strconcat ("\t\t|",_("_List contents"),"|>bzip2 -d -c %f \\| tar -tf - \\| less",NULL), g_strconcat (_("zip archives"),"||",NULL), g_strconcat ("\t|",_C(13),"|",NULL), g_strdup ("\t\t|zip|"), g_strconcat ("\t|",_C(6),"|",NULL), g_strconcat ("\t\t|",_("_Unzip"),"|unzip %f",NULL), //space after %F in next line is needed ? g_strconcat ("\t\t|",_("Unzip in _other pane"),"|",_A(10),".",_A(90),";unzip %F;",_A(10),".",_A(90),NULL), //<*2 #ifdef E2_VFS g_strconcat ("\t\t|",_("_Open"),"|",_A(5),".",_A(100),NULL), g_strconcat ("\t\t|",_("O_pen in other"),"|",_A(5),".",_A(118),NULL), #else g_strconcat ("\t\t|",PLUGIN,":",_("Unpack"),"|",_A(5),".",_A(100),NULL), //conformed name #endif g_strconcat ("\t\t|",_("_List contents"),"|>unzip -l %f \\| less",NULL), /* plugin functionality not tested yet, for these g_strconcat (_("arj archives"),"||",NULL), g_strconcat ("\t|",_C(13),"|",NULL), g_strdup ("\t\t|arj|"), g_strconcat ("\t|",_C(6),"|",NULL), #ifdef E2_VFS g_strconcat ("\t\t|",_("_Open")"|",_A(5),".",_A(100),NULL), g_strconcat ("\t\t|",_("O_pen in other"),"|",_A(5),".",_A(118),NULL), #else g_strconcat ("\t\t|",PLUGIN,":",_("Unpack"),"|",_A(5),".",_A(100),NULL), #endif g_strconcat (_("rar archives"),"||",NULL), g_strconcat ("\t|",_C(13),"|",NULL), g_strdup ("\t\t|rar|"), g_strconcat ("\t|",_C(6),"|",NULL), #ifdef E2_VFS g_strconcat ("\t\t|",_("_Open")"|",_A(5),".",_A(100),NULL), g_strconcat ("\t\t|",_("O_pen in other"),"|",_A(5),".",_A(118),NULL), #else g_strconcat ("\t\t|",PLUGIN,":",_("Unpack"),"|",_A(5),".",_A(100),NULL), #endif */ g_strconcat (_("RPM packages"),"||",NULL), g_strconcat ("\t|",_C(13),"|",NULL), g_strdup ("\t\t|rpm|"), g_strconcat ("\t|",_C(6),"|",NULL), g_strconcat ("\t\t|",_("In_formation"),"|rpm -qlip %f",NULL), g_strconcat ("\t\t|",_("_Install"),"|su rpm -Uvh %f",NULL), #ifdef E2_VFS g_strconcat ("\t\t|",_("_Open"),"|",_A(5),".",_A(100),NULL), g_strconcat ("\t\t|",_("O_pen in other"),"|",_A(5),".",_A(118),NULL), #endif g_strconcat (_("source code files"),"||",NULL), g_strconcat ("\t|",_C(13),"|",NULL), g_strdup ("\t\t|c|"), g_strdup ("\t\t|cpp|"), g_strdup ("\t\t|h|"), g_strdup ("\t\t|pl|"), g_strdup ("\t\t|java|"), g_strconcat ("\t|",_C(6),"|",NULL), g_strconcat ("\t\t|",_("Edit _with.."),"|%{(editors)@",_("Editor command:"),"} %p",NULL), // g_strconcat ("\t\t|",_("Edit with _vi"),"|x vi",NULL), // g_strconcat ("\t\t|",_("Edit with _emacs"),"|x emacs",NULL), g_strconcat ("\t\t|",_("_Edit"),"|",_A(5),".",_A(39),NULL), //_("file.edit") //< g_strconcat (_("object files"),"||",NULL), g_strconcat ("\t|",_C(13),"|",NULL), g_strdup ("\t\t|o|"), g_strdup ("\t\t|so|"), g_strdup ("\t\t|a|"), g_strconcat ("\t|",_C(6),"|",NULL), g_strconcat ("\t\t|",_("_View symbols"),"|>nm %f \\| less",NULL), g_strdup (">"), NULL); } /** @brief register filetype config data Ths sets up for the corresponding page in the config dialog and prepares the default tree of filetype config data The format of the data needs to conform with the 3-level interrogation scheme used in e2_filetype_add_all(). @return */ void e2_filetype_options_register (void) { //no screen rebuilds needed after any change to this option gchar *group_name = _C(15); //_("filetypes") E2_OptionSet *set = e2_option_tree_register ("filetypes", group_name, group_name, NULL, NULL, NULL, E2_OPTION_TREE_UP_DOWN | E2_OPTION_TREE_ADD_DEL, E2_OPTION_FLAG_ADVANCED #ifdef E2_RAINBOW | E2_OPTION_FLAG_BUILDPANES //may be a color-data change #endif | E2_OPTION_FLAG_BUILDFILES); e2_option_tree_add_column (set, _("Category"), E2_OPTION_TREE_TYPE_STR, 0, "", 0, NULL, NULL); //this column used for headings, extensions and command labels e2_option_tree_add_column (set, "", E2_OPTION_TREE_TYPE_STR, 0, "", 0, NULL, NULL); e2_option_tree_add_column (set, _("Command"), E2_OPTION_TREE_TYPE_SEL, 0, "", 0, NULL, GINT_TO_POINTER (E2_ACTION_EXCLUDE_LAYOUT | E2_ACTION_INCLUDE_FILES)); e2_option_tree_create_store (set); e2_option_tree_prepare_defaults (set, _e2_filetype_tree_defaults); group_name = g_strconcat(_C(15) ,".",_C(26),":",_C(25),NULL); //_("filetypes.options:miscellaneous" //FREEME group_name not here ! e2_option_bool_register ("anycase-filetypes", group_name, _("case-insensitive filetypes"), _("This causes text-case to always be ignored when matching a file-extension"), NULL, FALSE, E2_OPTION_FLAG_BASIC | E2_OPTION_FLAG_FREEGROUP ); //no rebuild } emelfm2-0.4.1/src/e2_pane.h0000600000175000017500000000623611010340377014263 0ustar cairocairo/* $Id: e2_pane.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_PANE_H__ #define __E2_PANE_H__ #include "emelfm2.h" #include "e2_toolbar.h" //#include "e2_option.h" in toolbar.h //E2PANENATIVE for using native CWD regardless whether panes 1 & 2 are mounted or virtual enum { E2PANECUR, E2PANE1, E2PANE2, E2PANENATIVE, E2PANECONF } ; typedef struct _E2_PaneRuntime { gchar *name; //"pane1" or "pane2", not translated, for constructing set names GtkWidget *outer_box; GtkWidget *inner_box; GtkWidget *pane_sw; // GtkWidget *focus_widget; #ifdef E2_FAM gint FAMreq; //FAM request id used for this pane #endif ViewInfo *view; gchar *path; //utf-8 string, generally = view->dir, but on heap and not length-constrained GList *opendirs; /*list of paths of dirs previously opened (utf-8, absolute for current namespace, with trailer). Used for "goto" buttons menu. List was cached, and then, data items were heaped copies of path strings. Now not cached, and shares data with pane->view->dir_history. Unlike the latter, this list may have multiple entries for the same place */ guint opendir_cur; //0-based index of current position in opendirs list E2_ToolbarRuntime toolbar; E2_OptionSet *opt_transparent; //for quick checks whether to interpret relative path strings GHookList hook_change_dir; //data for functions to run at end of change-dir function, for the pane } E2_PaneRuntime; gboolean e2_pane_goto_choice (E2_PaneRuntime *rt, GtkWidget *entry); gboolean e2_pane_cd_checks (gchar *path); gboolean e2_pane_activate_other_action (gpointer from, E2_ActionRuntime *art); gboolean e2_pane_change_dir_action (gpointer from, E2_ActionRuntime *art); #ifdef E2_VFS gboolean e2_pane_change_space (E2_PaneRuntime *rt, VPATH *utfpath); gboolean e2_pane_change_space_byuri (E2_PaneRuntime *rt, gchar *spacedescriptor); #endif void e2_pane_activate_other (void); void e2_pane_change_dir (E2_PaneRuntime *rt, gchar *path); //void e2_pane_change_dir_watch (E2_PaneRuntime *rt, gchar *path, E2_CDType *completed_flag); void e2_pane_create (E2_PaneRuntime *rt); void e2_pane_create_part (E2_PaneRuntime *rt); void e2_pane_create_option_data (E2_PaneRuntime *rt); //void e2_pane_destroy (E2_PaneRuntime *rt); //void e2_pane_recreate (E2_PaneRuntime *rt); // initialisation things void e2_pane_flag_active (void); //void e2_pane_flag_inactive (void); void e2_pane_actions_register (void); void e2_pane_options_register (gint num); #endif //ndef __E2_PANE_H__ emelfm2-0.4.1/src/e2_output.c0000600000175000017500000024220211002355535014671 0ustar cairocairo/* $Id: e2_output.c 854 2008-04-19 11:45:33Z tpgww $ Copyright (C) 2003-2008 tooar Portions copyright (C) 2004 Florian Zähringer Portions copyright (C) 1999 Michael Clark. This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/e2_output.c @brief output pane creation and action functions includes actions related to output contents, but not to the output visibility */ /** \page output the output pane ToDo - descibe how this works \section tabs output pane tabs ToDo */ #include "emelfm2.h" #include #include #include "e2_output.h" #include "e2_dialog.h" //for pane-text activation #include "e2_task.h" #include "e2_filetype.h" #include "e2_context_menu.h" //for selection-save #include "e2_view_dialog.h" #define E2_PANED_TOLERANCE 10 #define VOL volatile #warning A lot of warnings about discarded qualifiers follow - ignore them //#define VOL static GtkWidget *_e2_output_create_view (E2_OutputTabRuntime *rt); static void _e2_output_tabchange_cb (GtkNotebook *notebook, GtkNotebookPage *page, guint page_num, gpointer data); #ifdef E2_TABS_DETACH static void _e2_output_tabgone_cb (GtkNotebook *notebook, GtkWidget *child, guint page_num, GtkWidget *window); #endif //static gboolean output_activated = FALSE; static GStaticRecMutex print_mutex; extern pthread_mutex_t task_mutex; #ifdef E2_TABS_DETACH #define TARGET_NOTEBOOK_TAB 3 static GtkTargetEntry target_table2[] = { { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, TARGET_NOTEBOOK_TAB }, }; static guint n_targets2 = sizeof(target_table2) / sizeof(GtkTargetEntry); #endif /*****************/ /***** utils *****/ /*****************/ /** @brief determine whether a position in the output pane text buffer is presently visible @param text_view the output pane textview widget @param iter pointer to data for the position in @a text_view that is to be checked @return TRUE if @a iter relates to a point in @a text_view that is NOT presently visible */ static inline gboolean _e2_output_iter_offscreen (GtkTextView *text_view, VOLATILE GtkTextIter *iter) { GdkRectangle visible_rect, iter_rect; gtk_text_view_get_visible_rect (text_view, &visible_rect); gtk_text_view_get_iter_location (text_view, iter, &iter_rect); return //this makes the pane scroll 1 line too many, but is faster ((iter_rect.y + iter_rect.height) > (visible_rect.y + visible_rect.height) //this causes jiggling, due to trailing blank line (\n at line-ends) //being re-scrolled off-screeen-bottom // ((iter_rect.y > (visible_rect.y + visible_rect.height) || iter_rect.y < visible_rect.y); } /** @brief move the 'page' displayed in the output pane, relative to its text content @param down TRUE to move down, FALSE to move up @param arg string containing a number, the no. of 'moves' @param page TRUE move @a arg 'pages', FALSE move @a arg 'steps' @return */ static void _e2_output_scroll_helper (gboolean down, gchar *arg, gboolean page) { printd (DEBUG, "scroll_helper (down:%d,arg:%s,page:%d)", down, arg, page); E2_OutputTabRuntime *rt = &app.tab; g_return_if_fail (rt->scroll != NULL); gchar *end = NULL; gdouble times = 1.0; if (arg != NULL) times = g_ascii_strtod (arg, &end); if (end == arg) times = 1.0; GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (rt->scroll)); gdouble value = gtk_adjustment_get_value (vadj); gint inc = page ? vadj->page_increment : vadj->step_increment; if (down) { value += (inc * times); if (value > (vadj->upper - vadj->page_size)) value = vadj->upper - vadj->page_size; } else value -= (inc * times); gtk_adjustment_set_value (vadj, value); } /** @brief set a mark at the position of the bottom-left of the output pane This supports e2_output_scroll_to_bottom() @param rt pointer to tab data @return */ void e2_output_get_bottom_iter (E2_OutputTabRuntime *rt) { g_return_if_fail (rt->scroll != NULL); GdkRectangle visible_rect; gtk_text_view_get_visible_rect (rt->text, &visible_rect); GtkTextIter iter; gtk_text_view_get_iter_at_location (rt->text, &iter, visible_rect.x, visible_rect.y + visible_rect.height - 1); GtkTextMark *mark = gtk_text_buffer_get_mark (rt->buffer, "bottom_scroll"); if (mark != NULL) gtk_text_buffer_move_mark (rt->buffer, mark, &iter); else gtk_text_buffer_create_mark (rt->buffer, "bottom_scroll", &iter, TRUE); } /** @brief idle callback to move the 'page' displayed in the output pane @param rt pointer to tab data @return FALSE to remove the source */ static gboolean _e2_output_do_scroll (E2_OutputTabRuntime *rt) { if (rt->scroll != NULL) { GtkTextMark *mark = gtk_text_buffer_get_mark (rt->buffer, "bottom_scroll"); if (mark != NULL) { gdk_threads_enter (); gtk_text_view_scroll_to_mark (rt->text, mark, 0.0, TRUE, 0.0, 1.0); gdk_threads_leave (); } } return FALSE; } /** @brief move the 'page' displayed in the output pane, to show a point at the bottom @param rt pointer to tab data @return */ void e2_output_scroll_to_bottom (E2_OutputTabRuntime *rt) { //this won't work if a higher priority is applied g_idle_add ((GSourceFunc) _e2_output_do_scroll, rt); } /** @brief set popup menu position This function is supplied when calling gtk_menu_popup(), to position the displayed menu. set @a push_in to TRUE for menu completely inside the screen, FALSE for menu clamped to screen size @param menu the GtkMenu to be positioned @param x place to store gint representing the menu left @param y place to store gint representing the menu top @param push_in place to store pushin flag @param textview output pane widget in focus when the menu key was pressed @return */ void e2_output_set_menu_position (GtkWidget *menu, gint *x, gint *y, gboolean *push_in, GtkWidget *textview) { gint left, top; gtk_window_get_position (GTK_WINDOW (app.main_window), &left, &top); GtkAllocation alloc = textview->allocation; *x = left + alloc.x + alloc.width/2; *y = top + alloc.y + alloc.height/2; *push_in = FALSE; } #ifdef E2_TABS_DETACH /** @brief clear text buffer of notebook tab associated with @a rt This assumes BGL is closed @param rt tab runtime data struct @return */ static void _e2_output_clear_buffer (E2_OutputTabRuntime *rt) { // gdk_threads_enter (); //order of things here is to minimise risk when clearing during an ongoing print operation GtkTextTagTable *table = gtk_text_buffer_get_tag_table (rt->buffer); GtkTextBuffer *buffer = gtk_text_buffer_new (table); GtkTextIter start; gtk_text_buffer_get_start_iter (buffer, &start); GtkTextMark *mark = gtk_text_buffer_create_mark (buffer, "internal-end-mark", &start, FALSE); WAIT_FOR_EVENTS gtk_text_view_set_buffer (rt->text, buffer); // gdk_threads_leave (); rt->buffer = buffer; rt->mark = mark; rt->onscreen = TRUE; g_object_unref (G_OBJECT (buffer)); } /** @brief set or clear detached-related data for tab whose widget is @a child @param child the notebook tab widget @param dest_notebook the notebook into which a moved tab will go, NULL = app.outbook @return */ static void _e2_output_tab_set_detached_state (GtkWidget *child, GtkWidget *dest_notebook) { gboolean attaching; GtkWidget *window; attaching = (dest_notebook == NULL || dest_notebook == app.outbook); if (attaching) //moving to main notebook window = NULL; else //moving from main notebook or some other drop window { window = gtk_widget_get_toplevel (dest_notebook); if (!GTK_WIDGET_TOPLEVEL (window)) { //FIXME printd (DEBUG, "can't find top window for moved tab"); return; } } if (child == app.tab.scroll) { app.tab.detached = !attaching; app.tab.dropwindow = window; } GList *member; for (member = app.tabslist; member != NULL; member = member->next) { if (((E2_OutputTabRuntime *)member->data)->scroll == child) { ((E2_OutputTabRuntime *)member->data)->detached = !attaching; printd (DEBUG, "detached-flag of tab %d set %s", ((E2_OutputTabRuntime *)member->data)->labelnum, (attaching) ? "FALSE":"TRUE"); ((E2_OutputTabRuntime *)member->data)->dropwindow = window; break; } } } /** @brief move tab whose widget is @a child from @a newbook to @a oldbook @param child tab widget @param frombook notebook from which the tab will be removed @param tobook notebook to which the tab will be appended @return */ static void _e2_output_tab_move (GtkWidget *child, GtkNotebook *frombook, GtkNotebook *tobook) { GtkWidget *tab_label, *menu_label; gboolean tab_expand, tab_fill; //, reorderable, detachable; guint tab_pack; //this is essentially the same process that gtk uses g_object_ref (G_OBJECT (child)); tab_label = gtk_notebook_get_tab_label (frombook, child); if (tab_label) g_object_ref (G_OBJECT (tab_label)); menu_label = gtk_notebook_get_menu_label (frombook, child); if (menu_label) g_object_ref (G_OBJECT (menu_label)); gtk_container_child_get (GTK_CONTAINER (frombook), child, "tab-expand", &tab_expand, "tab-fill", &tab_fill, "tab-pack", &tab_pack, // "reorderable", &reorderable, // "detachable", &detachable, NULL); gtk_container_remove (GTK_CONTAINER (frombook), child); gtk_notebook_append_page_menu (tobook, child, tab_label, menu_label); gtk_container_child_set (GTK_CONTAINER (tobook), child, "tab-pack", tab_pack, "tab-expand", tab_expand, "tab-fill", tab_fill, "reorderable", TRUE, //reorderable, "detachable", TRUE, //detachable, NULL); g_object_unref (G_OBJECT (child)); if (tab_label) g_object_unref (G_OBJECT (tab_label)); if (menu_label) g_object_unref (G_OBJECT (menu_label)); if (child == app.tab.scroll) { //this is the default tab gtk_notebook_set_current_page (tobook, -1); } //update detached-data for the tab _e2_output_tab_set_detached_state (child, GTK_WIDGET (tobook)); } /** @brief process a tab being dragged This is called for second and later drops onto a tabdrop window and for all drops back onto the output-pane main notebook Page-widget properties are not changed when the page is dragged @param dest_notebook the notebook to which a tab is being dragged @param context drag context data @param x X coordinate where the drop happens @param y Y coordinate where the drop happens @param sel_data the received data @param info the info registered for the target in target_table2 @param time timestamp at which the data was received @param user_data UNUSED data specified when the callback was connected @return */ static void _e2_output_tabdrag_data_received_cb ( GtkWidget *dest_notebook, GdkDragContext *context, gint x, gint y, GtkSelectionData *sel_data, guint info, guint time, gpointer user_data) { printd (DEBUG, "_e2_output_tab_drag_data_received_cb"); gboolean success; if (sel_data->length > 0) //&& sel_data->format == 8) { GtkWidget *source_notebook = gtk_drag_get_source_widget (context); GtkWidget *child = *(GtkWidget **)sel_data->data; if (source_notebook == dest_notebook) { // printd (DEBUG, "trying to drag to same place"); success = FALSE; } else if (source_notebook == app.outbook && gtk_notebook_get_n_pages (GTK_NOTEBOOK (source_notebook)) == 1) { //prevent dragging the only output-pane tab // printd (DEBUG, "trying to drag last output tab"); success = FALSE; } else { success = TRUE; _e2_output_tab_set_detached_state (child, dest_notebook); //prevent changes of default tab, by block here //(WAS unblock in tab-removed cb, but that spits error - seems //that gtk unblocks this all by itself ! g_signal_handlers_block_by_func (G_OBJECT (source_notebook), _e2_output_tabchange_cb, NULL); } } else success = FALSE; if (!success) sel_data->target = GDK_NONE; gtk_drag_finish (context, success, FALSE, time); } /** @brief cleanup when closing a tab-drop window This moves the tab(s) in the closing window back to the output-pane notebook @param window the window being closed @param event UNUSED event data struct @param data UNUSED data specified when callback was connected @return FALSE to allow event to propogate */ static gboolean _e2_output_tabrestore_cb (GtkWidget *window, GdkEvent *event, gpointer data) { printd (DEBUG, "_e2_output_tabrestore_cb ()"); gint j; GtkNotebook *newbook = GTK_NOTEBOOK (gtk_bin_get_child (GTK_BIN (window))); if (newbook != NULL && (j = gtk_notebook_get_n_pages (newbook)) > 0) { //prevent sequential tab-changes during the cleanout process g_signal_handlers_disconnect_by_func (G_OBJECT (newbook), _e2_output_tabchange_cb, NULL); //no need to go back and check for empty book g_signal_handlers_disconnect_by_func (G_OBJECT (newbook), _e2_output_tabgone_cb, window); GtkWidget *defchild = app.tab.scroll; //remember which page is default now gint i; for (i = 0; i < j; i++) { GtkWidget *child = gtk_notebook_get_nth_page (newbook, 0); _e2_output_tab_move (child, newbook, GTK_NOTEBOOK (app.outbook)); } //reset the default page in the destination book gint indx = gtk_notebook_page_num (GTK_NOTEBOOK (app.outbook), defchild); if (indx != -1) { g_signal_handlers_block_by_func (G_OBJECT (app.outbook), _e2_output_tabchange_cb, NULL); gtk_notebook_set_current_page (GTK_NOTEBOOK (app.outbook), indx); g_signal_handlers_unblock_by_func (G_OBJECT (app.outbook), _e2_output_tabchange_cb, NULL); } } return FALSE; } /** @brief cleanup when a tab-drop window becomes empty This is a callback for the "page-removed" signal on @a notebook @param notebook the affected notebook @param child UNUSED the widget for the removed page @param page_num UNUSED the child page number @param window the parent window for @a notebook @return */ static void _e2_output_tabgone_cb (GtkNotebook *notebook, GtkWidget *child, guint page_num, GtkWidget *window) { printd (DEBUG, "_e2_output_tabgone_cb"); if (notebook != GTK_NOTEBOOK (app.outbook) && gtk_notebook_get_n_pages (notebook) == 0) gtk_widget_destroy (window); /* this generates warning - seems as if gtk already unblocked the thing else //revert the block added when the drag was in progress g_signal_handlers_unblock_by_func (G_OBJECT (notebook), _e2_output_tabchange_cb, NULL); */ } /** @brief when a mouse-button is pressed on tab "label", update current-page data if needed This is a callback for the "grab-focus" signal on @a notebook It's called after a tab-change, if any @param notebook the affected notebook @param user_data UNUSED data specified when the callback was connected @return */ static void _e2_output_grab_focus_cb (GtkWidget *notebook, gpointer user_data) { printd (DEBUG, "_e2_output_grab_focus_cb"); gint curindx = gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook)); GtkWidget *child = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook), curindx); GList *member; for (member = app.tabslist; member != NULL; member = member->next) { E2_OutputTabRuntime *tab; tab = (E2_OutputTabRuntime *)member->data; if (tab->scroll == child) { if (tab != curr_tab) { //swap with mimimum race-risk ... *curr_tab = app.tab; //backup current tab's data from stack to heap app.tab = *tab; //get the replacement stuff into stackspace curr_tab = tab; //adjust all relevant child foreground-tab pointers to/from the stacked tab data e2_command_retab_children (&app.tab, curr_tab); printd (DEBUG, "output tab change, new current-tab ID is %d", tab->labelnum); } break; } } } //for "focus-in-event" NO USE /*static gboolean _e2_output_focus_in_cb (GtkWidget *widget, GdkEventFocus *event, gpointer user_data) { printd (DEBUG, "_e2_output_focus_in_cb"); return FALSE; } void _e2_output_page_add_cb (GtkNotebook *notebook, GtkWidget *child, guint page_num, gpointer user_data) { printd (DEBUG, "_e2_output_page_add_cb"); } */ /** @brief when a detached notebook tab is dropped in an empty area, create a window containing a notebook to receive the tab Page-widget properties are not changed when dragged @param source the source GtkNotebook of the drag operation @param sw the child GtkWidget to be dropped @param x the X coordinate where the drop happens @param y the Y coordinate where the drop happens @param data data specified when the arrangement was set up @return the created GtkNotebook where the tab will be attached, or NULL to cancel the drag */ static GtkNotebook *_e2_output_tab_drop_new (GtkNotebook *source, GtkWidget *sw, gint x, gint y, gpointer data) { printd (DEBUG, "_e2_output_tab_drop_new ()"); printd (DEBUG, "name of current tab is %d", curr_tab->labelnum); if (gtk_notebook_get_n_pages (source) == 1) return NULL; //prevent dragging of the only tab GtkWidget *window = gtk_window_new (GTK_WINDOW_TOPLEVEL); e2_window_set_title (window, _("output tabs")); gtk_window_set_role (GTK_WINDOW (window), "tabdrop"); // gtk_window_set_wmclass (GTK_WINDOW (window), "main", BINNAME); #ifdef E2_COMPOSIT e2_window_set_opacity (window, -1); #endif gtk_window_set_position (GTK_WINDOW (window), GTK_WIN_POS_MOUSE); //restore dropped tab(s) when new window is closed g_signal_connect (G_OBJECT (window), "delete-event", G_CALLBACK (_e2_output_tabrestore_cb), NULL); GtkWidget *notebook = gtk_notebook_new (); printd (DEBUG, "new notebook widget = %x", notebook); gtk_container_add (GTK_CONTAINER (window), notebook); //CRASHER gtk_drag_source_set (notebook, GDK_BUTTON1_MASK, target_table2, n_targets2, // GDK_ACTION_PRIVATE); //setup to process further tabs being dragged here gtk_drag_dest_set (notebook, GTK_DEST_DEFAULT_DROP, target_table2, n_targets2, GDK_ACTION_MOVE); g_signal_connect (G_OBJECT (notebook), "drag-data-received", G_CALLBACK (_e2_output_tabdrag_data_received_cb), NULL); //CHECKME user_data //close window when all tabs dragged out or re-attached by menu-selection g_signal_connect (G_OBJECT (notebook), "page-removed", G_CALLBACK (_e2_output_tabgone_cb), window); //capture clicks on tab labels g_signal_connect (G_OBJECT (notebook), "grab-focus", G_CALLBACK (_e2_output_grab_focus_cb), NULL); // g_signal_connect (G_OBJECT (notebook), "focus-in-event", // G_CALLBACK (_e2_output_focus_in_cb), NULL); // g_signal_connect (G_OBJECT (notebook), "page-added", // G_CALLBACK (_e2_output_page_add_cb), NULL); g_signal_connect (G_OBJECT (notebook), "switch-page", G_CALLBACK (_e2_output_tabchange_cb), NULL); //no data #ifdef USE_GTK2_12DND //allow further dragging from this window //(this seems to be un-necessary here, though that may be a gtk deficiency) g_signal_connect (G_OBJECT (notebook), "create-window", G_CALLBACK (_e2_output_tab_drop_new), NULL); //CHECKME user_data #endif GtkNotebook *book = GTK_NOTEBOOK (notebook); gtk_notebook_popup_enable (book); gtk_notebook_set_scrollable (book, TRUE); gtk_notebook_set_show_border (book, FALSE); gtk_notebook_set_show_tabs (book, TRUE); gtk_notebook_set_tab_pos (book, GTK_POS_LEFT); // these 2 needed ? // gtk_notebook_set_tab_reorderable (book, sw, TRUE); // gtk_notebook_set_tab_detachable (book, sw, TRUE); #ifdef USE_GTK2_12DND gpointer group = gtk_notebook_get_group (source); gtk_notebook_set_group (book, group); #else gint group_id = gtk_notebook_get_group_id (source); gtk_notebook_set_group_id (book, group_id); #endif gtk_window_set_default_size (GTK_WINDOW (window), app.main_window->allocation.width, app.tab.scroll->allocation.height); gtk_widget_show_all (window); //dragging has made the dragged tab "current", so we can set data for that app.tab.dropwindow = window; //set flag for tab context-menu so it shows only items relevant to separate window app.tab.detached = TRUE; printd (DEBUG, "detached-flag of tab %d set TRUE", app.tab.labelnum); //gtk will change the active tab in the source notebook and then the active //page in the dest notebook - any way to prevent the former of these ? return book; } #endif //def E2_TABS_DETACH /** @brief execute action corresponding to item selected from filetype tasks menu This is the callback for handling a selection of a filetype action from the context menu @param widget the selected menu item widget @return */ static void _e2_output_choose_filetype_action_cb (GtkWidget *menu_item) { gpointer *command = g_object_get_data (G_OBJECT (menu_item), "e2-file-operation"); #ifdef E2_COMMANDQ e2_command_run ((gchar *) command, E2_COMMAND_RANGE_DEFAULT, FALSE); #else e2_command_run ((gchar *) command, E2_COMMAND_RANGE_DEFAULT); #endif } /** @brief populate @a menu with items for the actions for a filetype Each member of @a actions is like "command" or "label@command" @param text the path of the item to open, utf8 string @param menu the menu widget to which the action menu-items are to be added @param actions NULL-terminated array of utf8 strings, each a command for a filetype @return */ static void _e2_output_menu_create_filetype_actions_menu (gchar *text, GtkWidget *menu, const gchar **actions) { gchar *sep, *fullcmd; GtkWidget *menu_item; while (*actions != NULL) { if ((sep = strchr (*actions, '@')) != NULL) //if always ascii @, don't need g_utf8_strchr() { *sep = '\0'; menu_item = e2_menu_add (menu, (gchar *)*actions, NULL, NULL, _e2_output_choose_filetype_action_cb, NULL); fullcmd = e2_utils_replace_name (sep+1, text); if (fullcmd == NULL) //no replaced macro in command fullcmd = g_strdup_printf ("%s \"%s\"", sep+1, text); *sep = '@'; //revert to original form (this is the 'source' data) } else { menu_item = e2_menu_add (menu, (gchar *)*actions, NULL, NULL, _e2_output_choose_filetype_action_cb, NULL); fullcmd = e2_utils_replace_name ((gchar *)*actions, text); if (fullcmd == NULL) //no replaced macro in command fullcmd = g_strdup_printf ("%s \"%s\"", *actions, text); } g_object_set_data_full (G_OBJECT (menu_item), "e2-file-operation", fullcmd, g_free); actions++; } } /** @brief create a filetypes sub-menu for @a menu @param text utf8 string describing the item to process @param menu menu widget to add to @return */ static void _e2_output_add_filetype_menu (gchar *text, GtkWidget *menu) { struct stat statbuf; E2_ERR_DECLARE gchar *local = F_FILENAME_TO_LOCALE (text); #ifdef E2_VFS # ifdef E2_VFSTMP get relevant spacedata, or allow only local (NULL) # endif VPATH sdata = {local , NULL}; if (e2_fs_stat (&sdata, &statbuf E2_ERR_PTR())) #else if (e2_fs_stat (local, &statbuf E2_ERR_PTR())) #endif { #ifdef E2_VFSTMP //FIXME handle error #endif E2_ERR_CLEAR F_FREE (local); return; } gboolean exec; const gchar **actions = NULL; gchar *ext, *ext2 = NULL; gchar *base = g_path_get_basename (text); #ifdef E2_VFS if (e2_fs_is_dir3 (&sdata E2_ERR_NONE())) #else if (e2_fs_is_dir3 (local E2_ERR_NONE())) #endif { exec = FALSE; //no special treatment of dirs ext = ext2 = g_strconcat (".", _(""), NULL); } else { #ifdef E2_VFS exec = !e2_fs_access (&sdata, X_OK E2_ERR_NONE()); #else exec = !e2_fs_access (local, X_OK E2_ERR_NONE()); #endif //assumes extension is that part of the name after the leftmost '.' ext = strchr (base, '.'); //assumes '.' is ascii if (ext == base //it's a dot file #ifdef E2_VFS || (ext == NULL && e2_fs_is_text (&sdata E2_ERR_NONE()))) //no extension #else || (ext == NULL && e2_fs_is_text (local E2_ERR_NONE()))) //no extension #endif //fake text extension //too bad if this is not a recognised text extension in filetypes ext = ".txt"; } if (ext != NULL) { //check all possible extensions for a matching filetype do { //skip leading dot "." ext++; //NCHR(ext); ascii '.'. always single char actions = e2_filetype_get_actions (ext); if (actions != NULL) { _e2_output_menu_create_filetype_actions_menu (text, menu, actions); break; } } while ((ext = strchr (ext, '.')) != NULL); //always ascii '.', don't need g_utf8_strchr() } if (exec) { //add exec-filetype items unless item has been found in that type already const gchar **acts2 = e2_filetype_get_actions (_("")); if (actions != NULL //was a matching extension && actions != acts2 && acts2 != NULL) //CHECKME this test _e2_output_menu_create_filetype_actions_menu (text, menu, acts2); else if (acts2 != NULL) _e2_output_menu_create_filetype_actions_menu (text, menu, acts2); //if appropriate, get desktop entry actions and add them too gchar *path, *localpath; gchar *dfile = e2_utils_strcat (base, ".desktop"); gchar *localname = F_FILENAME_TO_LOCALE (dfile); const gchar* const *sysdir = g_get_system_data_dirs (); while (*sysdir != NULL) { path = g_build_filename (*sysdir, "applications", localname, NULL); localpath = F_FILENAME_TO_LOCALE (path); //probably redundant #ifdef E2_VFS sdata.localpath = localpath; if (!e2_fs_access (&sdata, R_OK E2_ERR_NONE())) #else if (!e2_fs_access (localpath, R_OK E2_ERR_NONE())) #endif { e2_context_menu_create_desktop_actions_menu (menu, localpath); g_free (path); F_FREE (localpath); break; } g_free (path); F_FREE (localpath); sysdir++; } g_free (dfile); F_FREE (localname); } F_FREE (local); g_free (base); if (ext2 != NULL) g_free (ext2); } /** @brief check for or open a taskable item Expects BGL on/closed @param text utf8 string describing the item to open @param run TRUE to execute a matched command, FALSE to just return the match status @return TRUE if the item was processed (@a run = FALSE) or found (@a run = TRUE) */ static gboolean _e2_output_open_text (gchar *text, gboolean run) { gboolean retval; struct stat statbuf; gchar *local = F_FILENAME_TO_LOCALE (text); //make sure the text means something #ifdef E2_VFS # ifdef E2_VFSTMP assume text from local command run on local items # endif VPATH sdata = { local, NULL }; if (e2_fs_stat (&sdata, &statbuf E2_ERR_NONE())) #else if (e2_fs_stat (local, &statbuf E2_ERR_NONE())) #endif { retval = FALSE; if (run) { gchar *msg = g_strdup_printf (_("Cannot get information about %s"), text); e2_output_print_error (msg, TRUE); } } else if (run) { #ifdef E2_VFS retval = e2_task_backend_open (&sdata, FALSE); #else retval = e2_task_backend_open (local, FALSE); #endif } else { //just check for a known filetype #ifdef E2_VFS if (e2_fs_is_dir3 (&sdata E2_ERR_NONE()) //we can always handle dirs || !e2_fs_access (&sdata, X_OK E2_ERR_NONE())) //and executable items #else if (e2_fs_is_dir3 (local E2_ERR_NONE()) //we can always handle dirs || !e2_fs_access (local, X_OK E2_ERR_NONE())) //and executable items #endif retval = TRUE; else { gchar *base = g_path_get_basename (text); gchar *ext = strchr (base, '.'); //assumes '.' is ascii if (ext == NULL //no extension || ext == base) //hidden file { #ifdef E2_VFS retval = e2_fs_is_text (&sdata E2_ERR_NONE()); #else retval = e2_fs_is_text (local E2_ERR_NONE()); #endif } else { gchar *action; retval = FALSE; do { NCHR(ext); //skip the . prefix action = e2_filetype_get_default_action (ext); if (action != NULL) { retval = TRUE; g_free (action); break; } } while ((ext = strchr (ext, '.')) != NULL); //if always ascii '.', don't need g_utf8_strchr() } g_free (base); } } F_FREE (local); return retval; } /** @brief select and get text of a taskable item (if any) at event position @param x event x coordinate @param y event y coordinate @param rt pointer to data struct for output pane @return newly allocated string containing item which can be activated, or NULL */ static gchar *_e2_output_get_item_path (gint x, gint y, E2_OutputTabRuntime *rt) { GtkTextIter iter, start, end; gint buffer_x, buffer_y; gboolean quoted; gunichar c, d; gtk_text_buffer_get_bounds (rt->buffer, &start, &end); if (gtk_text_iter_equal (&start, &end)) return NULL; gtk_text_view_window_to_buffer_coords (rt->text, GTK_TEXT_WINDOW_TEXT, x, y, &buffer_x, &buffer_y); gtk_text_view_get_iter_at_location (rt->text, &iter, buffer_x, buffer_y); if (!gtk_text_buffer_get_selection_bounds (rt->buffer, &start, &end) || !gtk_text_iter_in_range (&iter, &start, &end)) { c = gtk_text_iter_get_char (&iter); if (g_unichar_isspace (c)) return NULL; //word separators include valid path chars, so need char iteration while (gtk_text_iter_backward_char (&iter)) { c = gtk_text_iter_get_char (&iter); if (g_unichar_isspace (c)) { gtk_text_iter_forward_char (&iter); break; } } c = gtk_text_iter_get_char (&iter); quoted = (c == (gunichar) '"' || c == (gunichar) '\''); if (quoted) { if (!gtk_text_iter_forward_char (&iter)) return NULL; } start = iter; while (gtk_text_iter_forward_word_end (&iter)) { d = gtk_text_iter_get_char (&iter); if (quoted && d == c) break; else if (g_unichar_isspace (d)) break; } end = iter; } gchar *text = gtk_text_iter_get_text (&start, &end); if (_e2_output_open_text (text, FALSE)) //, FALSE)) gtk_text_buffer_select_range (rt->buffer, &start, &end); else { g_free (text); text = NULL; } return text; } /*******************/ /***** actions *****/ /*******************/ /** @brief print to output pane, with default settings Any escaped newlines are converted to real ones. @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_output_print_action (gpointer from, E2_ActionRuntime *art) { gchar *real_message = e2_utils_str_replace ((gchar *)art->data , "\\n", "\n"); e2_output_print (&app.tab, real_message, NULL, FALSE, NULL); g_free (real_message); return TRUE; } /** @brief print help message to output pane This expects as data a string indicating which message is to be displayed @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if type of help was recognised */ static gboolean _e2_output_help_action (gpointer from, E2_ActionRuntime *art) { gchar *arg = (gchar *)art->data; if ((arg == NULL) || (*arg == '\0') || g_str_has_prefix (arg, _("commands"))) { e2_command_output_help (); return TRUE; } else if (g_str_has_prefix (arg, _("keys"))) //this must be translated same as in default aliases { e2_keybinding_output_help (arg); return TRUE; } return FALSE; } /** @brief move the displayed text window by @a arg 'steps' This expects as data a string containing the no. of 'steps' to move @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_output_scroll_step (gpointer from, E2_ActionRuntime *art) { //action data = TRUE to move down, FALSE to move up gboolean down = GPOINTER_TO_INT (art->action->data); _e2_output_scroll_helper (down, (gchar *)art->data, FALSE); return TRUE; } /** @brief move the displayed text window by @a arg 'pages' This expects as data a string containing the no. of 'pages' to move @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_output_scroll_page (gpointer from, E2_ActionRuntime *art) { //action data = TRUE to move down, FALSE to move up gboolean down = GPOINTER_TO_INT (art->action->data); _e2_output_scroll_helper (down, (gchar *)art->data, TRUE); return TRUE; } /** @brief move the displayed text window to start or end of its content @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_output_scroll_all (gpointer from, E2_ActionRuntime *art) { E2_OutputTabRuntime *rt = &app.tab; g_return_val_if_fail (rt->scroll != NULL, FALSE); GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (rt->scroll)); //action data = TRUE to move down, FALSE to move up gboolean down = GPOINTER_TO_INT (art->action->data); if (down) gtk_adjustment_set_value (vadj, vadj->upper - vadj->page_size); else gtk_adjustment_set_value (vadj, 0.0); return TRUE; } /** @brief move the displayed text window to show text printed by a child process @param item the selected item from a children-menu @param rt pointer to command data @return */ static void _e2_output_scroll_to_child (GtkWidget *item, E2_TaskRuntime *rt) { if (rt == NULL) //rt == NULL for a menu item "no children" return; pthread_mutex_lock (&task_mutex); GList *member = g_list_find (app.taskhistory, rt); pthread_mutex_unlock (&task_mutex); if (member != NULL) //command data still exists { //the buffer may be cleared while the menu is active, //so check again for matching content GtkTextMark *origin_mark = gtk_text_buffer_get_mark (app.tab.buffer, rt->pidstr); if (origin_mark != NULL) gtk_text_view_scroll_to_mark (app.tab.text, origin_mark, 0.0, TRUE, 0.0, 1.0); else { gchar *msg = g_strdup_printf (_("Cannot find any output from process %s"), rt->pidstr); e2_output_print_error (msg, TRUE); } } } /** @brief clear all content of an output-pane tab @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_output_clear_action (gpointer from, E2_ActionRuntime *art) { #ifdef E2_TABS_DETACH _e2_output_clear_buffer (&app.tab); #else // gdk_threads_enter (); //order of things here is to handle clearing when the text buffer is being added to GtkTextTagTable *table = gtk_text_buffer_get_tag_table (app.tab.buffer); GtkTextBuffer *buffer = gtk_text_buffer_new (table); GtkTextIter start; gtk_text_buffer_get_start_iter (buffer, &start); GtkTextMark *mark = gtk_text_buffer_create_mark (buffer, "internal-end-mark", &start, FALSE); WAIT_FOR_EVENTS gtk_text_view_set_buffer (app.tab.text, buffer); // gdk_threads_leave (); app.tab.buffer = buffer; app.tab.mark = mark; app.tab.onscreen = TRUE; g_object_unref (G_OBJECT (buffer)); #endif *(curr_tab) = app.tab; //listed tab data needs the updated values too return TRUE; } /** @brief create another output pane tab, and go there @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2_output_tab_add (gpointer from, E2_ActionRuntime *art) { E2_OutputTabRuntime *tab = ALLOCATE0 (E2_OutputTabRuntime); CHECKALLOCATEDWARN (tab, return FALSE;) GtkWidget *sw = _e2_output_create_view (tab); GtkWidget *wid; #ifdef USE_GTK2_10 //there may be gaps in the tab labels //and for gtk >= 2.10, tabs can be in any order or in another notebook, //so check all tabs to find the last one and bump its label gint lablid, new = 1; GList *member; for (member = app.tabslist; member != NULL; member = member->next) { lablid = ((E2_OutputTabRuntime *)member->data)->labelnum; if (lablid >= new) new = lablid + 1; } tab->labelnum = new; //remember, for later searching #else wid = gtk_notebook_get_nth_page (GTK_NOTEBOOK (app.outbook), app.tabcount-1); const gchar *labtxt = gtk_notebook_get_tab_label_text (GTK_NOTEBOOK (app.outbook), wid); gint new = atoi (labtxt) + 1; #endif gchar *txt = g_strdup_printf ("%d", new); //no translation wid = gtk_label_new (txt); g_free (txt); gtk_notebook_append_page (GTK_NOTEBOOK (app.outbook), sw, wid); #ifdef USE_GTK2_10 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (app.outbook), sw, TRUE); #ifdef E2_TABS_DETACH gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (app.outbook), sw, TRUE); #endif #endif app.tabslist = g_list_append (app.tabslist, tab); app.tabcount++; //log, for cache if (app.tabcount == 2) //doesn't matter where the tabs are gtk_notebook_set_show_tabs (GTK_NOTEBOOK (app.outbook), TRUE); //focus the new tab #ifdef E2_TABS_DETACH new = gtk_notebook_get_n_pages (GTK_NOTEBOOK (app.outbook)); gtk_notebook_set_current_page (GTK_NOTEBOOK (app.outbook), new-1); #else gtk_notebook_set_current_page (GTK_NOTEBOOK (app.outbook), app.tabcount - 1); //uses new last-tab index #endif return TRUE; } /** @brief remove currently focused output pane tab This may be called from a keybinding or context menu item Essentially, tab-specific data are swpped between stack and heap space, and pointers for affected running commands are adjusted accordingly @param from the button, menu item etc which was activated @param art action runtime data @return TRUE if the removal was done */ static gboolean _e2_output_tab_remove (gpointer from, E2_ActionRuntime *art) { #ifdef E2_TABS_DETACH //ignore removal of only tab in this notebook gint homecount = gtk_notebook_get_n_pages (GTK_NOTEBOOK (app.outbook)); if (homecount == 1) return FALSE; //cleanup the notebook //FIXME only if tab in this notebook (context menu cleanup to prevent this ?) gint index = gtk_notebook_get_current_page (GTK_NOTEBOOK (app.outbook)); GtkWidget *sw = gtk_notebook_get_nth_page (GTK_NOTEBOOK (app.outbook), index); GList *member; for (member = app.tabslist; member != NULL; member = member->next) { if (((E2_OutputTabRuntime *)member->data)->scroll == sw) break; } if (member == NULL) return FALSE; //should never happen gtk_notebook_remove_page (GTK_NOTEBOOK (app.outbook), index); if (--app.tabcount == 1) gtk_notebook_set_show_tabs (GTK_NOTEBOOK (app.outbook), FALSE); index = gtk_notebook_get_current_page (GTK_NOTEBOOK (app.outbook)); sw = gtk_notebook_get_nth_page (GTK_NOTEBOOK (app.outbook), index); GList *newmember; for (newmember = app.tabslist; newmember != NULL; newmember = newmember->next) { if (((E2_OutputTabRuntime *)newmember->data)->scroll == sw) break; } if (newmember == NULL) return FALSE; //should never happen /*//CHECKME does this conform to gtk behaviour ? GList *member2; if (member == app.tabslist) member2 = member->next; else member2 = member->prev; if (member2 != newmember) printd (DEBUG, "replacement tab not correct !!"); */ #else //ignore removal of only tab if (app.tabcount == 1) return FALSE; //cleanup the notebook gint index = gtk_notebook_get_current_page (GTK_NOTEBOOK (app.outbook)); gtk_notebook_remove_page (GTK_NOTEBOOK (app.outbook), index); if (--app.tabcount == 1) gtk_notebook_set_show_tabs (GTK_NOTEBOOK (app.outbook), FALSE); //CHECKME does this conform to gtk behaviour ? GList *member = g_list_nth (app.tabslist, index); GList *newmember; if (index == 0) newmember = member->next; else newmember = member->prev; #endif E2_OutputTabRuntime *newtab = newmember->data; // printd (DEBUG, "curr_tab is %x", curr_tab); // printd (DEBUG, "replacement tab at %x", newtab); //quickly install replacement data, ready for use by any printing children app.tab = *newtab; //anthing running in the focused tab stays there, //so no need to update foreground pointers //but do update background-tab ptrs of children using the tab e2_command_retab2_children (member->data, newtab); // != curr_tab? //now its ok to update the list pointer curr_tab = newtab; //clean the old tab's data FIXME any leak ? text buffer ? if (((E2_OutputTabRuntime *)member->data)->origin_lastime!= NULL) g_free (((E2_OutputTabRuntime *)member->data)->origin_lastime); // GHashTable *hash = ((E2_OutputTabRuntime *)member->data)->origins; // if (hash != NULL) // g_hash_table_destroy (hash); // printd (DEBUG, "removing tab at %x", member->data); DEALLOCATE (E2_OutputTabRuntime, member->data); app.tabslist = g_list_delete_link (app.tabslist, member); //counter adjusted above return TRUE; } /** @brief update gtk's flag which sets output pane text wrapping This is a hook fn @param pvalue pointerised version of the T/F value to be set @param rt pointer to data struct for output pane @return */ static void _e2_output_set_op_wrap (gpointer pvalue, E2_OutputTabRuntime *rt) { // printd (DEBUG, "_e2_output_set_op_wrap (pvalue:_,rt:_)"); gint value = GPOINTER_TO_INT (pvalue); gint cur = gtk_text_view_get_wrap_mode (rt->text); if (cur != value) gtk_text_view_set_wrap_mode (rt->text, value); } /**************/ /**** menu ****/ /**************/ /** @brief save selected output-pane text @param menuitem UNUSED the selected widget, or NULL @param rt runtime struct to work on @return */ /*static void _e2_output_savesel_cb (GtkWidget *menuitem, E2_OutputTabRuntime *rt) { e2_edit_dialog_save_selected (rt->buffer, #ifdef E2_VFS NULL, //local namespace assumed #endif app.main_window); } */ /** @brief edit output-pane-tab text @param menuitem UNUSED the selected widget, or NULL @param rt data struct for the tab @return */ static void _e2_output_edit_cb (GtkWidget *menuitem, E2_OutputTabRuntime *rt) { //editing an empty buffer will cause a freeze, so we fake some content... GtkTextIter start, end; gtk_text_buffer_get_bounds (rt->buffer, &start, &end); if (gtk_text_iter_equal (&start, &end)) e2_output_print (rt, " ", NULL, FALSE, NULL); e2_edit_dialog_create (NULL, rt->buffer); } #ifdef E2_TABS_DETACH static void _e2_output_tabattach_cb (GtkWidget *menuitem, E2_OutputTabRuntime *rt) { GtkNotebook *newbook = GTK_NOTEBOOK (gtk_bin_get_child (GTK_BIN (rt->dropwindow))); _e2_output_tab_move (rt->scroll, newbook, GTK_NOTEBOOK (app.outbook)); } static void _e2_output_clear_cb (GtkWidget *menuitem, E2_OutputTabRuntime *rt) { return _e2_output_clear_buffer (rt); } #endif /** @brief construct and show output-pane context menu @param textview the textview widget where the click happened @param event_button which mouse button was clicked (0 for a menu key) @param event_time time that the event happened (0 for a menu key) @param rt runtime struct to work on @return */ static void _e2_output_show_context_menu (GtkWidget *textview, guint event_button, gint event_time, E2_OutputTabRuntime *rt) { gchar *item_name; GtkWidget *item; GtkWidget *menu = gtk_menu_new (); #ifdef E2_TABS_DETACH if (!rt->detached) { #endif item_name = g_strconcat (_A(9),".",_A(27),NULL); e2_menu_add_action (menu, _("_Hide"), "output_hide_"E2IP".png", _("Do not show the output pane"), item_name, "1", NULL); //no string translation g_free (item_name); item_name = g_strconcat (_A(9),".",_A(27),NULL); e2_menu_add_action (menu, _("_Toggle full"), (app.window.output_paned_ratio > 0.01) ? GTK_STOCK_ZOOM_FIT : GTK_STOCK_ZOOM_OUT, _("Toggle output pane size to/from the full window size"), item_name, "*,0", NULL); //no string translation g_free (item_name); item_name = g_strconcat (_A(9),".",_A(26),NULL); e2_menu_add_action (menu, _("_New tab"), GTK_STOCK_ADD, _("Add another tab for the output pane"), item_name, NULL, NULL); g_free (item_name); item_name = g_strconcat (_A(9),".",_A(38),NULL); item = e2_menu_add_action (menu, _("_Remove tab"), GTK_STOCK_REMOVE, _("Close this this tab"), item_name, NULL, NULL); g_free (item_name); if (app.tabcount == 1) gtk_widget_set_sensitive (item, FALSE); #ifdef E2_TABS_DETACH } else //rt->detached { e2_menu_add (menu, _("_Attach"), NULL, _("Move this tab back to output pane"), _e2_output_tabattach_cb, rt); } #endif e2_menu_add_separator (menu); #ifdef E2_TABS_DETACH e2_menu_add (menu, _("_Clear"), GTK_STOCK_CLEAR, _("Clear this tab"), _e2_output_clear_cb, rt); #else item_name = g_strconcat (_A(9),".",_A(29),NULL); e2_menu_add_action (menu, _("_Clear"), GTK_STOCK_CLEAR, _("Clear this tab"), item_name, NULL, NULL); g_free (item_name); #endif /* e2_menu_add (menu, _("Save as.."), "save_selection_"E2IP".png", //no suitable mnemonic _("Save the selected text"), _e2_output_savesel_cb, rt); if (!gtk_text_buffer_get_selection_bounds (rt->buffer, NULL, NULL)) gtk_widget_set_sensitive (item, FALSE); */ e2_menu_add (menu, _("_Edit"), "edit_"E2IP".png", _("Edit the tab contents"), _e2_output_edit_cb, rt); item = e2_menu_add (menu, _("_Open"), GTK_STOCK_EXECUTE, NULL, NULL, NULL); gint x, y; gdk_window_get_pointer (textview->window, &x, &y, NULL); item_name = _e2_output_get_item_path (x, y, rt); if (item_name == NULL) gtk_widget_set_sensitive (item, FALSE); else { GtkWidget *submenu = gtk_menu_new (); gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu); gtk_widget_show (submenu); _e2_output_add_filetype_menu (item_name, submenu); g_free (item_name); //?? } /* item_name = g_strconcat (_A(9),".",_A(50),NULL); e2_menu_add_action (menu, _("C_ommand help"), GTK_STOCK_HELP, _("Show information about using the command line"), item_name, "", NULL); g_free (item_name); */ item = e2_menu_add (menu, _("Co_mmand output"), "ps_"E2IP".png", _("Show output from a completed command"), NULL, NULL); GtkWidget *submenu = e2_menu_create_child_menu (E2_CHILD_OUTPUT, _e2_output_scroll_to_child); if (submenu == NULL) gtk_widget_set_sensitive (item, FALSE); else gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu); #ifdef E2_TABS_DETACH if (!rt->detached) { #endif e2_menu_add_separator (menu); submenu = e2_menu_add_submenu (menu, _("_Settings"), GTK_STOCK_PREFERENCES); e2_menu_create_options_menu (GTK_WIDGET (rt->text), submenu, rt->opt_wrap, NULL, NULL, app.output.opt_show_on_new, NULL, NULL, app.output.opt_show_on_focus_in, NULL, NULL, app.output.opt_hide_on_focus_out, NULL, NULL, app.output.opt_jump, NULL, NULL, app.output.opt_jump_follow, NULL, NULL, app.output.opt_jump_end, NULL, NULL, NULL); item_name = g_strconcat (_A(2),".",_A(32),NULL); e2_menu_add_action (submenu, _("_Other"), NULL, _("Open the configuration dialog at the output options page"), item_name, _C(27), //_("output") NULL); g_free (item_name); #ifdef E2_TABS_DETACH } #endif g_signal_connect (G_OBJECT (menu), "selection-done", G_CALLBACK (e2_menu_destroy_cb), NULL); if (event_button == 0) gtk_menu_popup (GTK_MENU (menu), NULL, NULL, (GtkMenuPositionFunc) e2_output_set_menu_position, textview, 0, event_time); else //this was a button-3 click gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, event_button, event_time); } /*********************/ /***** callbacks *****/ /*********************/ /** @brief process an 'intercepted' double-click on the output pane This handles 'activated' text in the output pane. @param rt runtime struct for the pane @return TRUE if the press was handled */ static gboolean _e2_output_activated_cb (E2_OutputTabRuntime *rt) { printd (DEBUG, "output activated cb"); GtkTextIter start, end; if (gtk_text_buffer_get_selection_bounds (rt->buffer, &start, &end)) { gchar *seltext = gtk_text_buffer_get_text (rt->buffer, &start, &end, FALSE); printd (DEBUG, "output text is %s", seltext); _e2_output_open_text (seltext, TRUE); //, TRUE); g_free (seltext); //BAD - this locks text DnD on // output_activated = TRUE; //prevent normal button-release handling return TRUE; //block internal click handling } printd (DEBUG, "nothing selected in output"); // output_activated = FALSE; //allow normal button-release handling return FALSE; } /** @brief process mouse button-click in the output pane 1/L = focus, 2/M = hide, 3/R = context menu L = open This also detects left-button double-clicks (for which there is no API) and performs an open if relevant @param textview the widget where the click happened @param event gdk event data struct @param rt runtime struct to work on @return TRUE if the signal has been handled here */ static gboolean _e2_output_button_press_cb (GtkWidget *textview, GdkEventButton *event, E2_OutputTabRuntime *rt) { /* GtkTextWindowType wtype = gtk_text_view_get_window_type ( GTK_TEXT_VIEW (textview), event->window); if (wtype != GTK_TEXT_WINDOW_TEXT) return FALSE; */ printd (DEBUG, "output button press cb"); #ifdef E2_TABS_DETACH //just changing notebook page is not sufficient to set current tab when //there's > 1 notebook if (event->type == GDK_BUTTON_PRESS) { if (rt != curr_tab) { //swap with mimimum race-risk ... *curr_tab = app.tab; //backup current tab's data from stack to heap app.tab = *rt; //get the replacement stuff into stackspace curr_tab = rt; //adjust all relevant child foreground-tab pointers to/from the stacked tab data e2_command_retab_children (&app.tab, curr_tab); printd (DEBUG, "output tab change, new current-tab ID is %d", curr_tab->labelnum); } } #endif E2_OutputTabRuntime *rrt = (rt == curr_tab) ? &app.tab : rt; /*for double-clicks, the callback sequence is: 1, then another < click interval, then 1 more with 0 click interval for triple-clicks, the callback sequence is: 1, then 2 * last 2 of double click sequence = 5 total The 0-click interval clicks are of type GDK_2BUTTON_PRESS or GDK_3BUTTON_PRESS as appropriate */ if (event->button == 1 && event->type == GDK_BUTTON_PRESS) { static gboolean valid_selection = FALSE; static GtkTextIter start, end; extern guint click_interval; static guint32 last_event_time = 0; guint32 interval; if (event->state & GDK_CONTROL_MASK) { gchar *text = _e2_output_get_item_path (event->x, event->y, rrt); if (text != NULL) { _e2_output_open_text (text, TRUE); //, FALSE); g_free (text); return TRUE; } } interval = event->time - last_event_time; last_event_time = event->time; // printd (DEBUG, "output button press interval %d", interval); //we don't want to repeat the action, if there was a double/triple click if (interval >= click_interval) { // output_activated = FALSE; //reinstate normal release handling //because a selection will be cleared by thge normal release //callback, note what is selected in case it's a double-click valid_selection = gtk_text_buffer_get_selection_bounds (rrt->buffer, &start, &end); /* //CHECKME is this sensible ?? it does not focus the commandline gchar *action_name = g_strconcat (_A(1),".",_A(42),NULL); //_("command.focus") e2_action_run_simple (action_name, NULL); g_free (action_name); */ //focus the output tab if it's not going to be hidden if (!e2_option_bool_get_direct (app.output.opt_hide_on_focus_out) #ifdef E2_TABS_DETACH || rrt->detached #endif ) gtk_widget_grab_focus (textview); } else { //reselect what was de-selected by the intervening release callback, //ready for the next press callback (type GDK_2BUTTON_PRESS etc) if (valid_selection) { gtk_text_buffer_select_range (rrt->buffer, &start, &end); valid_selection = FALSE; } } } else if (event->button == 1 && // ( event->type == GDK_2BUTTON_PRESS //|| event->type == GDK_3BUTTON_PRESS) ) { printd (DEBUG, "output button press event type %d", event->type); if (_e2_output_get_item_path (event->x, event->y, rrt) != NULL) return (_e2_output_activated_cb (rrt)); //FIXME stop the selected text from being de-selected in the //button-release callback - but we can't just block that !! } else if (event->button == 2 #ifdef E2_TABS_DETACH && !rrt->detached #endif ) { e2_window_output_hide (NULL, NULL, NULL); return TRUE; } else if (event->button == 3) { _e2_output_show_context_menu (textview, 3, event->time, rrt); return TRUE; } return FALSE; } /** @brief process mouse button-release in the output pane This is essentially to re-select text which is de-selected by the standard release callback @param textview the widget where the release happened @param event gdk event data struct @param rt runtime struct to work on @return TRUE if the signal has been handled */ /*static gboolean _e2_output_button_release_cb (GtkWidget *textview, GdkEventButton *event, E2_OutputTabRuntime *rt) { printd (DEBUG, "output button release cb"); return FALSE; E2_OutputTabRuntime *rrt = (rt == curr_tab) ? &app.tab : rt; gtk_text_buffer_select_range (rrt->buffer, &start, &end); return TRUE; } */ /** @brief process menu button press in the output pane @param widget the textview widget where the press happened @param rt output runtime struct to work on @return TRUE if the signal has been handled */ static gboolean _e2_output_popup_menu_cb (GtkWidget *widget, E2_OutputTabRuntime *rt) { E2_OutputTabRuntime *rrt = (rt == curr_tab) ? &app.tab : rt; gint event_time = gtk_get_current_event_time (); _e2_output_show_context_menu (widget, 0, event_time, rrt); return TRUE; } /** @brief Determine whether the buffer end-mark is on-screen, after manual change to the vertical adjustment This is a callback for manual changes to the output textview vertical adjustment @param adjust UNUSED the vertical adjustment for the textview @param rt data struct for the output pane @return */ static void _e2_output_scrolled_cb (GtkAdjustment *adjust, E2_OutputTabRuntime *rt) { //use active-tab data when relevant E2_OutputTabRuntime *rrt = (rt == curr_tab) ? &app.tab : rt; //FIXME make this faster - local copy of the option value ? GtkTextIter iter; if (e2_option_bool_get_direct (app.output.opt_jump_end)) //scrolling only when new output belongs to the last context in the textbuffer gtk_text_buffer_get_end_iter (rrt->buffer, &iter); else gtk_text_buffer_get_iter_at_mark (rrt->buffer, &iter, rrt->mark); #ifdef DEBUG_MESSAGES if (_e2_output_iter_offscreen (rrt->text, &iter)) { if (rrt->onscreen) printd (NOTICE, "not following anymore"); rrt->onscreen = FALSE; } else { if (!rrt->onscreen) printd (NOTICE, "following again"); rrt->onscreen = TRUE; } #else rrt->onscreen = !_e2_output_iter_offscreen (rrt->text, &iter); #endif } /* static gboolean test_cb (GtkWidget *widget, GdkEventMotion *event) { int x, y; GdkModifierType state; if (event->is_hint) { gdk_window_get_pointer(event->window, &x, &y, &state); printf("x = %d y = %d\n", x, y); } else { printf("x: %lf y: %lf\n", event->x, event->y); } return FALSE; } */ /** @brief handle an output-pane tab change This is callback for the notebook's "switch-page" signal Essentially, tab-specific data are swpped between stack and heap space, and pointers for affected running commands are adjusted accordingly @param notebook the notebook widget @param page UNDOCUMENTED notebook page which is now focused @param page_num the 0-based index of the new page @param data UNUSED pointer to data specified when callback was connected @return */ static void _e2_output_tabchange_cb (GtkNotebook *notebook, GtkNotebookPage *page, guint page_num, gpointer data) { E2_OutputTabRuntime *newtab; #ifdef E2_TABS_DETACH GtkWidget *child = gtk_notebook_get_nth_page (notebook, page_num); GList *member; for (member = app.tabslist; member != NULL; member = member->next) { newtab = (E2_OutputTabRuntime *)member->data; if (newtab->scroll == child) { printd (DEBUG, "output tab change cb, new current-tab ID is %d", newtab->labelnum); break; } } if (member == NULL) return; //should never happen #else printd (DEBUG, "output tab change cb, new current-tab index %d", page_num); // printd (DEBUG, "curr_tab is %x", curr_tab); newtab = g_list_nth_data (app.tabslist, page_num); #endif //swap with mimimum race-risk ... *curr_tab = app.tab; //backup current tab's data from stack to heap app.tab = *newtab; //get the replacement stuff into stackspace curr_tab = newtab; // printd (DEBUG, "curr_tab NOW is %x", curr_tab); //adjust all relevant child foreground-tab pointers to/from the stacked tab data e2_command_retab_children (&app.tab, curr_tab); } /******************/ /***** public *****/ /******************/ /** @brief show end-of-output message in tab associated with @a tab @param tab pointer to tab runtime @param beep TRUE to sound when the message is printed @return */ void e2_output_print_end (E2_OutputTabRuntime *tab, gboolean beep) { e2_output_print (tab, _("-- end-of-output --"), NULL, TRUE, "small", "grey", NULL); if (beep) e2_utils_beep (); } /** @brief show error message @a msg in current output pane tab, with beep @param msg message @param freemsg TRUE to free @a msg after it has been shown @return */ //FIXME use the "correct" tab instead of always the current one void e2_output_print_error (gchar *msg, gboolean freemsg) { e2_output_print (&app.tab, msg, NULL, TRUE, E2_ERRORTAGS, NULL); if (freemsg) g_free (msg); e2_utils_beep (); } /** @brief show system error message in current output pane tab, with beep @return */ //FIXME use the "correct" tab instead of always the current one void e2_output_print_strerrno (void) { e2_output_print (&app.tab, (gchar *) g_strerror (errno), NULL, TRUE, E2_ERRORTAGS, NULL); e2_utils_beep (); } /** @brief show @a msg in output pane @a msg is converted to utf-8 if it's not that form already Provides special handling of any '\\b', '\\r' char(s) in @a msg for an error message, style parameters are ignored Expects BGL to be on/closed @param tab pointer to data structure for the tab to get the message @param msg actual message @param origin context to which the message belongs (eg a pid string) or NULL for default @param newline TRUE if @a msg is to be printed with pre- and post- newline @param first_tag one or more tags to apply, terminated by NULL @return */ void e2_output_print (E2_OutputTabRuntime *tab, gchar *msg, gchar *origin, //gboolean error, gboolean debug, gboolean newline, const gchar *first_tag, ...) { //early exit if (msg == NULL) return; // printd (DEBUG, "e2_ouput_print (msg:,origin:%s,error:%d,debug:%d,newline:%d,first_tag:)", // origin, error, debug, newline); //if this is not a debug message //show the output pane if the user wishes that if (//!debug && !app.output.visible && e2_option_bool_get_direct (app.output.opt_show_on_new)) { e2_window_output_show (NULL, NULL); } //parse the msg string to handle special characters VOL gint len = strlen (msg); //process leading backspaces //these delete characters from the last message of this context VOL gint back = 0; while ((msg[0]) == '\b') { back++; msg++; len--; } //FIXME: handle \r correctly //flag whether there's a carriage return '\r' in the message VOL gboolean line_back = FALSE; //flag for backspaces not at the beginning of the message VOL gboolean backspaces_left = FALSE; //flag whether message variable should be freed VOL gboolean free_message = FALSE; VOL gint i; //FIXME make this faster !! for (i = 0; i < len; i++) { // printd (DEBUG, "%c - %d", msg[i], (gint) msg[i]); /* no point in handling just this escape code ... //escape if (((gint) msg[i]) == 27) { //clear sequence: ESC[2J if (((i + 3) < len) && (msg[i + 1] == '[') && (msg[i + 2] == '2') && (msg[i + 3] == 'J')) { _e2_output_clear (); return; } } else */ //carriage return if ((msg[i]) == '\r') line_back = TRUE; //backspace else if ((msg[i]) == '\b') { //yes, there are backspaces in the middle backspaces_left = TRUE; gint j = 1; //find a character back in the string that is not //already a backspace //FIXME handle multi-byte chars while (j <= i) { if (msg[i - j] == '\b') j++; else { //overwrite it with backspace (i.e. later replaced) msg[i - j] = '\b'; break; } } //if there is no prior non-backspace character, //setup to delete one from the previous message if (j > i) back++; } } //eliminate any backspace char(s) //FIXME handle multi-byte chars if (backspaces_left) { VOL gchar *s1 = msg, *s2; while (*s1 != '\0') { if (*s1 == '\b') { s2 = s1+1; while (*s2 == '\b') s2++; gint slide = (msg+len+1-s2); memcpy ((gchar *)s1, (gchar *)s2, slide); len -= (s2-s1); } else s1++; } } VOL gchar *utf; //check if message is already utf8 if (g_utf8_validate (msg, -1, NULL)) utf = msg; else // utf = e2_utf8_from_locale (msg); { //convert to utf before inserting GError *error = NULL; utf = g_locale_to_utf8 (msg, -1, NULL, NULL, &error); if (error != NULL) { printd (WARN, "locale string to UTF8 conversion failed: %s", error->message); g_error_free (error); utf = e2_utf8_from_locale_fallback (msg); } } //ensure that there's an origin set if (origin == NULL) origin = "default"; //no translate VOL gboolean is_default_origin = g_str_equal (origin, "default"); //flag whether this origin is new VOL gboolean is_new; //get the text buffer for the output pane VOL GtkTextBuffer *buffer = tab->buffer; VOL GtkTextIter start, end; // gdk_threads_enter (); //try to get insert mark //it exists if there has been output in this origin before VOL GtkTextMark *origin_mark = gtk_text_buffer_get_mark (buffer, origin); //if not, create it at the end if (origin_mark == NULL) { is_new = TRUE; gtk_text_buffer_get_end_iter (buffer, &end); origin_mark = gtk_text_buffer_create_mark (buffer, origin, &end, TRUE); } else { is_new = FALSE; //the default context always outputs to the end of the textview //(ie the output is not "glued together" if (is_default_origin) { gtk_text_buffer_get_end_iter (buffer, &end); gtk_text_buffer_move_mark (buffer, origin_mark, &end); } else gtk_text_buffer_get_iter_at_mark (buffer, &end, origin_mark); } //move the scroll helper mark //(it is used by the scroll callback to find out if the user is //following output) gtk_text_buffer_move_mark (buffer, tab->mark, &end); gtk_text_buffer_place_cursor (buffer, &end); //do we have to overwrite a previous message's characters in front of us? //(if a previous message had a carriage return '\r' in it) VOL gchar *mark_del_name = g_strconcat (origin, "-del", NULL); //no translate VOL GtkTextMark *mark_del = gtk_text_buffer_get_mark (buffer, mark_del_name); if (mark_del != NULL) { VOL GtkTextIter iter_del; gtk_text_buffer_get_iter_at_mark (buffer, &iter_del, mark_del); gtk_text_buffer_delete (buffer, &end, &iter_del); } if (back > 0) { VOL gint _offset = gtk_text_iter_get_offset (&end); GtkTextIter backi; gtk_text_buffer_get_iter_at_offset (buffer, &backi, _offset - back); gtk_text_buffer_delete (buffer, &backi, &end); gtk_text_buffer_get_iter_at_offset (buffer, &end, _offset - back); } //delete the delete mark, it's only used one time if (mark_del != NULL) gtk_text_buffer_delete_mark (buffer, mark_del); VOL gint lfcount = 0; //if necessary, insert a newline character before the message because //it has a different context from the last one and the last one hasn't //printed one yet; // g_static_rec_mutex_lock (&print_mutex); if (tab->origin_lastime != NULL && !g_str_equal (tab->origin_lastime, origin) && !gtk_text_iter_starts_line (&end) && (is_new || is_default_origin)) lfcount = 1; // g_static_rec_mutex_unlock (&print_mutex); if (newline && (!gtk_text_iter_starts_line (&end))) { printd (DEBUG, "inserted additional newline because the message requested it"); lfcount++; } VOL GtkTextMark *mark_end; if (lfcount > 0) { gtk_text_buffer_insert (buffer, &end, "\n", lfcount); mark_end = gtk_text_buffer_get_insert (buffer); gtk_text_buffer_get_iter_at_mark (buffer, &end, mark_end); } //AT LAST, WE PUT IT IN ! gtk_text_buffer_insert (buffer, &end, utf, -1); //get start and end of text just inserted gtk_text_buffer_get_iter_at_mark (buffer, &start, origin_mark); mark_end = gtk_text_buffer_get_insert (buffer); gtk_text_buffer_get_iter_at_mark (buffer, &end, mark_end); if (first_tag != NULL) { //apply style-tags (if any) for added text va_list args; va_start (args, first_tag); VOL const gchar *tag_name = first_tag; while (tag_name != NULL) { VOL GtkTextTag *tag = gtk_text_tag_table_lookup (buffer->tag_table, tag_name); /* tags are all pre-configured if (tag == NULL) printd (WARN, "%s: no tag with name '%s'!", G_STRLOC, tag_name); else */ gtk_text_buffer_apply_tag (buffer, tag, &start, &end); tag_name = va_arg (args, const gchar*); } va_end (args); } /* when there is a context after the current one, the font weight of the later one (eg bold) is sometimes (eg current has error then normal) used instead of the proper font (normal) EVEN IF the inserted text is explicitly set to normal style probably a gtk bug as the font color is correct the following is a workaround */ else //if (!gtk_text_iter_is_end (&end)) { //gtk_text_buffer_remove_all_tags (buffer, &start, &end); VOL GSList *tags = gtk_text_iter_get_tags (&start); if (tags != NULL) { gtk_text_buffer_remove_all_tags (buffer, &start, &end); g_slist_free (tags); } } if (newline) { //trailing \n gtk_text_buffer_insert (buffer, &end, "\n", 1); mark_end = gtk_text_buffer_get_insert (buffer); gtk_text_buffer_get_iter_at_mark (buffer, &end, mark_end); } //scroll down if necessary if (//scrolling to new output is in effect e2_option_bool_get_direct (app.output.opt_jump) && (//manual scrolling has not moved the insert mark off-screen tab->onscreen //scroll regardless of whether manually scrolled away || !e2_option_bool_get_direct (app.output.opt_jump_follow)) && ( //added text is at the end of the buffer gtk_text_iter_is_end (&end) //don't care whether added text is at the end of the buffer || !e2_option_bool_get_direct (app.output.opt_jump_end)) //the end of the added text is not visible already && _e2_output_iter_offscreen (tab->text, &end)) gtk_text_view_scroll_to_mark (tab->text, mark_end, 0.0, TRUE, 0.1, 1.0); //if there was a carriage return, save a delete mark for the next message if ((line_back) && (!is_new)) //CHECKME origin_mark moved in this case too ? gtk_text_buffer_create_mark (buffer, mark_del_name, &end, TRUE); else gtk_text_buffer_move_mark (buffer, origin_mark, &end); // gdk_threads_leave (); if (utf != msg) g_free ((gchar *)utf); if (free_message) g_free (msg); g_free ((gchar *)mark_del_name); //protect threaded clearing of origin_buffer g_static_rec_mutex_lock (&print_mutex); if (tab->origin_lastime == NULL || !g_str_equal (tab->origin_lastime, origin)) { //save the origin of the last message if (tab->origin_lastime != NULL) g_free (tab->origin_lastime); tab->origin_lastime = g_strdup (origin); } g_static_rec_mutex_unlock (&print_mutex); } #undef VOL /** @brief update several output pane textview settings to conform to current config parameters Used only during window re-creation @return */ void e2_output_update_style (void) { const gchar *fntname = e2_utils_get_output_font (); PangoFontDescription *font_desc = pango_font_description_from_string (fntname); app.output.font_size = pango_font_description_get_size (font_desc); //(pixels or points) * PANGO_SCALE GList *member; for (member = app.tabslist; member != NULL; member = member->next) { GtkTextView *tvw = ((E2_OutputTabRuntime *)member->data)->text; gtk_text_view_set_wrap_mode (tvw, e2_option_int_get ("output-wrap-mode")); gtk_text_view_set_left_margin (tvw, e2_option_int_get ("output-left-margin")); gtk_text_view_set_right_margin (tvw, e2_option_int_get ("output-right-margin")); gtk_widget_modify_font (GTK_WIDGET (tvw), font_desc); GtkTextBuffer *buf = ((E2_OutputTabRuntime *)member->data)->buffer; GtkTextTagTable *table = gtk_text_buffer_get_tag_table (buf); GtkTextTag *tag = gtk_text_tag_table_lookup (table, "green"); g_object_set (G_OBJECT (tag), "foreground", e2_option_str_get ("color-positive"), NULL); tag = gtk_text_tag_table_lookup (table, "red"); g_object_set (G_OBJECT (tag), "foreground", e2_option_str_get ("color-negative"), NULL); tag = gtk_text_tag_table_lookup (table, "grey"); g_object_set (G_OBJECT (tag), "foreground", e2_option_str_get ("color-unimportant"), NULL); tag = gtk_text_tag_table_lookup (table, "small"); g_object_set (G_OBJECT (tag), "size", (gint) (PANGO_SCALE_SMALL * app.output.font_size), NULL); } pango_font_description_free (font_desc); } /** @brief create a new textview and buffer and some basic tags @param rt runtime data struct for the tab (NOT app.tab) @return scrolled window containing a textview */ static GtkWidget *_e2_output_create_view (E2_OutputTabRuntime *rt) { //visible flag is set elswhere, depending on cache data //init some vars //at the outset, assume that the current content is on-screen rt->onscreen = TRUE; rt->mark = NULL; //create scrolled window rt->scroll = e2_widget_get_sw (GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, GTK_SHADOW_OUT); //create text view rt->text = GTK_TEXT_VIEW (gtk_text_view_new ()); gtk_container_add (GTK_CONTAINER (rt->scroll), GTK_WIDGET (rt->text)); gtk_text_view_set_editable (rt->text, FALSE); gtk_text_view_set_cursor_visible (rt->text, FALSE); // allow focus so popup menu signal can happen, & can select text in the pane // GTK_WIDGET_UNSET_FLAGS (rt->text, GTK_CAN_FOCUS); gtk_text_view_set_wrap_mode (rt->text, e2_option_int_get ("output-wrap-mode")); gtk_text_view_set_left_margin (rt->text, e2_option_int_get ("output-left-margin")); gtk_text_view_set_right_margin (rt->text, e2_option_int_get ("output-right-margin")); const gchar *fntname = e2_utils_get_output_font (); PangoFontDescription *font_desc = pango_font_description_from_string (fntname); gtk_widget_modify_font (GTK_WIDGET (rt->text), font_desc); pango_font_description_free (font_desc); //signal used for "links" in the output pane // g_signal_connect (G_OBJECT (rt->text), "motion-notify-event", // G_CALLBACK (test_cb), NULL); gtk_widget_set_events (GTK_WIDGET (rt->text), gtk_widget_get_events (GTK_WIDGET (rt->text)) | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK); g_signal_connect (G_OBJECT (rt->text), "popup_menu", G_CALLBACK (_e2_output_popup_menu_cb), rt); g_signal_connect (G_OBJECT (rt->text), "button-press-event", G_CALLBACK (_e2_output_button_press_cb), rt); // g_signal_connect_after (G_OBJECT (rt->text), "button-release-event", // G_CALLBACK (_e2_output_button_release_cb), rt); //YUK no "activate" available, nor so for any ancestor g_signal_connect_after (G_OBJECT (rt->text->vadjustment), "value-changed", G_CALLBACK (_e2_output_scrolled_cb), rt); gtk_widget_show (GTK_WIDGET (rt->text)); rt->buffer = gtk_text_view_get_buffer (rt->text); gtk_text_buffer_create_tag (rt->buffer, "bold", "weight", PANGO_WEIGHT_BOLD, NULL); gtk_text_buffer_create_tag (rt->buffer, "italic", "style", PANGO_STYLE_ITALIC, NULL); gtk_text_buffer_create_tag (rt->buffer, "uline", "underline", PANGO_UNDERLINE_SINGLE, NULL); gtk_text_buffer_create_tag (rt->buffer, "small", "size", (gint) (PANGO_SCALE_SMALL * app.output.font_size), NULL); gtk_text_buffer_create_tag (rt->buffer, "blue", "foreground", "blue", NULL); gtk_text_buffer_create_tag (rt->buffer, "green", "foreground", e2_option_str_get ("color-positive"), NULL); gtk_text_buffer_create_tag (rt->buffer, "red", "foreground", e2_option_str_get ("color-negative"), NULL); gtk_text_buffer_create_tag (rt->buffer, "grey", "foreground", e2_option_str_get ("color-unimportant"), NULL); /* these not needed unless output links are parsed gtk_text_buffer_create_tag (rt->tab.buffer, "link", "foreground", "blue", "underline", PANGO_UNDERLINE_NONE, NULL); gtk_text_buffer_create_tag (rt->tab.buffer, "link-active", "foreground", "blue", "underline", PANGO_UNDERLINE_SINGLE, NULL); */ GtkTextIter end; gtk_text_buffer_get_end_iter (rt->buffer, &end); rt->mark = gtk_text_buffer_create_mark (rt->buffer, "internal-end-mark", &end, FALSE); //attach options rt->opt_wrap = e2_option_attach_value_changed ("output-wrap-mode", GTK_WIDGET (rt->text), _e2_output_set_op_wrap, rt); gtk_widget_show (rt->scroll); return rt->scroll; } /** @brief create output pane Create initial output pane - with initial no. of panes as per cache @return notebook, one or more pages, each containing an output pane */ GtkWidget *e2_output_initialise (void) { //create notebook for tabs, no callback until all pages created app.outbook = gtk_notebook_new (); GtkNotebook *book = GTK_NOTEBOOK (app.outbook); gtk_notebook_set_show_tabs (book, (app.tabcount > 1)); gtk_notebook_popup_enable (book); gtk_notebook_set_tab_pos (book, GTK_POS_LEFT); gtk_notebook_set_scrollable (book, TRUE); gtk_notebook_set_show_border (book, FALSE); const gchar *fntname = e2_utils_get_output_font (); PangoFontDescription *font_desc = pango_font_description_from_string (fntname); app.output.font_size = pango_font_description_get_size (font_desc);//(pixels or points) * PANGO_SCALE pango_font_description_free (font_desc); if (app.output.font_size == 0) //in case of invalid font name string { //set 10-point default app.output.font_size = (pango_font_description_get_size_is_absolute (font_desc)) ? 10 * PANGO_SCALE * 96 / 72 : //assume screen is 96 DPI 10 * PANGO_SCALE; } printd (DEBUG, "stacked tab is at %x", &app.tab); //we're always going to have at least 1 tab, which gets the initial focus gint i; E2_OutputTabRuntime *tab = NULL; //assignment for complier-warning prevention only //iterate backward so that we end with the first (default) tab for (i = app.tabcount ; i > 0 ; i--) { tab = ALLOCATE0 (E2_OutputTabRuntime); //FIXME only deallocated by the user, not at session end CHECKALLOCATEDFATAL (tab); // printd (DEBUG, "created tab data at %x", tab); app.tabslist = g_list_prepend (app.tabslist, tab); GtkWidget *sw = _e2_output_create_view (tab); gchar *labltxt = g_strdup_printf ("%d", i); //tab labels are numbers GtkWidget *label = gtk_label_new (labltxt); gtk_notebook_prepend_page (book, sw, label); g_free (labltxt); #ifdef USE_GTK2_10 tab->labelnum = i; //save tab id for matching gtk_notebook_set_tab_reorderable (book, sw, TRUE); # ifdef E2_TABS_DETACH gtk_notebook_set_tab_detachable (book, sw, TRUE); # ifdef USE_GTK2_12DND gtk_notebook_set_group (book, app.outbook); //instance-specific pointer # else gtk_notebook_set_group_id (book, getpid()); # endif # endif #endif } //last-added one becomes current //its current-page is set when the window is shown app.tab = *tab; curr_tab = tab; #ifdef E2_TABS_DETACH //enable tab dragging to new windows # ifdef USE_GTK2_12DND g_signal_connect (G_OBJECT (app.outbook), "create-window", G_CALLBACK (_e2_output_tab_drop_new), NULL); //CHECKME user_data # else //this is the gtk 2.10 approach gtk_notebook_set_window_creation_hook ((GtkNotebookWindowCreationFunc) _e2_output_tab_drop_new, NULL, //CHECKME gpointer data NULL); //(GDestroyNotify) destroy # endif //enable processing of tabs being dragged back from a new window gtk_drag_dest_set (app.outbook, GTK_DEST_DEFAULT_DROP, target_table2, n_targets2, GDK_ACTION_MOVE); g_signal_connect (G_OBJECT (app.outbook), "drag-data-received", G_CALLBACK (_e2_output_tabdrag_data_received_cb), NULL); //CHECKME user_data //capture clicks on tab labels g_signal_connect (G_OBJECT (app.outbook), "grab-focus", G_CALLBACK (_e2_output_grab_focus_cb), NULL); // g_signal_connect (G_OBJECT (app.outbook), "focus-in-event", // G_CALLBACK (_e2_output_focus_in_cb), NULL); //for unblocking g_signal_connect (G_OBJECT (app.outbook), "page-removed", G_CALLBACK (_e2_output_tabgone_cb), app.main_window); #endif g_signal_connect (G_OBJECT (app.outbook), "switch-page", G_CALLBACK (_e2_output_tabchange_cb), NULL); //no data //setup data common to all tabs //repetitive, but option data structs may move after options hash recreation, //and possibly without re-running the options init function app.output.opt_show_on_new = e2_option_get ("show-output-window-on-output"); // app.output.opt_show_on_focus_in = e2_option_get ("command-line-show-output-on-focus-in"); UNUSED DIRECTLY app.output.opt_hide_on_focus_out = e2_option_get ("command-line-hide-output-on-focus-out"); app.output.opt_jump = e2_option_get ("output-jump-new"); app.output.opt_jump_follow = e2_option_get ("output-jump-new-following"); app.output.opt_jump_end = e2_option_get ("output-jump-new-end"); //setup mutex to protect threaded access to print-function static variables g_static_rec_mutex_init (&print_mutex); return app.outbook; } /** @brief register actions related to output pane @return */ void e2_output_actions_register (void) { gchar *action_name = g_strconcat(_A(9),".",_A(29),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_output_clear_action, NULL, FALSE); action_name = g_strconcat(_A(9),".",_A(68),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_output_print_action, NULL, TRUE); action_name = g_strconcat(_A(9),".",_A(50),NULL); //output.help e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_output_help_action, NULL, TRUE); action_name = g_strconcat(_A(9),".",_A(74),NULL); //output.scrolldown e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_output_scroll_step, GINT_TO_POINTER (TRUE), TRUE); action_name = g_strconcat(_A(9),".",_A(75),NULL); //output.scrollup e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_output_scroll_step, GINT_TO_POINTER (FALSE), TRUE); action_name = g_strconcat(_A(9),".",_A(64),NULL); //output.pagedown e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_output_scroll_page, GINT_TO_POINTER (TRUE), TRUE); action_name = g_strconcat(_A(9),".",_A(65),NULL); //output.pageup e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_output_scroll_page, GINT_TO_POINTER (FALSE), TRUE); action_name = g_strconcat(_A(9),".",_A(48),NULL); //output.goto bottom e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_output_scroll_all, GINT_TO_POINTER (TRUE), TRUE); action_name = g_strconcat(_A(9),".",_A(49),NULL); //output.goto top e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_output_scroll_all, GINT_TO_POINTER (FALSE), TRUE); action_name = g_strconcat (_A(9),".",_A(26),NULL); //output.add e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_output_tab_add, NULL, FALSE); action_name = g_strconcat (_A(9),".",_A(38),NULL); //output.delete e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_output_tab_remove, NULL, FALSE); //FIXME data } /** @brief register options related to output pane @return */ void e2_output_options_register (void) { gchar *group_name = g_strconcat(_C(6),".",_C(27),":",_C(25),NULL); //_("commands.output:miscellaneous" e2_option_bool_register ("show-output-window-on-output", group_name, _("show output pane when a new message appears"), _("This will ensure you don't miss any messages"), NULL, FALSE, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP); e2_option_bool_register ("command-line-show-output-on-focus-in", group_name, _("show output pane if the command line is focused"), _("This causes the output pane to be opened when you are about enter a command"), NULL, FALSE, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_BUILDBARS); e2_option_bool_register ("command-line-hide-output-on-focus-out", group_name, _("hide output pane if the command line is unfocused"), _("This causes the output pane to be closed when you move focus away from the command line"), NULL, FALSE, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_BUILDBARS); e2_option_bool_register ("fileop-show", group_name, _("show commands"), _("This echoes the commands that are run and their exit value"), NULL, TRUE, E2_OPTION_FLAG_ADVANCED); e2_option_bool_register ("output-jump-new", group_name, _("scroll to new output"), _("This will automatically scroll the output pane content, to show new message(s)"), NULL, TRUE, E2_OPTION_FLAG_ADVANCED); e2_option_bool_register ("output-jump-new-following", group_name, _("only scroll when really following"), _("This stops automatic pane scrolling to new output if you've manually scrolled away"), "output-jump-new", TRUE, E2_OPTION_FLAG_ADVANCED); e2_option_bool_register ("output-jump-new-end", group_name, _("only scroll when new output is at the end"), _("This stops pane scrolling if a different process has displayed text after the current insert position"), "output-jump-new", FALSE, E2_OPTION_FLAG_ADVANCED); const gchar *opt_wrap_mode[] = {_("none"), _("everywhere"), _("words"), NULL}; group_name = g_strconcat(_C(6),".",_C(27),":",_C(37),NULL); //_("commands.output:style" e2_option_sel_register ("output-wrap-mode", group_name, _("line wrap mode"), _("If mode is 'none', a horizontal scrollbar will be available. Mode 'words' will only break the line between words"), NULL, 2, opt_wrap_mode, E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP | E2_OPTION_FLAG_BUILDALL); e2_option_int_register ("output-left-margin", group_name, _("left margin (pixels)"), _("This is the left margin between the output-pane edge and the text in it"), NULL, 6, 0, 1000, E2_OPTION_FLAG_ADVANCED); e2_option_int_register ("output-right-margin", group_name, _("right margin (pixels)"), _("This is the right margin between the output-pane edge and the text in it"), NULL, 2, 0, 1000, E2_OPTION_FLAG_ADVANCED); e2_option_bool_register ("custom-output-font", group_name, _("use custom font"), _("If activated, the font specified below will be used, instead of the theme default"), NULL, FALSE, E2_OPTION_FLAG_BASIC); //no rebuild e2_option_font_register ("output-font", group_name, _("custom output font"), _("This is the font used for text in the output pane"), "custom-output-font", "Sans 10", //_I( font name E2_OPTION_FLAG_BASIC); //no rebuild // group_name = g_strconcat(_C(32),".",_C(2),":",_C(27),NULL); //_("panes.other colors:output" group_name = g_strconcat(_C(6),".",_C(27),":",_C(2),NULL); //_("commands.output:colors" //CHECKME which of these output colors really do need the whole window to be reconstructed ? e2_option_color_register ("color-positive", group_name, _("positive color"), _("This color is used for messages about successful operations or other 'positive events'"), NULL, "dark green", E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP | E2_OPTION_FLAG_BUILDALL); e2_option_color_register ("color-negative", group_name, _("negative color"), _("This color is used for messages about unsuccessful operations or other 'negative events'"), NULL, "red", E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_BUILDALL); e2_option_color_register ("color-unimportant", group_name, _("unimportant color"), _("This color is used for messages of minor importance or other miscellaneous events"), NULL, "light grey", E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_BUILDALL); } emelfm2-0.4.1/src/e2_main.c0000600000175000017500000005444411014516433014265 0ustar cairocairo/* $Id: e2_main.c 893 2008-05-20 09:42:51Z tpgww $ Copyright (C) 2003-2008 tooar Portions copyright (C) 1999 Michael Clark. This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/e2_main.c @brief main function This file contains the main function and some local startup and shutdown helpers. */ /** \mainpage emelFM2 API \attention This API-documentation is under construction!\n It is planned to provide additional information, to make it easier to read and understand the code and hopefully to inspire you to enhance it. \section intro introduction Welcome to emelFM2-doxygen. If you don't know doxygen well, click on everything you're interested in. Even within the source documentation you will find lots of links. It's quite fun.\n\n On this page you'll find all the important aspects of coding for emelFM2. There are three parts:\n \arg \ref conventions \arg \ref internals \arg \ref visible\n If you're interested in developing plugins, go to \ref plugin_writing \section conventions conventions emelFM2 code should follow some layout and naming conventions: \arg use tabs, not spaces, to indent code. Preferred tab size is 4. \arg stick to a consistent coding style - vertically-aligned matching braces - a space between text and left-brackets, after commas, and around operators - "//" C++ style single-line comments - in general, break lines at or about column 80 \code void routine (void) { if (function (a, b, c)) { //do something x = a + 1; printf (_("Break lines after 80 characters, in general, like the following.\n)) rt->set = e2_option_tree_register ("option_name", strdup ("group_name"), _("option_label"), NULL, NULL, NULL, E2_OPTION_TREE_UP_DOWN | E2_OPTION_TREE_ADD_DEL); printf (_("or _perhaps_ NOT after 80, if the line is just a string that needs to be internationalised like this.\n")); } } \endcode \arg files that are part of emelFM2 have a name starting with e2_, or e2p_ for a plugin \arg emelFM2's own header files can included with just "" even if they are not in the same directory \arg the name of emelFM2's own functions starts with e2_, e2p_, _e2_, or _e2p_. The "_" variants are for static (private) functions, those prefixes should not be used for functions called from other parts of the program. The "p" variants apply in plugins. In general, the second part of the function name matches the file that contains the function. Sometimes it's a condensed form of that name. Functions imported from other code are probably best left with their original name. \arg emelFM2's own constants should begin with E2 or E2_ Code should be liberally commented, to assist others in reviewing and enhancing things. In particular, functions should be documented in a form which doxygen understands.\n\n Strings which are displayed to the user should be internationalised, like _("Tell me") \section internals internals This section explains how emelFM2 works. \arg \ref options \arg \ref cache \arg \ref actions \arg \ref commands \arg \ref bindings \arg \ref filesystem \arg \ref plugins \section visible user-interface This section is about parts of emelFM2 you can see and interact with. It provides development-related information which goes beyond, and should be read in conjuntion with, user-guidance in the files - USAGE (general help) - CONFIGURATION (configuration help mainly for use via a configuration dialog) - ACTIONS (a listing of action-names, each with parameters and brief description)\n The following items are covered: \arg \ref panes \arg \ref output \arg \ref toolbar \arg \ref commline \arg \ref menu \arg \ref othermenu \arg \ref status \arg \ref dialogs */ #include "emelfm2.h" #include #include #include #include #include "e2_dialog.h" #include "e2_filelist.h" #include "e2_filetype.h" #include "e2_plugins.h" #include "e2_complete.h" #include "e2_task.h" #define UPGRADE_PNAME "e2p_upgrade.so" extern gint refresh_refcount; extern GList *open_history; #ifdef USE_GLIB2_10 extern GHashTable *actions_hash; #endif static void _e2_main_system_shutdown (gint num); //__attribute__ ((noreturn)); /** @brief local management of gdk threads mutex These allow a conservative approach to locking without risk of deadlock @return */ void e2_main_close_gdklock (void) { #ifdef NATIVE_BGL gdk_threads_enter (); #else //a single thread can't write to screen more than once at a time, //so deadlocks just reflect "unnecessary" re-locks, and can be ignored # ifdef DEBUG_MESSAGES if (pthread_mutex_lock (&gdklock) == EDEADLK) printd (DEBUG, "attempted BGL re-lock"); # else pthread_mutex_lock (&gdklock); # endif #endif } void e2_main_open_gdklock (void) { #ifdef NATIVE_BGL gdk_threads_leave (); #else # ifdef DEBUG_MESSAGES if (pthread_mutex_unlock (&gdklock) == EPERM) printd (DEBUG, "bad attempt to open BGL"); # else pthread_mutex_unlock (&gdklock); # endif #endif } /** @brief idle and then timer callback to start refreshing filelists after both filelists are initialised This tests that both view->dir strings are not "" @param data NULL specified when idle was established, non-NULL for timer @return TRUE until the filelists have been established */ static gboolean _e2_main_refresh_initialise (gpointer data) { if (app.pane1_view.dir == NULL || *app.pane1_view.dir == '\0' || app.pane2_view.dir == NULL || *app.pane2_view.dir == '\0' || app.pane1_view.listcontrols.cd_working || app.pane2_view.listcontrols.cd_working) { if (data != NULL) //not first first callback return TRUE; //start a timer to check completion //app.timers[?] = CHECKME no need to force shutdown ? g_timeout_add (100, (GSourceFunc) (_e2_main_refresh_initialise), GINT_TO_POINTER (100)); return FALSE; } //this func may be called at some racy time when these need to be reset app.pane1_view.listcontrols.norefresh = FALSE; app.pane2_view.listcontrols.norefresh = FALSE; #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, session start after filelists filled"); #endif e2_filelist_enable_refresh (); #ifndef E2_REFRESH_DEBUG printd (DEBUG, "all change-polling initialised after filelists filled"); #endif return FALSE; } /** @brief spit out a warning if the current locale-information looks dodgy @param locale single or ";"-joined series of locale identifier strings @return */ static void _e2_main_check_locale_broken (const gchar *locale) { gboolean broken = FALSE; if (g_str_equal (locale, "C")) broken = TRUE; else { gchar **split = g_strsplit (locale, ";", -1); gint i = 0; while (split[i] != NULL) { if (g_str_has_prefix (split[0], "LC_CTYPE") && g_str_has_suffix (split[0], "=C")) { broken = TRUE; break; } i++; } } if (broken) { printf (_("Your current locale is '%s'.\n"), locale); printf ( _("You have set the environment variable G_BROKEN_FILENAMES, which\n" "causes GTK+ to convert filename encoding, from the one specified\n" "by the system locale, to UTF-8.\n" "However, you have not set a system locale. Please do so, by setting\n" "the environment variable LANG or LC_CTYPE!\n")); if (!e2_cl_options.ignore_problems) { printf ( _("(Note: There is a command line option -i/--ignore-problems, but use it\n" "at your own risk!)\n")); exit (1); } else printf ( _("%s will ignore locale problems when reading filenames because\n" "--ignore-problems/-i has been set. This might result in segfaults and all\n" "kind of problems. You really should set a system locale with the\n" "LANG or LC_CTYPE environment variable.\n"), PROGNAME); } } /** @brief check locale @return */ static void _e2_main_check_locale (void) { printd (DEBUG, "check_locale ()"); const gchar *locale = gtk_set_locale (); printd (NOTICE, "current locale is '%s'", locale); const gchar *broken = g_getenv ("G_BROKEN_FILENAMES"); if (broken != NULL) _e2_main_check_locale_broken (locale); #ifdef ENABLE_NLS printd (DEBUG, "setting locale base dir to '%s'", LOCALE_DIR); bindtextdomain (BINNAME, LOCALE_DIR); textdomain (BINNAME); bind_textdomain_codeset (BINNAME, "UTF-8"); #endif } /** @brief blind log handler used when gtk messages-logging is suppressed @return */ //static void _e2_main_blind_log_handler () { } /** @brief handle messages from glib or gtk @return */ static void _e2_main_message_handler (const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) { if (e2_cl_options.suppress_gtk_log) g_log_default_handler (log_domain, log_level, message, user_data); else { #ifdef DEBUG_MESSAGES printd (WARN, "%s message: %s", log_domain, message); #else //mimic default message printer without aborting on fatal error gchar *fmt = (log_level < G_LOG_LEVEL_WARNING) ? "\n** %s: %s **\n\n" : "%s: %s\n"; printf (fmt, log_domain, message); #endif g_log (log_domain, log_level, "%s", message); } } /****************/ /***** main *****/ /****************/ /** @brief main function @param argc number of command-line arguments @param argv array of command-line arguments @return nothing, directly */ gint main (gint argc, gchar *argv[]) { //threads needed pthread_mutexattr_t attr; //setup mutex to protect threaded access to cd functionality pthread_mutexattr_init (&attr); pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init (&list_mutex, &attr); pthread_mutex_init (&history_mutex, &attr); #ifndef NATIVE_BGL //PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP isn't always available to init mutex // pthread_mutexattr_t attr; pthread_mutexattr_init (&attr); pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_ERRORCHECK); pthread_mutex_init (&gdklock, &attr); // pthread_mutexattr_destroy (&attr); #endif pthread_mutexattr_destroy (&attr); g_thread_init (NULL); #ifndef NATIVE_BGL gdk_threads_set_lock_functions (e2_main_close_gdklock, e2_main_open_gdklock); #endif gdk_threads_init (); //setup gdk mutex //gtk initialization printd (DEBUG, "gtk init"); gtk_init (&argc, &argv); g_set_application_name (_("emelFM2")); e2_cl_option_process (argc, argv); _e2_main_check_locale (); #ifndef E2_FILES_UTF8ONLY e2_fs_check_coding (); //setup for path/filename conversion if needed #endif //send all messages to the handler GLogLevelFlags levels = G_LOG_LEVEL_MASK | G_LOG_FLAG_RECURSION | G_LOG_FLAG_FATAL; g_log_set_handler ("Glib", levels, _e2_main_message_handler, NULL); g_log_set_handler ("Gtk", levels, _e2_main_message_handler, NULL); //some local init stuff e2_action_setup_labels (); //need this before option_init, to avoid crashes when there is no config file e2_option_setup_labels (); //after labels & before options setup, setup data for referencing toolbars e2_toolbar_data_create (); e2_option_init (); //setup default options (before cache or config read) gboolean confdir_ok = e2_option_set_config_dir (); //set name for config dir, make it if not there already e2_cache_init (confdir_ok); //read and process cache file (if any) (before options init) gboolean confile_ok = (confdir_ok) ? e2_option_file_read (): //read & process saved options (if any) FALSE; if (confile_ok) { //ASAP after config settled, CHECKME custom dialog ok ? if (strcmp (app.cfgfile_version, VERSION RELEASE) < 0) { Plugin *p = e2_plugins_open1 (PLUGINS_DIR G_DIR_SEPARATOR_S UPGRADE_PNAME); //localised path if (p != NULL) { if (!p->plugin_init (p)) //do any upgrades printd (ERROR, "Can't initialize upgrade plugin: "UPGRADE_PNAME); gdk_threads_enter (); e2_plugins_unload1 (p, FALSE); gdk_threads_leave (); } else printd (ERROR, "Can't find upgrade plugin "UPGRADE_PNAME", so can't upgrade the config file"); } gchar **values = e2_list_to_strv (e2_cl_options.option_overrides); e2_option_read_array (values); g_strfreev (values); } //after config & updates, install default tree options where needed e2_option_tree_install_defaults (); //before plugins loaded, before bars/menus created e2_actions_init (); //setup action data stores & register most actions e2_complete_init (); //"like" actions #ifndef USE_GTK2_12TIPS app.tooltips = gtk_tooltips_new (); #endif //check if all plugin data is in config already // E2_OptionSet *set = e2_option_get ("plugins"); //internal name // gboolean defer_plugs = set->ex.tree.synced; // if (!defer_plugs) //if this is a fresh start, //do this before bars/menus created e2_plugins_load_all (); // load plugins specified in the config data //build GUI printd (DEBUG, "create main window"); e2_window_create (&app.window); //show the window before the filelist creation (in case there's a big delay) gdk_threads_enter (); gtk_widget_show (app.main_window); gdk_threads_leave (); #ifdef E2_FAM //before change dir, signal unknown state app.pane1.FAMreq = -1; app.pane2.FAMreq = -1; //hookup to a file monitor if possible e2_fs_FAM_connect (); //CHECKME ensure FAM reports don't pile up due to lack of polling #endif //initate dirty-checks e2_filelist_start_refresh_polling (); //config-change checks are disabled during list creation //so the mechanism needs to be initialised beforehand // app.config_timer_id = 0; e2_option_enable_config_checks (); #ifndef E2_STATUS_DEMAND e2_window_enable_status_update (-1); #endif //set refresh counter to its real initial value refresh_refcount = 0; #ifdef E2_REFRESH_DEBUG printd (DEBUG, "disable refresh, session start before filelists built"); #endif //prevent any actual refreshing until after the initial filelists are created e2_filelist_disable_refresh (); e2_button_setup_labels (); //maybe needed by dialog when opening dir //session-starts with BGL closed // gdk_threads_leave (); //threaded cd's may start at any time, including after main loop is running //and cd completion depends on speed of dir loading //this func will probably not set app.pane2.view.dir immediately e2_utils_goto_accessible_path (&app.pane2); //show pane 1 last, making it the default for session start e2_utils_goto_accessible_path (&app.pane1); // gdk_threads_enter (); gint pos = gtk_paned_get_position (GTK_PANED (app.window.output_paned)); if (pos == 0) { gchar *command = g_strconcat(_A(1),".",_A(42),NULL); //_("command.focus") e2_action_run_simple (command, NULL); g_free (command); } e2_utils_update_gtk_settings (); e2_filetype_add_all (); //process filetypes into data structure e2_cache_list_register ("open-history", &open_history); //history for 'open with' command e2_cache_int_register ("config-width", &app.cfgdlg_width, -1); //default to 'normal' window size e2_cache_int_register ("config-height", &app.cfgdlg_height, -1); e2_option_set_trash_dir (); GList *tmp; for (tmp = e2_cl_options.startup_commands; tmp != NULL; tmp = g_list_next (tmp)) #ifdef E2_COMMANDQ e2_command_run (tmp->data, E2_COMMAND_RANGE_DEFAULT, FALSE); #else e2_command_run (tmp->data, E2_COMMAND_RANGE_DEFAULT); #endif //if instructed, detach from controlling terminal and run in background if (e2_cl_options.detached #ifdef DEBUG_MESSAGES && (e2_cl_options.debug_level < 5) #endif ) daemon (1, 1); //trap signals that might be (but probably aren't) emitted when a //session-manager requests a shutdown/"save-yourself" //CHECKME only work if not running as daemon ? struct sigaction sigdata; sigdata.sa_handler = _e2_main_system_shutdown; // sigemptyset (&sigdata.sa_mask); //don't block any signal while the handler is busy sigfillset (&sigdata.sa_mask); //block all allowed signals while the handller is busy sigdata.sa_flags = 0; // sigaction (SIGKILL, &sigdata, NULL); //CHECKME save this for stop without saving ? sigaction (SIGABRT, &sigdata, NULL); sigaction (SIGQUIT, &sigdata, NULL); sigaction (SIGTSTP, &sigdata, NULL); sigaction (SIGHUP, &sigdata, NULL); sigaction (SIGINT, &sigdata, NULL); //this from a c in a terminal sigaction (SIGTERM, &sigdata, NULL); /* //SIGPIPE ignorance maybe needed to ensure thread-safety ? //(but we don't want to pass that on to child processes) // sigemptyset (&sigdata.sa_mask); //don't block any signal while the handler is busy sigaction (SIGPIPE, NULL, &sigdata); sigdata.sa_handler = SIG_IGN; sigaction (SIGPIPE, &sigdata, NULL); */ #ifdef E2_VFSTMP //session startup always uses local fs first, to prevent potential long //delay when app window is being created //now we can arrange to reinstate vfs state if appropriate //now done in plugin init g_idle_add ((GSourceFunc) e2_vfs_initialise, NULL); #endif //start change polling after both initial filelists are completed g_idle_add ((GSourceFunc) _e2_main_refresh_initialise , NULL); //get all actions, for help document // e2_action_list_all (); #ifdef E2_HAL e2_hal_init (); #endif printd (DEBUG, "enter main loop"); gdk_threads_enter (); gtk_main (); gdk_threads_leave (); exit (0); } /************************/ /*** departure lounge ***/ /************************/ /** @brief session-end cleanups Expects BGL on/closed, for any dialog or error message here or downstream @param compulsory TRUE to prevent the user from cancelling @param saveconfig TRUE to write config and cache files @param doexit TRUE to do a normal program exit @return FALSE if the user cancels or @a doexit is FALSE, else no return */ gboolean e2_main_closedown (gboolean compulsory, gboolean saveconfig, gboolean doexit) { if (!compulsory) { if (e2_option_bool_get ("session-end-warning")) { //only count running child commands if they are in line for killing guint running = e2_command_count_running_tasks (!e2_option_bool_get ("command-persist"), TRUE); if (running > 0) { gchar *prompt = g_strdup_printf (_("%u process(es) are running"), running); DialogButtons choice = e2_dialog_warning (prompt); g_free (prompt); if (choice != OK) return FALSE; } } } guint i; for (i = 0; i < MAX_TIMERS; i++) { if (app.timers[i] > 0) g_source_remove (app.timers[i]); } //CHECKME other non-static timers e.g. keybinding timers, edit blink timers etc e2_task_abort (TRUE); #ifdef E2_FAM e2_fs_FAM_disconnect (); #endif #ifdef E2_HAL e2_hal_disconnect (); #endif #ifdef E2_IMAGECACHE e2_cache_image_clearall (); #endif e2_plugins_unload_all (TRUE); //backup caches, cleanup etc /*#ifdef E2_VFS if (vfs.loaded) { //unloading does cacheing and proper cleanups Plugin *p = e2_plugins_check_installed ("vfs"VERSION); e2_plugins_unload1 (p, TRUE); } #endif */ if (saveconfig) { e2_option_file_write (NULL); e2_cache_file_write (); printd (DEBUG, "touch config dir"); e2_fs_touch_config_dir (); //signal to the world that changes saved } #ifdef USE_GLIB2_10 // GList *member; //need to deallocate the glib slices, at least //NOTE this must be after all cleanups involving actions g_hash_table_destroy (actions_hash); //NOTE this must be after all cleanups involving options g_hash_table_destroy (options_hash); /* e2_cache_clean (); e2_complete_clear (); e2_alias_clean (); e2_command_line_clean (); for (member = app.taskhistory; member != NULL; member = member->next) { //also free the data in the rt ?? DEALLOCATE (E2_TaskRuntime, (E2_TaskRuntime *) member->data); } e2_command_clear_pending (NULL, NULL); //output tabs for (member = app.tabslist; member != NULL; member = member->next) DEALLOCATE (E2_OutputTabRuntime, (E2_OutputTabRuntime *) member->data); //toolbar data E2_ToolbarData **thisbar; for (thisbar = app.bars; *thisbar != NULL; thisbar++) DEALLOCATE (E2_ToolbarData, (E2_ToolbarData *)*thisbar); e2_keybinding_clean (); //keybinding runtimes //FIXME deallocate other slices: //EncArray //E2_ToggleBox's ? //E2_ToggleData's ? //anything from e2_fileview_get_selected() //PlaceInfo's handled by unloading the vfs plugin //FileInfo's tied to treeview lines are cleaned already ?? HOW? //all E2_OptionTreeColumns //unref all optiontree stores ? //view & edit dialog history items (if sliced!) //edit dialog undo items //ETC */ #endif //these might be in some backup place, for VFS //dirline histories ? //directory history list items e2_fileview_clean_history (&app.pane1_view.dir_history); e2_fileview_clean_history (&app.pane2_view.dir_history); g_list_free (app.pane1.opendirs); //this for non-cached list without heaped data g_list_free (app.pane2.opendirs); //ditto /* printd (DEBUG, "clean options"); destroy_config (); //why bother, if the kernel does it all anyway? //cache(s) @ cache or named (lists ??) //FIXME?? = all those non-constant config strings printd (DEBUG, "clean bookmarks"); e2_bookmark_clean (); printd (DEBUG, "clean actions"); e2_actions_clean (); */ i = gtk_main_level (); //mainloop level isn't decremented by quit func printd (DEBUG, "gtk_main_quit () from level %d", i); while (i > 0) { gtk_main_quit (); i--; } if (doexit) { gdk_threads_leave (); // printd (DEBUG, "exit (0)"); exit (0); } return FALSE; } /** @brief user-initiated shutdown action @param from the button, menu item etc which was activated @param art action runtime data @return FALSE if the user cancelled, or else it does not return at all */ gboolean e2_main_user_shutdown (gpointer from, E2_ActionRuntime *art) { e2_main_closedown (FALSE, TRUE, TRUE); return FALSE; } /** @brief perform system-initiated shutdown in repsonse to a shutdown signal This is performed the "proper" way, so as to not block signalling to related processes @param num the signal number @return */ static void _e2_main_system_shutdown (gint num) { printd (DEBUG, "system shutdown initiated by signal %d", num); gdk_threads_enter (); //downstream errors expect BGL closed e2_main_closedown (TRUE, (num != SIGINT && num != SIGKILL), FALSE); gdk_threads_leave (); //in this handler, current signal is blocked, unblock it for this use struct sigaction sigdata; sigdata.sa_handler = SIG_DFL; sigemptyset (&sigdata.sa_mask); sigdata.sa_flags = 0; sigaction (num, &sigdata, NULL); sigaddset (&sigdata.sa_mask, num); sigprocmask (SIG_UNBLOCK, &sigdata.sa_mask, NULL); kill (getpid(), num); } emelfm2-0.4.1/src/e2_keybinding.h0000600000175000017500000000474211010340377015463 0ustar cairocairo/* $Id: e2_keybinding.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelfm2. emelfm2 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 3, or (at your option) any later version. emelfm2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_KEYBINDING_H__ #define __E2_KEYBINDING_H__ #include "emelfm2.h" typedef struct _E2_KeyRuntime { GdkModifierType mask; guint keyval; //gdk key code gchar *action; //name of action or command, maybe "" gchar *action_data; //action data, maybe "" gboolean cont; //TRUE to keep looking for other actions to activate GList *chained; //list of E2_KeyRuntime's for immediate-child key(s) //i.e. chained directly to this one } E2_KeyRuntime; typedef struct _E2_KeybindingRuntime { gchar *name; //binding (category) name // GtkTreeModel *model;//always opt_keys->ex.tree.model so that all known keys appear in config dialog GtkTreeRowReference *ref; //reference to model iter that's the root for the category //#ifdef E2_IDLE_KEYSYNC // gboolean synced; //TRUE when treestore data have been converted to runtime list //#endif GList *keys; //E2_KeyRuntime's for each key in the category GList *curr_chain; //this is set to the chained list of the most-recent activated key guint timeout_id; //id for chained keys timer // guint count; //no. of widgets bound to this binding group GSList *instances; //list of widgets bound to this binding group } E2_KeybindingRuntime; void e2_keybinding_register_all (void); void e2_keybinding_register (gchar *category, GtkWidget *widget); #ifdef E2_TRANSIENTKEYS void e2_keybinding_register_transient (gchar *category, GtkWidget *widget, gpointer install_func); void e2_keybinding_unregister (gchar *category, GtkWidget *widget); #endif void e2_keybinding_output_help (gchar *section); void e2_keybinding_clean (void); #ifdef E2_KEYALIAS void e2_keybinding_actions_register (void); #endif void e2_keybinding_options_register (void); #endif //ndef __E2_KEYBINDING_H__ emelfm2-0.4.1/src/emelfm2.h0000600000175000017500000005162211014516433014302 0ustar cairocairo/* $Id: emelfm2.h 893 2008-05-20 09:42:51Z tpgww $ Copyright (C) 2003-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __EMELFM2_H__ #define __EMELFM2_H__ //see also: Makefile has build-time conditional #defines and compiler parameters #define _GNU_SOURCE //default 64-bit functions (handle files > 2GB) (also in Makefile) #define _FILE_OFFSET_BITS 64 //some of the Makefile define's are transferred in this header #include "build.h" #include //needed for various statbuf declarations #include #include //some general things ... #include #include //#define GTK_DISABLE_DEPRECATED see Makefile #include #include #include #define GETTEXT_PACKAGE BINNAME #include //#define E2_ASSISTED see build.h #ifdef E2_ASSISTED # include #endif #ifndef PRIu64 //for systems that don't define PRI??? macros # if defined(__WORDSIZE) && __WORDSIZE == 64 # define PRIu64 "lu" # else //guess # define PRIu64 "llu" # endif #endif //include code valid only for gtk versions > 2.6, according to the //version in use when compiling //#define E2_CURRENTGTK_VERSION see makefile and build.h //mechanism for using glib/gtk facilities > min standard (2.6.0) #ifdef E2_CURRENTGTK_VERSION #if GLIB_CHECK_VERSION (2,8,0) #define USE_GLIB2_8 #endif #if GLIB_CHECK_VERSION (2,10,0) #define USE_GLIB2_10 #endif #if GLIB_CHECK_VERSION (2,12,0) #define USE_GLIB2_12 #endif #if GLIB_CHECK_VERSION (2,14,0) #define USE_GLIB2_14 #endif #if GTK_CHECK_VERSION (2,8,0) #define USE_GTK2_8 #endif #if GTK_CHECK_VERSION (2,10,0) #define USE_GTK2_10 #endif #if GTK_CHECK_VERSION (2,12,0) #define USE_GTK2_12 # endif #if GTK_CHECK_VERSION (2,14,0) #define USE_GTK2_14 #endif #endif #ifdef USE_GTK2_12 //this token governs use of gtk 2.12 tooltips #define USE_GTK2_12TIPS //this token governs use of gtk 2.12 notebook-tab DnD #define USE_GTK2_12DND #endif //tag for development of on-demand status-bar updates //NOTE need to check all BGL effects //#define E2_STATUS_DEMAND #ifndef E2_STATUS_DEMAND //tag for refcounting enable/disable status-bar updates //#define E2_STATUS_REF //allow checking for status-in progess when updating filelists //#define E2_STATUS_BLOCK #endif //include error-message code regardless of debug state //#define DEBUG_MESSAGES_ALWAYS //setup for kernel-based file monitoring, as opposed to fam/gamin //see Makefile & build.h for definition of these variables #if defined(E2_FAM_INOTIFY) || defined(E2_FAM_DNOTIFY) || defined(E2_FAM_KQUEUE) || defined(E2_FAM_PORTEVENT) # define E2_FAM_KERNEL //this is needed too # define E2_FAM #else //include code for file-change detection using FAM or gamin //#define E2_FAM see Makefile & build.h # ifdef E2_FAM # include # endif #endif #ifdef E2_FAM //usec interval after file events before FAM is polled //needs to be long enough for most/all changes to be noticed //by the monitor, but not so long that the user perceives a delay #define E2_FAMWAIT 100000 #endif //include code for making incremental changes to //file lists, instead of dump & refill //DOES NOT WORK //#define E2_INCLIST #define E2_BLOCK #define E2_UNBLOCK //include debug messages about refreshing //#define E2_REFRESH_DEBUG //include debug messages about mkdir dialog shutdown //#define RACE_CHECK //#ifndef USE_GTK2_10 //flag for code which enables something like the pre-0.1 approach to selecting //items by dragging //for gtk >= 2.10, this could be done by rubber-banding, but that's not so flexible #define E2_ALTLEFTMOUSE //#endif //bytes, NOT characters this is a linux define //#define NAME_MAX 255 //length of various buffers #define E2_MAX_LEN 1024 //msec interval between pane-directory polls //NOTE that the actual refresh interval may be a multiple (up to 3) times this //to avoid too many refreshes during e.g. archive creation #define E2_FILESCHECK_INTERVAL 1000 #define E2_FILESCHECK_INTERVAL_S 1 //msec interval between config-directory polls //this is only used if E2_FAM not defined #define E2_CONFIGCHECK_INTERVAL 5000 #define E2_CONFIGCHECK_INTERVAL_S 5 //msec interval between status line updates #define E2_STATUSREPORT_INTERVAL 1500 //allowed msec interval between clicks to be treated as a double //if gtk's value is smaller, then it is set to this instead #define E2_CLICKINTERVAL 330 //padding for various widgets #define E2_PADDING_LARGE 8 #define E2_PADDING 5 #define E2_PADDING_SMALL 3 #define E2_PADDING_XSMALL 2 #define E2_COMMAND_PREFIX "."G_DIR_SEPARATOR_S #define PLUGIN action_labels[14] // in these, "home" is a pointer determined at runtime #define E2_CONFIG_DIR home,"."BINNAME //#define E2_TRASH_DIR E2_CONFIG_DIR, "Trash" //#define SYSTEM_TRASH_DIR home, ".Trash" //#define E2_CONFIG_FILE "config" no good for gettext ?? do not translate //default build with horizontal panes - see Makefile //#define E2_PANES_HORIZONTAL //default icon pointsize - see Makefile //#define E2IP "24" //enable config/button/menu image-cacheing #define E2_IMAGECACHE //multi-colored filelists #define E2_RAINBOW #ifdef E2_RAINBOW //no. of color values in each allocated block of memory #define ATOMSPERCHUNK 10 typedef struct _E2_ColorData { GMemChunk *chunk; //data structure for a block of ATOMSPERCHUNK GdkColor structs GdkColor **pointers; //allocated space for ATOMSPERCHUNK pointers } E2_ColorData; #endif //maybe useful for multi-processor optimisation ? //#define VOLATILE volatile #define VOLATILE //support key.alias action //#define E2_KEYALIAS //support addition of items to tree options (eg keybindings) //#define E2_TRANSIENTKEYS see Makefile for this #ifdef E2_TRANSIENTKEYS # ifndef E2_KEYALIAS # define E2_KEYALIAS # endif #endif //support addition of treestore iters from strings like 1.2.3 //#define E2_TREEINCREMENT //support mountpoint-related capabilities #define E2_FS_MOUNTABLE //support detachable tabs for gtk >= 2.10 #ifdef USE_GTK2_10 #define E2_TABS_DETACH #endif //use replacement command-running (see Makefile) //NOT FINISHED //#ifndef E2_NEW_COMMAND //#define E2_NEW_COMMAND //#endif //enable filesystem-tree-navigation dialogs #define E2_TREEDIALOG //support app-specific window translucence NOT WORKING FOR gtk < 2.12 //anyhow, good enough for the window-manager to do this equally for all apps //#define E2_COMPOSIT see Makefile #ifdef E2_COMPOSIT # ifdef USE_GTK2_12 //minimum opacity % for dialogs # define DIALOG_OPACITY_LEVEL 95 # else # undef E2_COMPOSIT # endif #endif //support vfs for file/dir processing //NOT FINISHED //needs cacheing of view data, backend data structs, history, marks //lib interrogation, task backends etc etc //#define E2_VFS see Makefile //transition management #ifdef E2_VFS //# define E2_VFSTMP # ifdef E2_VFSTMP # define E2_VFSTMPOK # endif #endif //enable filelist liststore copying instead of getting fresh data from source #ifdef E2_VFS #define STORECOPY #endif //support for custom error handling with vfs #ifdef E2_VFS # define E2_ERR_NAME __sys_err # define E2_ERR_INIT __sys_err = NULL; # define E2_ERR_DECLARE GError *__sys_err = NULL; # define E2_ERR_CLEARBACKUP(a) if (a != NULL) g_error_free (a); //in these, there should never be any "a", we just need to get the leading comma # define E2_ERR_NONE(a) a, NULL # define E2_ERR_ARG(a) a, GError **__sys_err # define E2_ERR_SAMEARG(a) a, __sys_err # define E2_ERR_PTR(a) a, &__sys_err # define E2_ERR_MSGC(a) a, (*__sys_err)->message //these are for GError *, not for GError ** # define E2_ERR_MSGL(a) a, __sys_err->message # define E2_ERR_IS(num) (__sys_err != NULL && __sys_err->code == num) # define E2_ERR_ISNOT(num) (__sys_err == NULL || __sys_err->code != num) //these are for use in a func with GError ** argument # define E2_ERR_PIS(num) (__sys_err != NULL && (*__sys_err)->code == num) # define E2_ERR_PISNOT(num) (__sys_err == NULL || (*__sys_err)->code != num) # define E2_ERR_BACKUP(a) GError *a = NULL;if (__sys_err == NULL) __sys_err = &a; # define E2_ERR_CLEAR if (__sys_err != NULL) { g_error_free (__sys_err); __sys_err = NULL; } #else # define E2_ERR_NAME # define E2_ERR_INIT # define E2_ERR_DECLARE # define E2_ERR_CLEARBACKUP(a) # define E2_ERR_NONE(a) # define E2_ERR_PTR(a) # define E2_ERR_ARG(a) # define E2_ERR_SAMEARG(a) # define E2_ERR_MSGC(a) # define E2_ERR_MSGL(a) # define E2_ERR_IS(num) (errno==num) # define E2_ERR_ISNOT(num) (errno!=num) # define E2_ERR_PIS(num) (errno==num) # define E2_ERR_PISNOT(num) (errno!=num) # define E2_ERR_CLEAR # define E2_ERR_BACKUP(a) #endif //support for tailored argument(s) for fs functions #ifdef E2_VFS # define VPATH vpath # define VPCSTR(p) *((const gchar**)p) # define VPSTR(p) *((gchar**)p) #else # define VPATH const gchar # define VPCSTR(p) p # define VPSTR(p) (gchar*)p #endif //type of "dir" in a pane typedef enum { FS_LOCAL = 0, //default, mounted-local dir FS_FUSE = 1, //virtual local dir that needs special handling FS_REMOTE = 1 << 1,//virtual non-mounted remote dir FS_ARCHIVE = 1 << 2,//archive (can be local or remote) FS_KERNEL = 1 << 3,//a namespace in a multi-space local filesystem FS_SYNTH = 1 << 4,//a synthesised virtual dir e.g. from a search process or metadata derived } E2_FSType; //types of virtual dir that are non-native, and warrant timeout if misbehaved #define FS_SLOW (FS_FUSE | FS_REMOTE | FS_ARCHIVE) #ifdef E2_VFS typedef enum { E2PLACE_UNMOUNTED, E2PLACE_MOUNTING, E2PLACE_MOUNTED } PlaceReady; typedef struct _E2_PlaceInfo { gchar *priv_name; /* for mounted dir NULL CHECKME synth dir for remote site escaped utf8, no trailer, login-ready URI before the filepath proper with p/w etc for archive localised absolute path, with trailer, of temp dir used for unpacking whole or part constructed from vtab data (not stored there, it may have P/W text in URI) */ gchar *tip; /* for mounted dir: NULL CHECKME synth dir for remote site: utf8, no trailer, public components of 1st part of URI, before the filepath proper for archive: utf8, no trailer, pseudo-path of archive, with all ancestor(s) including remote URI and other archive */ gchar *plain_pw; //edit-ready password, utf8, or NULL if no P/W gchar *alias; //user-friendly short-form utf8 for menu label etc, NULL or "" if not in use gchar *workplace; //path of local fs dir, absolute localised no trailer E2_FSType dirtype; //flags for type of namespace this is //UNUSED E2_FSHandler handler; //enumerator of lib/plugin which handles fs-operations for this space //CHECK could be op-specific //UNUSED E2_VFSOpFlags cando; //flags for the sorts of operations that can be performed PlaceReady mountstate; //this place has been initialised by its backend handler //(maybe redundant if only mounted places can have a PlaceInfo) gboolean monitored; //alteration monitoring in effect for this place (even if just polling) //directory histories for this namespace GList *pane1_entered; //pane 1 dirline history, specific to current namespace GList *pane2_entered; //pane 2 dirline history, specific to current namespace GList *pane1_history; //pane1 dir_history, but specific to this namespace GList *pane2_history; //pane2 dir_history, but specific to this namespace GList *pane1_visited; //pane1 visited, opendirs, but specific to this namespace GList *pane2_visited; //pane2 visited, opendirs, but specific to this namespace struct _E2_PlaceInfo* parentplace; //for cd .. past root //etc } PlaceInfo; //data for vfs operations uning path-argument(s) typedef struct _E2_Vpath { const gchar *localpath; /*first, so that vpath* == gchar ** typically an absolute path string encoding may actually be utf-8 if a handler lib requires that */ PlaceInfo *spacedata; // GError *err; //replacement for error args in fs functions } vpath; #endif #include "debug.h" #include "e2_pane.h" #include "e2_fileview.h" #include "e2_alias.h" #include "e2_bookmark.h" #include "e2_button.h" #include "e2_cache.h" #include "e2_cl_option.h" #include "e2_combobox.h" #include "e2_command.h" #include "e2_command_line.h" #include "e2_fs.h" #include "e2_hook.h" #include "e2_keybinding.h" #include "e2_list.h" #include "e2_menu.h" #include "e2_output.h" #include "e2_toolbar.h" #include "e2_tree.h" #include "e2_utf8.h" #include "e2_utils.h" #include "e2_widget.h" #include "e2_window.h" // some nice helper/utility macros //these 2 must both be called with BGL closed //the gtk-versions block until at least one event is processed //#define WAIT_FOR_EVENTS while (gtk_events_pending ()) { gtk_main_iteration (); } //#define WAIT_FOR_EVENTS_SLOWLY while (gtk_events_pending ()) { gtk_main_iteration ();usleep(5000); } #define WAIT_FOR_EVENTS gdk_threads_leave (); \ while (g_main_context_pending (NULL)) { g_main_context_iteration (NULL, TRUE); } \ gdk_threads_enter (); #define WAIT_FOR_EVENTS_SLOWLY gdk_threads_leave (); \ while (g_main_context_pending (NULL)) { g_main_context_iteration (NULL, TRUE); usleep(5000); } \ gdk_threads_enter (); //and these 2 are the corresponding ones with BGL open #define WAIT_FOR_EVENTS_UNLOCKED while (g_main_context_pending (NULL)) { g_main_context_iteration (NULL, TRUE); } #define WAIT_FOR_EVENTS_UNLOCKED_SLOWLY while (g_main_context_pending (NULL)) { g_main_context_iteration (NULL, TRUE); usleep(5000); } #define ITEM_ISHIDDEN(expr) (expr[0]=='.') //#define ITEM_ISHIDDEN(expr) (*expr=='.') // development-phase string tagger // search for _I( for general strings that need attention #define _I(d) d #define RUN_ONCE_CHECK(ret) \ static gboolean run_once_check_init = FALSE; \ if (run_once_check_init) \ return ret; \ else \ run_once_check_init = TRUE; // TEMP_FAILURE_RETRY for systems which do not have it //uses errno so is valid for local operations only #ifndef TEMP_FAILURE_RETRY #define TEMP_FAILURE_RETRY(expr) \ ({ glong _res; \ do _res = (glong) (expr); while (_res == -1L && errno == EINTR); \ _res; }) #endif //do not implement slices everywhere until session-end cleanups are coded //#ifdef USE_GLIB2_10 #if 0 # define ALLOCATE0(type) (type *) g_slice_alloc0 (sizeof (type)); # define ALLOCATE(type) (type *) g_slice_alloc (sizeof (type)); # define DEALLOCATE(type,ptr) g_slice_free1 (sizeof (type), ptr); #else # ifdef USE_GLIB2_8 # define ALLOCATE0(type) (type *) g_try_malloc0 (sizeof (type)); # else # define ALLOCATE0(type) (type *) calloc (1, sizeof (type)); # endif # define ALLOCATE(type) (type *) g_try_malloc (sizeof (type)); # define DEALLOCATE(type,ptr) g_free (ptr); #endif //for ALLOCATE's that are too small for a slice #ifdef USE_GLIB2_8 # define MALLOCATE0(type) (type *) g_try_malloc0 (sizeof (type)); #else # define MALLOCATE0(type) (type *) calloc (1, sizeof (type)); #endif #define MALLOCATE(type) (type *) g_try_malloc (sizeof (type)); #define DEMALLOCATE(type,ptr) g_free (ptr); #ifdef USE_GLIB2_8 # define NEW(type,n) g_try_new (type, n) # define NEW0(type,n) g_try_new0 (type, n) #else # define NEW(type,n) (type*)malloc (sizeof(type)*n) # define NEW0(type,n) (type*)calloc (n, sizeof(type)) #endif //survive or exit gracefully when allocation fails //can implement this now, if so desired //#define CHECKALLOCATEDFATAL(p) if (p==NULL) e2_utils_memory_error (); //#define CHECKALLOCATEDWARN(p,more) if (p==NULL) {e2_utils_show_memory_message (); more;} //#define CHECKALLOCATEDFATALT(p) if (p==NULL) {gdk_threads_enter();e2_utils_memory_error();gdk_threads_leave();} //#define CHECKALLOCATEDWARNT(p,more) if (p==NULL) {gdk_threads_enter();e2_utils_show_memory_message();gdk_threads_leave(); more;} #define CHECKALLOCATEDFATAL(p) #define CHECKALLOCATEDWARN(p,more) #define CHECKALLOCATEDFATALT(p) #define CHECKALLOCATEDWARNT(p,more) //index enumerator for cancellable timers //CHECKME some other repeating, non-trivial-delay, timers are non-static enum { FAMPOLL_T, //polling for reports from file alteration monitor DIRTYCHECK_T, //for polling whether filelists are in need of refresh REFRESHBEGIN_T, //for waiting to start refreshing filelist(s) STATUS_T, //for status-line updates CONFIG_T, //for config-file change checking REFRESHWAIT_T, //for pausing until a prior refresh is completed CDWAIT_T, //for pausing until a prior cd is completed ASCROLL_T, //DnD auto-scroll timer MAX_TIMERS }; #ifdef E2_FAM typedef enum { E2_MONITOR_DEFAULT, E2_MONITOR_FAM, //unused E2_MONITOR_GAMIN } E2_FAMonitor; #endif typedef struct _E2_MainData { GtkWidget *main_window; //pair of main boxes that can be surrounded by toolbars GtkWidget *hbox_main; GtkWidget *vbox_main; GtkWidget *outbook; //notebook container for tabbed output-panes GtkWidget *status_bar_label2; //this is a hbox, at the right of the status bar, for general usage // needs to be shown when used GtkWidget *status_bar_box3; E2_WindowRuntime window; E2_PaneRuntime pane1; E2_PaneRuntime pane2; ViewInfo pane1_view; ViewInfo pane2_view; E2_ToolbarRuntime toolbar; E2_ToolbarRuntime commandbar; E2_AliasRuntime aliases; E2_OutputRuntime output; //non-tab-specific data for output pane E2_OutputTabRuntime tab; //tab-specific data for output pane GList *tabslist; //list of E2_OutputTabRuntime's for each tab gint tabcount; //no. of tabs (cached) E2_ToolbarData **bars; //null-terminated array of pointers to toolbar data GList *command_lines; //list of rt data structs of command-lines #if defined(E2_FAM) && !defined(E2_FAM_KERNEL) FAMConnection *fcp; FAMEvent *fep; #endif #ifdef E2_FAM E2_FAMonitor monitor_type; gint FAMreq; //request no used for monitoring config file changes #endif guint timers[MAX_TIMERS]; //glib timer id's time_t config_mtime; //last-logged (seconds) timestamp of config file #ifndef USE_GTK2_12TIPS GtkTooltips *tooltips; #endif GList *plugins; //list of data structs for all loaded plugins, "non-children" //in menu-order (children-submenu-order depends on a list in //corresponding parent) GHashTable *filetypes; GSList *typelist; //list of string arrays, needed tor cleanup #ifdef E2_IMAGECACHE GHashTable *icons; //table of E2_Images, for cached icons #endif #ifdef E2_RAINBOW GHashTable *colors; GList *colorchunks; #endif GSList *used_stores; //for deferred liststore clearing GList *taskhistory; #ifndef E2_FILES_UTF8ONLY gboolean utf8_filenames; //TRUE when filesystem coding is utf-8 or ascii only #endif GdkWindowState mainwindow_state; //flags indicating changes to main_window GtkWidget *context_menu; //main context-menu when it is popped up gint cfgdlg_width; //config dialog width (cached) gint cfgdlg_height; //config dialog height (cached) gchar cfgfile_version[20]; //store for config version string eg "0.1.9", used only for upgrades gboolean rebuild_enabled; //set FALSE to block window recreation #ifdef E2_STATUS_BLOCK gboolean status_working; //set TRUE to block reentrant status-checking etc #endif GHookList hook_pane_focus_changed; } E2_MainData; E2_MainData app; E2_PaneRuntime *curr_pane; E2_PaneRuntime *other_pane; ViewInfo *curr_view; ViewInfo *other_view; E2_OutputTabRuntime *curr_tab; //currently-focused member of tabslist pthread_mutex_t list_mutex; #define LISTS_LOCK pthread_mutex_lock (&list_mutex); #define LISTS_UNLOCK pthread_mutex_unlock (&list_mutex); //CHECKME worth having a separate mutex for this ? pthread_mutex_t history_mutex; #define HISTORY_LOCK pthread_mutex_lock (&history_mutex); #define HISTORY_UNLOCK pthread_mutex_unlock (&history_mutex); //for testing - define to use gtk's own mutex for BGL //#define NATIVE_BGL #ifndef NATIVE_BGL //local management of gdk mutex, to enable tolerant re-locking by the same thread #include //direct mutex-manipulation #define gdk_threads_enter e2_main_close_gdklock #define gdk_threads_leave e2_main_open_gdklock pthread_mutex_t gdklock; //BGL replacement void e2_main_close_gdklock (void); void e2_main_open_gdklock (void); //not locked now and not already locked - this should never happen //prevent unlock #define CLOSEBGL_IF_OPEN \ gint _lockres; \ _lockres = pthread_mutex_trylock (&gdklock); \ printd (DEBUG, "dialog title trylock result %d", _lockres); \ if (!(_lockres == 0 || _lockres == EBUSY)) \ { \ printd (WARN, "dialog title close BGL failed"); \ WAIT_FOR_EVENTS_UNLOCKED \ _lockres = 1; \ } #define OPENBGL_IF_CLOSED \ if (_lockres == 0) \ pthread_mutex_unlock (&gdklock); #else //def NATIVE_BGL //WAIT_FOR_EVENTS expects BGL closed #define CLOSEBGL_IF_OPEN \ WAIT_FOR_EVENTS_UNLOCKED #define OPENBGL_IF_CLOSED #endif gboolean e2_main_closedown (gboolean compulsory, gboolean saveconfig, gboolean doexit); gboolean e2_main_user_shutdown (gpointer from, E2_ActionRuntime *art); // __attribute__ ((noreturn)); #endif //ndef __EMELFM2_H__ emelfm2-0.4.1/src/e2_fileview.h0000600000175000017500000002216611010340377015152 0ustar cairocairo/* $Id: e2_fileview.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2004-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_FILEVIEW_H__ #define __E2_FILEVIEW_H__ #include "emelfm2.h" //these are in same order as ascii <, =, > typedef enum { LT, EQ, GT } Operator; typedef enum { E2_REFRESH, E2_RECREATE, E2_CHANGE } E2_RefreshType; typedef enum { CD_NOTFINISHED, CD_TIMEOUT, CD_SUCCESS } E2_CDType; //filelist treestore column-number enumerator enum { FILENAME = 0, SIZE, PERM, OWNER, GROUP, MODIFIED, ACCESSED, CHANGED, MAX_COLUMNS }; //directory-read failure codes enum { E2DREAD_ENOENT, //empty dir (not really an error) E2DREAD_NS, //stat dir failed E2DREAD_DNR, //dir not opened E2DREAD_DNCH, //cannot cd into dir to poll its contents E2DREAD_DNF, //read cancelled by user after timeout E2DREAD_ENOTH, //failed to create thread when doing timed read E2DREAD_ENOMEM, //not enough memory for storing data about read item E2DREAD_ELAST //just an enumerator }; #define E2DREAD_FAILED(ptr) (!(ptr == NULL || (gpointer)ptr > GUINT_TO_POINTER (E2DREAD_ENOMEM))) typedef enum { E2FSCASE_UNKNOWN, E2FSCASE_SENSITIVE, E2FSCASE_ANY, //insensitive E2FSCASE_NEVER, //tried to discover, but unable to do so E2FSCASE_VIRTUAL, E2FSCASE_NOCACHE = 16, //a bitfag set for removable devices E2FSCASE_SENSITIVENOW, //values corresponding to fixed devices E2FSCASE_ANYNOW, E2FSCASE_NOTNOW, E2FSCASE_VIRTUALNOW } E2_FSSensitive; typedef struct _E2_Listman { VOLATILE E2_RefreshType refreshtype; //whether a view update is a refresh, recreate or cd VOLATILE struct _E2_ViewInfo *view; //use this when re-starting a cd after timeout VOLATILE gchar *newpath; //stored copy of place to open, non-NULL implies cd requested VOLATILE gboolean history; VOLATILE gboolean hook; VOLATILE gboolean cd_requested; //TRUE when we can't yet cd using this data VOLATILE gboolean cd_working; //TRUE to prevent re-entrant cd of the same filelist // VOLATILE guint timer; //id of timer running a cd process VOLATILE gboolean refresh_requested; //TRUE when something has proposed that the view be refreshed VOLATILE gboolean refresh_working; VOLATILE gboolean norefresh; //flag signalling refresh is disabled (though backend checks are not) // VOLATILE gboolean listing ; //TRUE during th "core" part of list re-creation } E2_Listman; typedef struct _E2_ViewInfo { VOLATILE GtkListStore *store; VOLATILE GtkTreeModel *model; //may be a filter model or standard VOLATILE GtkWidget *treeview; VOLATILE GtkTreeSelection *selection; //pointer to selection for this view's treeview VOLATILE GHashTable *selected_names; //names of selected items (for reselection), or NULL gint sort_column; //copy of current sort column, can be <0 gboolean extsort; //TRUE when name-sorting is by extension GtkSortType sort_order; //copy of current sort order GtkWidget *sort_arrows[MAX_COLUMNS]; GtkLabel *name_label; //for showing active pane by changing label content // E2_FSType dirtype; //flags for type of namespace this view belongs to E2_FSSensitive case_sensitive_names; //cache, FIXME needs to be cleared when removable device changes #ifdef E2_VFS VOLATILE PlaceInfo *spacedata; //pointer to extra data for virual namespace, NULL for mounted dir #endif gchar dir[PATH_MAX]; //path shown in this view (utf-8) with vpreface if approprate GList *dir_history; //list of structs with focused line etc for each opened dir in the current namespace //FIXME clean list data at end-session //#ifndef E2_FAM time_t dir_mtime; time_t dir_ctime; //#endif GtkWidget *check_dirs; //filter sub-menu toggle-widget GtkWidget *check_name; //filter sub-menu toggle-widget struct { gchar *patternptr; GSList *compiled_patterns; //list of _E2_SelectPattern's gboolean invert_mask; gboolean case_sensitive; gboolean active; } name_filter; GtkWidget *check_size; struct { size_t size; Operator op; gboolean active; } size_filter; GtkWidget *check_date; struct { time_t time; //time_t may be int or long int Operator op; enum {MTIME, ATIME, CTIME} time_type; gboolean active; } date_filter; gboolean filtered_before; //state of prior call to filter function gboolean filter_directories; gboolean show_hidden; //whether to display hidden items in this view guint total_items; //total displayed and undisplayed items in the dir - 2 (to ignore . and ..) gint row; //index of most-recently clicked row in the treeview gint drop_row; //index of most-recently moused row during a drag operation gboolean lit; //TRUE if drop-row is highlighted // E2_RefreshType refreshtype; //whether a view update is a refresh, recreate or cd // gboolean refresh_requested; //TRUE when something has proposed that the view be refreshed // gboolean norefresh; //flag signalling refresh is disabled (though backend checks are not) // gboolean listing ; //flag to block recursive filelist updates E2_Listman listcontrols; gchar last_find[NAME_MAX]; //buffer for storing names used in find task // GHookList hook_refresh; //data for functions to run during post-refresh idle-time function } ViewInfo; typedef struct _E2_FileInfo { gchar filename[NAME_MAX+1]; //localised string struct stat statbuf; } FileInfo; typedef struct _E2_SelectedItemInfo { gchar filename[NAME_MAX+1]; //localised string no trailing / /* FileInfo info; gchar *filename; //selected item name (except no trailing /), utf-8 string #ifdef E2_INCLIST //use this if the store data is changed on the fly GtkTreeRowReference *ref; #else //this is used for un-selecting liststore items on the fly GtkTreePath *path; #endif */ } E2_SelectedItemInfo; typedef struct _E2_CDwatch { ViewInfo *view; //data for view being changed (can't use E2_PaneRuntime due to circular includes) gchar *newpath; //place to be opened, utf8 with trailing separator gint repeats; //current retry counter // guint watchtimer_id; //id of timer that is watching for completion E2_CDType *completed_flag; //store for flag to set when completion detected } E2_CDwatch; typedef struct _E2_DRead { pthread_t aid; //read thread ID, 0 when stopped pthread_t mid; //monitor thread ID, 0 when stopped GtkWidget *dialog; //too-slow dialog widget, or NULL } E2_DRead; typedef struct _E2_SelectPattern { GPatternSpec *pspec; gboolean negated; } E2_SelectPattern; gint e2_fileview_ext_sort (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction); gboolean e2_fileview_sort_column (gint colnum, ViewInfo *view); void e2_fileview_refilter_list (ViewInfo *view); void e2_fileview_filter_dirs_cb (GtkWidget *widget, ViewInfo *view); void e2_fileview_remove_filters_cb (GtkWidget *widget, ViewInfo *view); void e2_fileview_initialize_filters (ViewInfo *view); void e2_fileview_set_menu_position (GtkMenu *menu, gint *x, gint *y, gboolean *push_in, GtkWidget *treeview); void e2_fileview_focus_row (ViewInfo *view, gint row, gboolean select_row, gboolean clear_selection, gboolean center, gboolean grab_focus); //void e2_fileview_select_row_by_filename (ViewInfo *view, gchar *filename); unused GHashTable *e2_fileview_log_selected_names (ViewInfo *view); void e2_fileview_reselect_names (ViewInfo *view); void e2_fileview_translate_cols_array (gint *src_array, gint *dest_array, gint size); void e2_fileview_switch_views (void); //void e2_fileview_switch_views_simple (void); void e2_fileview_set_font (void); void e2_fileview_set_row_background (ViewInfo *view, GtkTreeIter *iter, GdkColor *color); void e2_fileview_clear_row_background (ViewInfo *view, GtkTreeIter *iter); void e2_fileview_update_col_cachedata (void); void e2_fileview_get_scroll_data (ViewInfo *view, gint *leftcol, gint *toprow); void e2_fileview_scroll_to_position (ViewInfo *view, gint leftcol, gint toprow); //gint e2_fileview_find_name_col (ViewInfo *view); unused gboolean e2_fileview_prepare_list (ViewInfo *view); void e2_fileview_scroll_list (GThread *viewthread); gpointer e2_fileview_refresh_list (ViewInfo *view); gboolean e2_fileview_treehash_free (GHashTable *data); gboolean e2_fileview_cd_watch (E2_CDwatch *data); gboolean e2_fileview_cd_manage (E2_Listman *data); GtkWidget *e2_fileview_create_list (ViewInfo *view); void e2_fileview_select_all (GtkWidget *widget, ViewInfo *view); void e2_fileview_clean_selected (GPtrArray *selected); void e2_fileview_clean_history (GList **history); GPtrArray *e2_fileview_get_selected (ViewInfo *view); GList *e2_fileview_get_selected_local (ViewInfo *view); FileInfo *e2_fileview_get_selected_first_local (ViewInfo *view, gboolean updir); gchar *e2_fileview_get_row_name (ViewInfo *view, gint row); #endif //ndef __E2_FILEVIEW_H__ emelfm2-0.4.1/src/filesystem/0000700000175000017500000000000011015120161014744 5ustar cairocairoemelfm2-0.4.1/src/filesystem/e2_fs_FAM.c0000600000175000017500000003536610770423624016631 0ustar cairocairo/* $Id: e2_fs_FAM.c 827 2008-03-20 09:00:04Z tpgww $ Copyright (C) 2005-2008 tooar . This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/filesystem/e2_fs_FAM.c @brief directory/file content change management functions This file contains functions related to detection of changes to the content of directory(ies) displayed in the file panes. Some of the code relates to monitoring with 'gamin' or with the original FAM. Kernel-specific monitoring code is elsewhere. */ #include "emelfm2.h" #ifdef E2_FAM #ifdef E2_FAM_KERNEL //this is in another file when not using FAM/gamin extern void e2_fs_FAM_poll (gboolean *p1altered, gboolean *p2altered, gboolean *cfgaltered); #else //using gamin or FAM for monitoring /** @brief initialise FAM/gamin connection the gamin-specific part of this is FAMNoExists() This creates a shared event data struct for both panes (toggle the request no & userdata as appropriate) Sets app.monitor_type, E2_MONITOR_GAMIN if the connection succeeded, else E2_MONITOR_DEFAULT @return */ void e2_fs_FAM_connect (void) { app.fcp = ALLOCATE0 (FAMConnection); CHECKALLOCATEDWARN (app.fcp, return;) if (FAMOpen2 (app.fcp, BINNAME)) { // printd (WARN, "Cannot initialise files monitor"); DEALLOCATE (FAMConnection, app.fcp); app.monitor_type = E2_MONITOR_DEFAULT; return; } // CHECKME any merit in runtime detection whether FAM or GAMIN ?? app.monitor_type = E2_MONITOR_GAMIN; #ifdef E2_GAMIN //block existence confirmation events - for dirs, not for files //gamin (0.1.5 at least) blocks Exist etc EVENTS ONLY after FAMMonitorDirectory[2] (); FAMNoExists (app.fcp); #endif app.fep = ALLOCATE0 (FAMEvent); CHECKALLOCATEDWARN (app.fep, return;) app.fep->fc = app.fcp; return; } /** @brief terminate FAM/gamin connection at session end or when monitoring is cancelled gamin itself not affected @return */ void e2_fs_FAM_disconnect (void) { if (app.monitor_type == E2_MONITOR_FAM || app.monitor_type == E2_MONITOR_GAMIN) { FAMClose (app.fcp); DEALLOCATE (FAMConnection, app.fcp); DEALLOCATE (FAMEvent, app.fep); } } /** @brief change monitored directory if current-dir not also in the other pane, cancel current-dir monitoring if new-dir not also in other pane, start monitoring new-dir If new-dir is not monitored (FAM error or dir is already monitored in other pane) the FAM request for the pane is set to -1 if new-dir is a link (which should not be able to be traversed) then we monitor its target @a olddir needs trailing '/', for valid comparing with rt->path etc @param olddir utf-8 string with absolute path of dir to stop monitoring @param rt data struct for the pane to which the cd applies, including the new dir in rt->path @return */ void e2_fs_FAM_change (gchar *olddir, E2_PaneRuntime *rt) { if (app.monitor_type == E2_MONITOR_DEFAULT) return; //cancel monitoring of current dir if that was happening //at session-start, both panes have rt->FAMreq == -1 if (rt->FAMreq != -1) { //use the request for this pane app.fep->fr.reqnum = rt->FAMreq; if (FAMCancelMonitor (app.fcp, &app.fep->fr)) { printd (DEBUG, "FAM cancel for %s failed", olddir); //FIXME handle error; } else { printd (DEBUG, "FAM cancelled for %s", olddir); //#ifndef E2_GAMIN //FAMAcknowledge event ?? //gamin < 0.1 did not do this, tho' FAM API doco says there is one if (FAMPending (app.fcp) > 0) { FAMNextEvent (app.fcp, app.fep); if (app.fep->code == FAMAcknowledge) printd (DEBUG, "FAMAcknowledge received for %s", olddir); } //#endif } //default flag = no-monitoring this pane rt->FAMreq = -1; } E2_PaneRuntime *ort = (rt == curr_pane) ? other_pane : curr_pane; #ifdef E2_VFSTMP //FIXME path for non-mounted dirs #else if (g_str_equal (olddir, ort->path)) #endif { //panes are showing same dir now //connect to the other pane if need be, to get correct pane id //when monitoring if (ort->FAMreq == -1) { guint watch_id = (ort == &app.pane1) ? E2PANE1 : E2PANE2; #ifdef E2_VFSTMP //FIXME path for non-mounted dirs #else gchar *local = D_FILENAME_TO_LOCALE (ort->path); #endif e2_fs_walk_link (&local E2_ERR_NONE()); if (FAMMonitorDirectory (app.fcp, local, &app.fep->fr, GUINT_TO_POINTER (watch_id))) { printd (DEBUG, "FAM init for %s failed", ort->path); //FIXME handle error; } else { //remember the request no. for this pane ort->FAMreq = FAMREQUEST_GETREQNUM(&app.fep->fr); printd (DEBUG, "FAM now monitoring %s", ort->path); } g_free (local); } } //now hoookup to the new dir, if it's not going already #ifdef E2_VFSTMP //FIXME path for non-mounted dirs #else if (!g_str_equal (ort->path, rt->path)) //NB panes may be same at session start #endif { //new dir not already monitored guint watch_id = (rt == &app.pane1) ? E2PANE1 : E2PANE2; #ifdef E2_VFSTMP //FIXME path for non-mounted dirs #else gchar *local = D_FILENAME_TO_LOCALE (rt->path); #endif e2_fs_walk_link (&local E2_ERR_NONE()); if (FAMMonitorDirectory (app.fcp, local, &app.fep->fr, GUINT_TO_POINTER (watch_id))) { printd (DEBUG, "FAM init for %s failed", rt->path); //FIXME handle error; } else { //remember the request no. for this pane rt->FAMreq = FAMREQUEST_GETREQNUM(&app.fep->fr); printd (DEBUG, "FAM now monitoring %s", rt->path); } g_free (local); } else rt->FAMreq = -1; } /** @brief setup monitoring of config file Set up with data = E2PANECONF, to distinguish this from panes 1 & 2 monitoring @return TRUE if the connection was successfully established */ gboolean e2_fs_FAM_monitor_config (void) { gchar *config_file = g_build_filename (e2_cl_options.config_dir, default_config_file, NULL); gchar *local = F_FILENAME_TO_LOCALE (config_file); if (FAMMonitorFile (app.fcp, local, &app.fep->fr, GUINT_TO_POINTER (E2PANECONF))) { printd (DEBUG, "FAM init for %s failed", config_file); g_free (config_file); F_FREE (local); return FALSE; } printd (DEBUG, "FAM init for %s succeeded", config_file); //#ifndef E2_GAMIN //siphon off FAMAcknowledge, FAMExists and FAMEndExist events while (FAMPending (app.fcp) > 0) { FAMNextEvent (app.fcp, app.fep); //FAMAcknowledge event ?? // if (app.fep->code == FAMAcknowledge) // printd (DEBUG, "FAMAcknowledge received for %s", config_file); } //#endif g_free (config_file); F_FREE (local); //remember the request no. for this file app.FAMreq = FAMREQUEST_GETREQNUM(&app.fep->fr); return TRUE; } /** @brief cancel monitoring of config file @return TRUE if the connection was successfully removed */ gboolean e2_fs_FAM_cancel_monitor_config (void) { gchar *config_file = g_build_filename (e2_cl_options.config_dir, default_config_file, NULL); //use the request for this file app.fep->fr.reqnum = app.FAMreq; if (FAMCancelMonitor (app.fcp, &app.fep->fr)) { printd (DEBUG, "FAM cancel for %s failed", config_file); g_free (config_file); return FALSE; } printd (DEBUG, "FAM cancel for %s succeeded", config_file); //#ifndef E2_GAMIN //siphon off FAMAcknowledge, FAMExists and FAMEndExist events while (FAMPending (app.fcp) > 0) { FAMNextEvent (app.fcp, app.fep); //FAMAcknowledge event ?? // if (app.fep->code == FAMAcknowledge) // printd (DEBUG, "FAMAcknowledge received for %s", config_file); } //#endif g_free (config_file); return TRUE; } /** @brief poll monitor to check if any monitored thing has changed This just updates flags, to signal that refresh is needed For a pane, update is needed if any directory content has changed, or if the dir is not accessible/readable or gone A missing config file is not reported (as we can't reload it anyway) @param p1altered pointer to location to store T/F for whether pane 1 needs refresh @param p2altered pointer to location to store T/F for whether pane 2 needs refresh @param cfgaltered pointer to location to store T/F for whether config needs refresh @return */ static void e2_fs_FAM_poll (gboolean *p1altered, gboolean *p2altered, gboolean *cfgaltered) { //make sure the monitored dir(s) are still valid //gamin (0.1.5 at least) will not have reported inaccessible/renamed/deleted dir(s) //access () traverses links (ok) and does not change atime (ok) #ifdef E2_VFSTMP //FIXME dir when not mounted local #else gchar *local = F_FILENAME_TO_LOCALE (app.pane1.path); #endif *p1altered = e2_fs_access (local, R_OK | X_OK E2_ERR_PTR()); F_FREE (local); #ifdef E2_VFSTMP //FIXME dir when not mounted local #else local = F_FILENAME_TO_LOCALE (app.pane2.path); #endif *p2altered = e2_fs_access (local, R_OK | X_OK E2_ERR_PTR()); F_FREE (local); //not interested whether config is still present *cfgaltered = FALSE; if (FAMPending (app.fcp) < 1) return; //new files trigger a Created event then a Changed event while (FAMPending (app.fcp) > 0) { FAMNextEvent (app.fcp, app.fep); //no error check //FAM (when starting to monitor any dir or file) and gamin //(when starting to monitor config file) generate events //we're not interested in if (app.fep->code == FAMExists || app.fep->code == FAMEndExist) continue; guint watch_id = GPOINTER_TO_UINT (app.fep->userdata); //CHECKME this reports if a monitored dir is renamed/deleted ? switch (watch_id) { case E2PANE1: *p1altered = TRUE; //mirrored panes need to get the same status if (app.pane2.FAMreq == -1) *p2altered = TRUE; break; case E2PANE2: *p2altered = TRUE; if (app.pane1.FAMreq == -1) *p1altered = TRUE; break; case E2PANECONF: *cfgaltered = TRUE; break; default: break; } } return; } #endif //ndef E2_FAM_KERNEL #endif //def E2_FAM /** @brief check whether contents of either filepane (and with kernel-type FAM, config too) need to be refreshed Filepane(s) are marked 'dirty' if dir is no longer present, or if its contents are changed. The latter will be determined by the applicable fam, which at a minimum involves comparing dir modification and change times against stored values Those times are updated if refresh is needed and not 'blocked' @param p1dirty pointer to flag, set TRUE if pane 1 needs refreshing or is gone @param p2dirty pointer to flag, set TRUE if pane 2 needs refreshing or is gone @if FAM @param cfgdirty pointer to flag, set TRUE if config file needs refreshing, only used if FAM/gamin in use @endif @return */ void e2_fs_FAM_check_dirty (gboolean *p1dirty, gboolean *p2dirty #ifdef E2_FAM , gboolean *cfgdirty #endif ) { #ifdef E2_FAM if (app.monitor_type != E2_MONITOR_DEFAULT) e2_fs_FAM_poll (p1dirty, p2dirty, cfgdirty); else { #endif #ifdef E2_VFS VPATH ddata; #endif struct stat statbuf; gboolean busy; gchar *local = F_FILENAME_TO_LOCALE (app.pane1_view.dir); #ifdef E2_VFS ddata.localpath = local; ddata.spacedata = app.pane1_view.spacedata; *p1dirty = e2_fs_access (&ddata, R_OK | X_OK E2_ERR_NONE()); //signal dir is gone now ! #else *p1dirty = e2_fs_access (local, R_OK | X_OK E2_ERR_NONE()); //signal dir is gone now ! #endif if (!*p1dirty) { #ifdef E2_VFS if (!e2_fs_stat (&ddata, &statbuf E2_ERR_NONE())) #else if (!e2_fs_stat (local, &statbuf E2_ERR_NONE())) #endif { *p1dirty = statbuf.st_mtime != app.pane1_view.dir_mtime || statbuf.st_ctime != app.pane1_view.dir_ctime; if (*p1dirty) { LISTS_LOCK busy = app.pane1_view.listcontrols.cd_working || app.pane1_view.listcontrols.refresh_working; LISTS_UNLOCK if (!busy) { app.pane1_view.dir_mtime = statbuf.st_mtime; app.pane1_view.dir_ctime = statbuf.st_ctime; } } } else { *p1dirty = TRUE; LISTS_LOCK busy = app.pane1_view.listcontrols.cd_working || app.pane1_view.listcontrols.refresh_working; LISTS_UNLOCK if (!busy) { app.pane1_view.dir_mtime = app.pane1_view.dir_ctime = time (NULL); } } } F_FREE (local); local = F_FILENAME_TO_LOCALE (app.pane2_view.dir); #ifdef E2_VFS ddata.localpath = local; ddata.spacedata = app.pane2_view.spacedata; *p2dirty = e2_fs_access (&ddata, R_OK | X_OK E2_ERR_NONE()); #else *p2dirty = e2_fs_access (local, R_OK | X_OK E2_ERR_NONE()); #endif if (!*p2dirty) { #ifdef E2_VFS if (!e2_fs_stat (&ddata, &statbuf E2_ERR_NONE())) #else if (!e2_fs_stat (local, &statbuf E2_ERR_NONE())) #endif { *p2dirty = statbuf.st_mtime != app.pane2_view.dir_mtime || statbuf.st_ctime != app.pane2_view.dir_ctime; if (*p2dirty) { LISTS_LOCK busy = app.pane2_view.listcontrols.cd_working || app.pane2_view.listcontrols.refresh_working; LISTS_UNLOCK if (!busy) { app.pane2_view.dir_mtime = statbuf.st_mtime; app.pane2_view.dir_ctime = statbuf.st_ctime; } } } else { *p2dirty = TRUE; LISTS_LOCK busy = app.pane2_view.listcontrols.cd_working || app.pane2_view.listcontrols.refresh_working; LISTS_UNLOCK if (!busy) { app.pane2_view.dir_mtime = app.pane2_view.dir_ctime = time (NULL); } } } F_FREE (local); #ifdef E2_FAM if (e2_option_bool_get ("auto-refresh-config") // && e2_option_check_config_dir () ) { gchar *filename = g_build_filename (e2_cl_options.config_dir, default_config_file, NULL); local = F_FILENAME_TO_LOCALE (filename); //CHECKME check for R_OK (conservative)? #ifdef E2_VFS ddata.localpath = local; ddata.spacedata = NULL; //local config files only if (!e2_fs_stat (&ddata, &statbuf E2_ERR_NONE())) #else if (!e2_fs_stat (local, &statbuf E2_ERR_NONE())) #endif { *cfgdirty = (statbuf.st_mtime != app.config_mtime); if (*cfgdirty && app.rebuild_enabled) app.config_mtime = statbuf.st_mtime; } else //don't report if the config file is missing *cfgdirty = FALSE; g_free (filename); F_FREE (local); } } #endif } #ifndef E2_FAM /** @brief initilise config file refresh baseline timestamp @return */ void e2_fs_FAM_config_stamp (void) { gchar *filename = g_build_filename (e2_cl_options.config_dir, default_config_file, NULL); gchar *local = F_FILENAME_TO_LOCALE (filename); #ifdef E2_VFS VPATH ddata = { local, NULL }; //only local config data supported #endif struct stat stat_buf; #ifdef E2_VFS if (!e2_fs_stat (&ddata, &stat_buf E2_ERR_NONE())) #else if (!e2_fs_stat (local, &stat_buf E2_ERR_NONE())) #endif app.config_mtime = stat_buf.st_mtime; g_free (filename); F_FREE (local); } #endif emelfm2-0.4.1/src/filesystem/e2_hal.c0000600000175000017500000003140310753423515016266 0ustar cairocairo/* $Id: e2_hal.c 800 2008-02-09 22:21:01Z tpgww $ Copyright (C) 2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/filesystem/e2_hal.c @brief functions related to system hardware-abstraction-layer */ #include "emelfm2.h" #ifdef E2_HAL #include #include #include #include #define COMPUTER_UDI "/org/freedesktop/Hal/devices/computer" #define COMPUTER_TYPE "system.formfactor" #define VOLUME "volume" #define IS_VOLUME "block.is_volume" #define REMOVABLE "storage.removable" static DBusConnection *connection = NULL; static LibHalContext *ctx = NULL; static GList *removable_devices = NULL; //list of HalStorageData's typedef struct _HalStorageData { gchar *udi; //for checking which devices are removed gchar *device_path; gboolean ejectable; } HalStorageData; /* //user data for HAL context struct _HalContextData { } HalContextData; */ static void _e2_hal_add_device_cb (LibHalContext *ctx, const gchar *udi); static void _e2_hal_remove_device_cb (LibHalContext *ctx, const gchar *udi); /* udi = '/org/freedesktop/Hal/devices/platform_floppy_0' info.bus = 'platform' (string) info.parent = '/org/freedesktop/Hal/devices/computer' (string) info.product = 'Platform Device (floppy.0)' (string) info.subsystem = 'platform' (string) info.udi = '/org/freedesktop/Hal/devices/platform_floppy_0' (string) linux.hotplug_type = 2 (0x2) (int) linux.subsystem = 'platform' (string) linux.sysfs_path = '/sys/devices/platform/floppy.0' (string) platform.id = 'floppy.0' (string) udi = '/org/freedesktop/Hal/devices/platform_floppy_0_storage' block.device = '/dev/fd0' (string) block.is_volume = false (bool) block.major = 2 (0x2) (int) block.minor = 0 (0x0) (int) block.storage_device = '/org/freedesktop/Hal/devices/platform_floppy_0_storage' (string) info.capabilities = {'storage', 'block'} (string list) info.category = 'storage' (string) info.interfaces = {'org.freedesktop.Hal.Device.Volume'} (string list) info.parent = '/org/freedesktop/Hal/devices/platform_floppy_0' (string) info.product = 'PC Floppy Drive' (string) info.udi = '/org/freedesktop/Hal/devices/platform_floppy_0_storage' (string) info.vendor = '' (string) linux.hotplug_type = 3 (0x3) (int) linux.sysfs_path = '/sys/block/fd0' (string) org.freedesktop.Hal.Device.Volume.method_argnames = {'mount_point fstype extra_options', 'extra_options', 'extra_options'} (string list) org.freedesktop.Hal.Device.Volume.method_execpaths = {'hal-storage-mount', 'hal-storage-unmount', 'hal-storage-eject'} (string list) org.freedesktop.Hal.Device.Volume.method_names = {'Mount', 'Unmount', 'Eject'} (string list) org.freedesktop.Hal.Device.Volume.method_signatures = {'ssas', 'as', 'as'} (string list) storage.automount_enabled_hint = true (bool) storage.bus = 'platform' (string) storage.drive_type = 'floppy' (string) storage.hotpluggable = false (bool) storage.media_check_enabled = false (bool) storage.model = '' (string) storage.no_partitions_hint = true (bool) storage.originating_device = '/org/freedesktop/Hal/devices/platform_floppy_0' (string) storage.physical_device = '/org/freedesktop/Hal/devices/platform_floppy_0' (string) storage.removable = true (bool) storage.removable.media_available = false (bool) storage.requires_eject = false (bool) storage.size = 0 (0x0) (uint64) storage.vendor = 'PC Floppy Drive' (string) volume.mount.valid_options = {'ro', 'sync', 'dirsync', 'noatime', 'nodiratime', 'noexec', 'quiet', 'remount', 'exec', 'utf8', 'shortname=', 'codepage=', 'iocharset=', 'umask=', 'uid='} (string list) */ /* * @brief display hal/dbus error message @param message specific message @error dbus error data, or NULL @return */ /* static void _e2_hal_advise (const gchar *message, const DBusError *error) { if (error != NULL && dbus_error_is_set (error)) { gchar *msg = g_strdup_printf ("%s (%s)", message, error->message); e2_output_print_error (msg, TRUE); } else e2_output_print_error ((gchar *)message, FALSE); } */ static gboolean _e2_hal_detect_current_removables (LibHalContext *ctx) { gint i, count; gchar **devices; DBusError error; dbus_error_init (&error); devices = libhal_manager_find_device_string_match (ctx, "info.category", "storage", &count, &error); if (dbus_error_is_set (&error)) { // _e2_hal_advise (_("HAL: cannot find any storage device"), &error); printd (WARN, "HAL: cannot find any storage device - %s", error.message); dbus_error_free (&error); return FALSE; } if (devices != NULL) { for (i = 0; i < count; i++) { if (devices[i] != NULL) _e2_hal_add_device_cb (ctx, devices[i]); } dbus_free_string_array (devices); } return TRUE; } /** @brief callback for a device-add event @param ctx the hal connection-context @param udi string identifying the device added @return */ static void _e2_hal_add_device_cb (LibHalContext *ctx, const gchar *udi) { DBusError error; dbus_error_init (&error); if (libhal_device_property_exists (ctx, udi, "storage.removable", &error)) { if (libhal_device_get_property_bool (ctx, udi, "storage.removable", &error)) { HalStorageData *devicedata = ALLOCATE0 (HalStorageData); CHECKALLOCATEDWARN (devicedata, return;); devicedata->udi = g_strdup (udi); //need a copy, original doesn't persist during removal devicedata->device_path = libhal_device_get_property_string (ctx, udi, "block.device", &error); printd (DEBUG, "HAL: storage %s is removable", devicedata->device_path); if (libhal_device_property_exists (ctx, udi, "storage.requires_eject", &error)) { devicedata->ejectable = (gboolean) libhal_device_get_property_bool (ctx, udi, "storage.requires_eject", &error); if (devicedata->ejectable) { if (!libhal_device_add_property_watch (ctx, udi, &error)) { // _e2_hal_advise (_("HAL: cannot set watch on device"), &error); printd (WARN, "HAL: cannot set watch on device - %s", error.message); dbus_error_free (&error); } else { printd (DEBUG, "HAL: Watch added to device udi %s", udi); printd (DEBUG, "storage will be ejected"); } } } removable_devices = g_list_append (removable_devices, devicedata); } else dbus_error_free (&error); } else dbus_error_free (&error); } /** @brief callback for a device-remove event @param ctx the hal connection-context @param udi string identifying the device removed @return */ static void _e2_hal_remove_device_cb (LibHalContext *ctx, const gchar *udi) { if (removable_devices != NULL) { GList *node; for (node = removable_devices; node != NULL; node = node->next) { HalStorageData *store; store = (HalStorageData *)node->data; if (strcmp (store->udi, udi) == 0) { if (store->ejectable) { DBusError error; dbus_error_init (&error); if (!libhal_device_remove_property_watch (ctx, udi, &error)) { // _e2_hal_advise (_("HAL: cannot set watch on device"), &error); printd (WARN, "HAL: cannot set watch on device - %s", error.message); dbus_error_free (&error); } else printd (DEBUG, "HAL: Watch added to device udi %s", udi); } if (store->udi != NULL) g_free (store->udi); libhal_free_string (store->device_path); DEALLOCATE (HalStorageData, store); removable_devices = g_list_remove (removable_devices, store); break; } } } } /** @brief callback for a property-change for a removable device that requires ejection @param ctx the hal connection-context @param udi string identifying the device whose property changed @param key string identifying the property which changed @param is_removed UNUSED TRUE when a property-change is a removal @param is_added UNUSED TRUE when a property-change is an addition @return */ static void _e2_hal_property_change_cb (LibHalContext *ctx, const gchar *udi, const gchar *key, dbus_bool_t is_removed, dbus_bool_t is_added) { printd (DEBUG, "HAL: property changed for udi %s and key %s", udi, key); //interested in ejectable media property changes if (strcmp (key, "storage.removable.media_available") == 0) { DBusError error; dbus_error_init (&error); gboolean loaded = (gboolean) libhal_device_get_property_bool (ctx, udi, "storage.removable.media_available", &error); if (dbus_error_is_set (&error)) { // _e2_hal_advise (_("HAL: cannot find media property"), &error); printd (WARN, "HAL: cannot find media property - %s", error.message); dbus_error_free (&error); return; } if (loaded) { printd (DEBUG, "HAL: removable media present now"); //do stuff //mount if device mountpoint is not mounted (os should handle that) } else { printd (DEBUG, "HAL: removable media gone"); //eject if not just done to trigger this callback //unmount if device mountpoint is mounted (os should handle that) } } } /** @brief check whether native @a utfpath resides on a removable device @param devpath /dev/ @return TRUE if it's removable */ gboolean e2_hal_device_is_removable (const gchar *devpath) { if (removable_devices != NULL) { GList *node; for (node = removable_devices; node != NULL; node = node->next) { if (strcmp (((HalStorageData *)node->data)->device_path, devpath) == 0) return TRUE; } } return FALSE; } /** @brief check whether native @a utfpath resides on an ejectable device @param devpath /dev/ @return TRUE if it's ejectable */ gboolean e2_hal_device_is_ejectable (const gchar *devpath) { if (removable_devices != NULL) { GList *node; for (node = removable_devices; node != NULL; node = node->next) { if (strcmp (((HalStorageData *)node->data)->device_path, devpath) == 0) return ((HalStorageData *)node->data)->ejectable; } } return FALSE; } /** @brief initialise HAL connection @return */ void e2_hal_init (void) { DBusError error; ctx = libhal_ctx_new (); if (ctx == NULL) { printd (DEBUG, "Cannot create HAL context"); return; } dbus_error_init (&error); printd (DEBUG, "connecting to DBUS"); connection = dbus_bus_get (DBUS_BUS_SYSTEM, &error); if (connection == NULL) { // _e2_hal_advise (_("Cannot connect to system data bus"), &error); printd (WARN, "Cannot connect to system data bus - %s", error.message); goto cleanup; } libhal_ctx_set_dbus_connection (ctx, connection); // libhal_ctx_set_user_data (ctx, hal_user_data); libhal_ctx_set_device_added (ctx, _e2_hal_add_device_cb); libhal_ctx_set_device_removed (ctx, _e2_hal_remove_device_cb); libhal_ctx_set_device_property_modified (ctx, _e2_hal_property_change_cb); if (!libhal_ctx_init (ctx, &error)) { // _e2_hal_advise (_("Cannot connect to system HAL"), &error); printd (WARN, "Cannot connect to system HAL - %s", error.message); goto cleanup; } if (!_e2_hal_detect_current_removables (ctx)) { // _e2_hal_advise (_(""), NULL); printd (WARN, "Cannot determine mounted volumes at session start"); goto cleanup; } if (!libhal_device_property_watch_all (ctx, &error)) { // _e2_hal_advise (_("HAL: Cannot watch properties"), &error); printd (WARN, "HAL: Cannot watch properties - %s", error.message); goto cleanup; } dbus_connection_setup_with_g_main (connection, NULL); return; cleanup: if (ctx != NULL) { libhal_ctx_free (ctx); ctx = NULL; } if (connection != NULL) dbus_connection_unref (connection); if (dbus_error_is_set (&error)) dbus_error_free (&error); } /** @brief end HAL connection and related cleanup @return */ void e2_hal_disconnect (void) { if (ctx != NULL) { DBusError error; dbus_error_init (&error); if (!libhal_ctx_shutdown (ctx, &error)) { printd (WARN, "error disconnecting from HAL - %s", error.message); dbus_error_free (&error); } libhal_ctx_free (ctx); } if (connection != NULL) dbus_connection_unref (connection); if (removable_devices != NULL) { GList *node; HalStorageData *store; for (node = removable_devices; node != NULL; node = node->next) { store = (HalStorageData *)node->data; if (store->udi != NULL) g_free (store->udi); if (store->device_path != NULL) libhal_free_string (store->device_path ); DEALLOCATE (HalStorageData, store); } g_list_free (removable_devices); } } #endif //def E2_HAL emelfm2-0.4.1/src/filesystem/e2_fs_FAM_inotify.c0000600000175000017500000005752010643544426020371 0ustar cairocairo/* $Id: e2_fs_FAM_inotify.c 469 2007-07-06 22:58:30Z tpgww $ Copyright (C) 2005-2007 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/filesystem/e2_fs_FAM_inotify.c @brief functions related to file-alteration monitoring using inotify on linux */ #include "emelfm2.h" #ifdef E2_FAM_INOTIFY #include #include #include "e2_fs_FAM_inotify.h" //#define EXTRA_MESSAGES //timer interval between inotify queue polls #define POLL_MILLISECONDS 200 /* Size (bytes) of kernel-space reports-quueue which triggers transcription to user-space. A queue can hold 16384 reports, each at least 16,and possibly up to (16+MAX_NAME), bytes. So, to avoid queue overflow, we conservatively need to transcribe before the size reaches 256k.However, we probably worry about memory paging before the queue gets to that size. For now, use 128k */ #define INQUEUE_SIZE 131072 /*Size (bytes) of local buffer for transcribing inotify reports When monitoring a dir, each reported inotify_event struct ends with a gchar stub, for the item name (without its path) So each such report is of indeterminate length. If there is a name, it will have a trailing \0 and then be padded out to a multiple of 16 bytes (informative but irrelevant..). NOTE that each relevant bit-flag for a reported item is sent as a separate report, so copying a 1000 items, say, would trigger 4000 reports !! but that is ok as the buffer is re-cycled */ #define READ_BUFFER_SIZE 8192 /*Max. no of sequential loops to check for anything pending in the inotify report queue */ #define MAX_CHECKS 5 /* When looping to check for queued pending reports, we stop iterating if the incremental size of the queue is less than GROWTH_FACTOR * 2^n, where n is the 0-based loop counter. This is essentially the approach taken by beagle */ #define GROWTH_FACTOR 32 //delay between sequential inotify checks #define PAUSE_MICROSECONDS 2000 /*typedef struct _E2_FAMReport { gboolean *p1altered; gboolean *p2altered; gboolean *cfgaltered; } E2_FAMReport; */ //since we don't detect what types of reports are provided, need //a statbuf for deciding whether a config file change is of interest /*static gchar *localconfig; static struct stat cfgstatbuf; */ static gint inotify_device_fd; //value to be stored in array to signal an unused column #define WD_UNUSED -1 //this is where we log the wd of any useful reports received from inotify enum { REPORT_WD, REPORT_COUNT, ACCESS_COUNT, REPORT_ROWS }; /*store for logging reports and associated stuff for up to 3 fd's (1 or 2 pane-dirs and config file) Rows are used as per the enum above*/ static gint reports [3][REPORT_ROWS] = {{WD_UNUSED}, {WD_UNUSED}, {WD_UNUSED}}; //static GHashTable *report_hash; //this is where we log monitored wd's //using the wd for the key and path for the value static GHashTable *wd_hash; //we get inotify reports via this static GIOChannel *inotify_ioc; //id of timer for polling the inotify queue //static guint inotify_id; //FIXME to reduce polling, find a way for inotify to trigger //a queue-read to clear the inotify queue before it's full //counter used to reduce frequency of access-only reports static gint access_counter; /** @brief get any inotify reports, and record the essential data for later processing This is a timer callback fn, as well as being called manually It must not do any processing per se - that takes too long and risks loss of reports @param user_data UNUSED pointer to data specified when the source was created @return TRUE always, so timer is never cancelled */ static gboolean _e2_fs_FAM_inotify_read (gpointer user_data) { static gpointer buffer = NULL; if (buffer == NULL) { buffer = g_try_malloc (READ_BUFFER_SIZE); //never cleared CHECKALLOCATEDWARN (buffer, ); if (buffer == NULL) { printd (WARN, "_e2_fs_inotify_read_events: unable to create read buffer"); return TRUE; } } //check a few times for pending queued inotify-reports gint count; guint pending, prev_pending = 0; for (count=0; count < MAX_CHECKS; count++) { if (ioctl (inotify_device_fd, FIONREAD, &pending) == -1) { pending = 0; //ensure we exit immediately break; } //stop checking now if the reports-queue is big enough if (pending > INQUEUE_SIZE) break; //or stop checking now if queue accretion-rate is //not still growing fast enough //(i.e. < GROWTH_FACTOR * 2^n extra queued bytes in loop n) //CHECKME merits of this ?? if ((pending - prev_pending) < (guint)(GROWTH_FACTOR * (1 << count))) break; prev_pending = pending; //short pause before checking again g_usleep (PAUSE_MICROSECONDS); } if (pending == 0) return TRUE; //nothing to get //now transcribe the reports to user-space void *store = buffer; gsize store_size = READ_BUFFER_SIZE; //must stop getting data if we can't fit another report header gsize buffer_limit = READ_BUFFER_SIZE - sizeof(struct inotify_event); gsize buffer_bytes; struct inotify_event *report = buffer; gint items = 0; //for debug reporting //loop to read all the queued inotify reports do { //read into full or partial buffer GIOStatus result = g_io_channel_read_chars (inotify_ioc, store, store_size, &buffer_bytes, NULL); if (result == G_IO_STATUS_EOF || result == G_IO_STATUS_AGAIN) break; else if (result == G_IO_STATUS_ERROR) { printd (WARN, "_e2_fs_inotify_read_events: error reading inotify data"); return TRUE; } gsize buffer_ptr = 0; //get the real no. of bytes in the buffer buffer_bytes += (store - buffer); //loop until we run out of data, or out of room while (buffer_ptr < buffer_bytes && buffer_ptr <= buffer_limit) { //we always scan from the real start of the buffer report = (struct inotify_event *) (buffer+buffer_ptr); buffer_ptr += sizeof (struct inotify_event) + report->len; //buffer_ptr may now be <,=.> buffer_bytes if (buffer_ptr > buffer_bytes) //if this happens, would be > READ_BUFFER_SIZE break; //can't read all of this report's data yet #ifdef EXTRA_MESSAGES if (report->len > 0) { //there is a name provided if (report->mask & IN_ISDIR) printd (DEBUG, "inotify reported a direcory: %s", report->name); else printd (DEBUG, "inotify reported wd %d mask 0x%x for item %s", report->wd, report->mask, report->name); } else { if (report->mask & IN_ISDIR) printd (DEBUG, "inotify reported a direcory"); else printd (DEBUG, "inotify reported wd %d mask 0x%x", report->wd, report->mask); } #endif if (report->mask & IN_Q_OVERFLOW) { //OOPS we lost something, don't know what .. //just trigger a refresh for everything register gint i; E2_BLOCK for (i=0; i<3; i++) reports[i][REPORT_COUNT]++; E2_UNBLOCK printd (DEBUG, "inotify reported queue overflow"); } else if (report->mask & IN_IGNORED) { #ifdef EXTRA_MESSAGES printd (DEBUG, "IN_IGNORE report(s) logged for wd %d", report->wd); #endif if (report->wd == app.FAMreq) { //monitored config file probably was deleted e2_fs_FAM_cancel_monitor_config (); e2_fs_FAM_monitor_config (); //log change for new wd E2_BLOCK register gint i, r = app.FAMreq; for (i=0; i<3; i++) { if (reports[i][REPORT_WD] == r) { reports[i][REPORT_COUNT] = 1; break; } } E2_UNBLOCK } } else //if (!(report->mask & IN_IGNORED)) { /*we don't care about any item name, because we only want to record changes to a monitored dir or the config file, not any item in a dir. So we just do a quick & clean report-count ref This also ignores any reports in the queue but relating to a watch that has been cancelled */ // g_hash_table_replace (report_hash, (gpointer)report->wd, NULL); //(gpointer)event->mask); // items++; //log the report E2_BLOCK register gint i, r = report->wd; for (i=0; i<3; i++) { if (reports[i][REPORT_WD] == r) { if (report->mask & IN_ACCESS) reports[i][ACCESS_COUNT]++; else reports[i][REPORT_COUNT]++; items++; #ifdef EXTRA_MESSAGES printd (DEBUG, "%d reports logged for wd %d", reports[i][REPORT_COUNT] + reports[i][ACCESS_COUNT], report->wd); #endif break; } } E2_UNBLOCK } } //decide how to play the next read ... if (buffer_ptr <= buffer_limit) //buffer not full, this time, so nothing more to read break; else if (buffer_ptr == buffer_bytes) { //the last-parsed report exactly-filled the buffer //so next read uses whole buffer store = buffer; store_size = READ_BUFFER_SIZE; } else { //(buffer_ptr > buffer_limit or buffer_ptr <> buffer_bytes) // in practice this means buffer_ptr > buffer_limit, as the buffer will be full //move partial-report to start of buffer //CHECKME does it matter when report not updated, i.e. //buffer_ptr > buffer_limit or buffer_ptr >= buffer_bytes gsize remnant = READ_BUFFER_SIZE + buffer - (void *)report; memcpy (buffer, report, remnant); //begin next read after the end of moved stuff store = buffer + remnant; store_size = READ_BUFFER_SIZE - remnant; } } while (buffer_bytes > 0); // if (items > 0) // printd (DEBUG, "inotify reported %d events from %d scans", items, count+1); return TRUE; } /** @brief helper fn for finding the hash-table entry for a monitored wd @param key a hash key, pointerised wd that is being monitored @param value UNUSED value associated with @a key @param thiswd pointerised wd to be found @return TRUE if @a key matches @a thiswd */ gboolean _e2_fs_FAM_inotify_find_wd (gpointer key, gpointer value, gpointer thiswd) { return (key == thiswd); } /** @brief process and clear all hashed inotify reports This is a helper fn for hash-walking @param key a hash key, a pointerised wd reported by inotify @param value UNUSED the value associated with @a key @param results data passed to g_hash_table_remove(). @return TRUE so that the key/value will be removed from the hash */ /*static gboolean _e2_fs_FAM_inotify_poll (gpointer key, gpointer value, E2_FAMReport *results) { gint wd = GPOINTER_TO_INT (key); if (wd == app.pane1.FAMreq) *results->p1altered = TRUE; else if (wd == app.pane2.FAMreq) *results->p2altered = TRUE; else if (wd == app.FAMreq) *results->cfgaltered = TRUE; return TRUE; } */ /** @brief register @a path with inotify This assumes any item will not be registered more than once This func can be called from within a threaded refresh @param path utf-8 string with path of item to be monitored @return the watch descriptor established by inotfy, <0 for an error */ static gint _e2_fs_FAM_inotify_monitor_item (gchar *path, guint mask) { gchar *local = F_FILENAME_TO_LOCALE (path); gint wd = inotify_add_watch (inotify_device_fd, local, (__u32) mask); F_FREE (local); if (wd >= 0) { //setup the reports log gint i; E2_BLOCK for (i=0; i<3; i++) { if (reports[i][REPORT_WD] == WD_UNUSED) { reports[i][REPORT_WD] = wd; reports[i][REPORT_COUNT] = 0; reports[i][ACCESS_COUNT] = 0; break; } } //remember it, for cleanup if inotify is killed g_hash_table_replace (wd_hash, GINT_TO_POINTER (wd), g_strdup (path)); E2_UNBLOCK printd (DEBUG, "Added inotify watch %d for %s", wd, path); } else printd (WARN, "Failed to add inotify watch for %s\n%s", path, strerror (errno)); return wd; } /** @brief de-register @a path with inotify This func can be called from within a threaded refresh @param path utf-8 string with path of item no longer to be monitored, used in debug messages @param wd watch descriptor number that is to be cancelled @return TRUE if successfully cancelled */ static gboolean _e2_fs_FAM_inotify_demonitor_item (gchar *path, __u32 wd) { gboolean retval = TRUE; if (inotify_rm_watch (inotify_device_fd, wd) < 0) { //error may be because monitored item (e.g. config file or unmounted dir) is gone now printd (DEBUG, "%s error when trying to remove inotify watch for %s", strerror (errno), path); retval = FALSE; } //flush the reports queue _e2_fs_FAM_inotify_read (NULL); //free up the corresponding reports-log E2_BLOCK gint i, r = (gint)wd; for (i=0; i<3; i++) { if (reports[i][REPORT_WD] == r) { reports[i][REPORT_WD] = WD_UNUSED; break; } } //no need to remember wd, for cleanup if inotify is killed g_hash_table_remove (wd_hash, GINT_TO_POINTER (wd)); E2_UNBLOCK printd (DEBUG, "Removed inotify watch for %s", path); return retval; } /** @brief Initialize inotify monitoring. @return TRUE if initialization succeeded */ static gboolean _e2_fs_FAM_inotify_init (void) { inotify_device_fd = inotify_init (); if (inotify_device_fd < 0) { printd (WARN, "Could not initialize inotify"); return FALSE; } inotify_ioc = g_io_channel_unix_new (inotify_device_fd); g_io_channel_set_encoding (inotify_ioc, NULL, NULL); g_io_channel_set_flags (inotify_ioc, G_IO_FLAG_NONBLOCK, NULL); /* inotify_src = g_io_create_watch (inotify_ioc, G_IO_IN | G_IO_HUP | G_IO_ERR); g_source_set_callback (inotify_src, _e2_fs_FAM_inotify_read, NULL, NULL); g_source_set_priority (inotify_src, 300); g_source_set_can_recurse (inotify_src, FALSE); inotify_src_id = g_source_attach (inotify_src, NULL); */ app.timers[FAMPOLL_T] = g_timeout_add (POLL_MILLISECONDS, _e2_fs_FAM_inotify_read, NULL); // report_hash = g_hash_table_new (g_direct_hash, g_direct_equal); wd_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); return TRUE; } /** @brief helper fn for cancelling all monitoring @param key a hash key, a pointerised wd for the monitored item @param value value associated with @a key, a copy of path string for the monitored item @param data UNUSED pointer to data given to the foreach fn @return */ void _e2_fs_FAM_inotify_cancel_wd (gpointer key, gpointer value, gpointer data) { inotify_rm_watch (inotify_device_fd, (__u32) key); //cancel without changing the hash //don't bother with data cleanups at session-end } /** @brief cleanup after ending inotify monitoring. @return */ static void _e2_fs_FAM_inotify_abandon (void) { //cancel all monitoring that's in force g_hash_table_foreach (wd_hash, (GHFunc) _e2_fs_FAM_inotify_cancel_wd, NULL); GError *err = NULL; g_io_channel_shutdown (inotify_ioc, FALSE, &err); // g_source_destroy (inotify_src); //don't worry about hash } /**********************/ /** public functions **/ /**********************/ /** @brief establish inotify connection @return */ void e2_fs_FAM_connect (void) { app.monitor_type = (_e2_fs_FAM_inotify_init ()) ? E2_MONITOR_FAM : E2_MONITOR_DEFAULT ; } /** @brief terminate inotify connection session-end, no data freeing @return */ void e2_fs_FAM_disconnect (void) { if (app.monitor_type == E2_MONITOR_FAM) _e2_fs_FAM_inotify_abandon (); } /** @brief change monitored directory if current-dir not also in the other pane, cancel current-dir monitoring if new-dir not also in other pane, start monitoring new-dir If new-dir is not monitored (FAM error or dir is already monitored in other pane) the FAM request for the pane is set to -1 @a olddir needs trailing '/', for valid comparing with rt->path etc @param olddir utf-8 string with absolute path of dir to stop monitoring @param rt data struct for the pane to which the cd applies, including the new dir in rt->path @return */ void e2_fs_FAM_change (gchar *olddir, E2_PaneRuntime *rt) { if (app.monitor_type == E2_MONITOR_DEFAULT) return; //cancel monitoring of current dir if that was happening //at session-start, both panes have rt->FAMreq == -1 if (rt->FAMreq != -1) { //use the request for this pane _e2_fs_FAM_inotify_demonitor_item (olddir, (__u32) rt->FAMreq); //default flag = no-monitoring this pane rt->FAMreq = -1; } E2_PaneRuntime *ort = (rt == curr_pane) ? other_pane : curr_pane; #ifdef E2_VFSTMP //FIXME path for non-mounted dirs #else if (g_str_equal (olddir, ort->path)) #endif { //panes are showing same dir now //connect to the other pane if need be if (ort->FAMreq == -1) { #ifdef E2_VFSTMP //FIXME path for non-mounted dirs #else ort->FAMreq = _e2_fs_FAM_inotify_monitor_item (ort->path, INDIR_FLAGS); #endif } } //now hoookup to the new dir, if it's not going already #ifdef E2_VFSTMP //FIXME path for non-mounted dirs #else if (!g_str_equal (ort->path, rt->path)) //NB panes may be same at session start #endif { //new dir not already monitored #ifdef E2_VFSTMP //FIXME path for non-mounted dirs #else rt->FAMreq = _e2_fs_FAM_inotify_monitor_item (rt->path, INDIR_FLAGS); #endif } else rt->FAMreq = -1; } /** @brief get rid of one pending change-report for directory @a path Used as part of filelist refreshing There is no actual change if the wd for the relevant pane is -1 which means there is no monitoring anyway @param path utf-8 string with path of dir to be processed @return */ void e2_fs_FAM_clean_reports (gchar *path) { if (app.monitor_type == E2_MONITOR_DEFAULT) return; //suspend periodic polling of pending-reports quene if (app.timers[FAMPOLL_T] > 0) { g_source_remove (app.timers[FAMPOLL_T]); app.timers[FAMPOLL_T] = 0; } //and poll now _e2_fs_FAM_inotify_read (NULL); gint wd = #ifdef E2_VFSTMP //FIXME path for non-mounted dirs #else (g_str_equal (curr_pane->path, path)) ? #endif curr_pane->FAMreq : other_pane->FAMreq; if (wd != -1) { //dir was being monitored before //decrement its reports log gint i; E2_BLOCK for (i=0; i<3; i++) { if (reports[i][REPORT_WD] == wd) { //FIXME ACCESS reports need to be considered // if (reports[i][REPORT_COUNT] > 0) // reports[i][REPORT_COUNT]--; if (reports[i][ACCESS_COUNT] > 0) reports[i][ACCESS_COUNT]--; break; } } E2_UNBLOCK } //resume polling the queue app.timers[FAMPOLL_T] = g_timeout_add (POLL_MILLISECONDS, (GSourceFunc) _e2_fs_FAM_inotify_read, NULL); } /** @brief begin inotify monitoring of directory @a path Used as part of filelist refreshing There is no actual change if the fd for the relevant pane is -1 which means there is no monitoring anyway @param path utf-8 string with path of dir to be resumed @return */ /*void e2_fs_FAM_monitor_dir (gchar *path) { E2_PaneRuntime *rt = #ifdef E2_VFSTMP //FIXME path for non-mounted dirs #else (g_str_equal (curr_pane->path, path)) ? #endif curr_pane : other_pane; if (rt->FAMreq != -1) { //dir was being monitored before rt->FAMreq = _e2_fs_FAM_inotify_monitor_item (path, INDIR_FLAGS); // printd (DEBUG, "(as part of a resumption)"); } } */ /** @brief cancel inotify monitoring of directory @a path Used as part of filelist refreshing There is no actual change if the fd for the relevant pane is -1 which means there is no monitoring anyway Any prior reports for the dir are cleared @param path utf-8 string with path of dir to be suspended @return */ /*void e2_fs_FAM_cancel_monitor_dir (gchar *path) { E2_PaneRuntime *rt = #ifdef E2_VFSTMP //FIXME path for non-mounted dirs #else (g_str_equal (curr_pane->path, path)) ? #endif curr_pane : other_pane; if (rt->FAMreq != -1) { _e2_fs_FAM_inotify_demonitor_item (path, (__u32) rt->FAMreq); // printd (DEBUG, "(as part of a suspension)"); } } */ /** @brief setup monitoring of config file Set up with data = 3, to distinguish this from panes 1 & 2 monitoring @return TRUE if the connection was successfully established */ gboolean e2_fs_FAM_monitor_config (void) { gchar *config_file = g_build_filename (e2_cl_options.config_dir, default_config_file, NULL); app.FAMreq = _e2_fs_FAM_inotify_monitor_item (config_file, INCFG_FLAGS); //setup to check for irrelevant reports on this file // if (localconfig == NULL) // localconfig = D_FILENAME_TO_LOCALE (config_file); // stat (localconfig, &cfgstatbuf); //ok to traverse a link FIXME vfs // gboolean result = (app.FAMreq < 0); // if (result) // printd (DEBUG, "FAM init for %s succeeded", config_file); // else // printd (WARN, "FAM init for %s failed", config_file); g_free (config_file); // return result; return (app.FAMreq < 0); } /** @brief cancel monitoring of config file @return TRUE if the connection was successfully removed */ gboolean e2_fs_FAM_cancel_monitor_config (void) { gchar *config_file = g_build_filename (e2_cl_options.config_dir, default_config_file, NULL); gboolean result = _e2_fs_FAM_inotify_demonitor_item (config_file, (__u32) app.FAMreq); if (result) { printd (DEBUG, "FAM cancel for %s succeeded", config_file); } else printd (WARN, "FAM cancel for %s failed", config_file); g_free (config_file); return result; } /** @brief poll monitor to check if any monitored thing has changed This just updates flags, to signal that refresh is needed For a pane, update is needed if any directory content has changed, or if the parent dir is not accessible/readable or gone A missing config file is not reported (as we can't reload it anyway) @param p1altered pointer to location to store T/F for whether pane 1 needs refresh @param p2altered pointer to location to store T/F for whether pane 2 needs refresh @param cfgaltered pointer to location to store T/F for whether config needs refresh @return */ void e2_fs_FAM_poll (gboolean *p1altered, gboolean *p2altered, gboolean *cfgaltered) { // static E2_FAMReport *results = NULL; if (++access_counter == 5) //report on accesses every 5th check access_counter = 0; //suspend periodic polling of pending reports quene if (app.timers[FAMPOLL_T] > 0) { g_source_remove (app.timers[FAMPOLL_T]); app.timers[FAMPOLL_T] = 0; } //and poll now, under our control _e2_fs_FAM_inotify_read (NULL); *p1altered = *p2altered = *cfgaltered = FALSE; // if (g_hash_table_size (report_hash) == 0) E2_BLOCK gboolean none = ( reports[0][REPORT_COUNT] == 0 && reports[1][REPORT_COUNT] == 0 && reports[2][REPORT_COUNT] == 0 && reports[0][ACCESS_COUNT] == 0 && reports[1][ACCESS_COUNT] == 0 && reports[2][ACCESS_COUNT] == 0 ); E2_UNBLOCK if (none) { //resume polling the queue app.timers[FAMPOLL_T] = g_timeout_add (POLL_MILLISECONDS, (GSourceFunc) _e2_fs_FAM_inotify_read, NULL); return; } /* if (results == NULL) { results = ALLOCATE (E2_FAMReport); CHECKALLOCATEDWARN (results, return;) } results->p1altered = p1altered; results->p2altered = p2altered; results->cfgaltered = cfgaltered; g_hash_table_foreach_remove (report_hash, (GHRFunc) _e2_fs_FAM_inotify_poll, results); */ //interrogate reports logs gint i; E2_BLOCK for (i=0; i<3; i++) { if ( (reports[i][REPORT_COUNT] > 0 || (access_counter == 0 && reports[i][ACCESS_COUNT] > 0)) && reports[i][REPORT_WD] != WD_UNUSED) { #ifdef EXTRA_MESSAGES printd (DEBUG, "%d total reports for wd %d", reports[i][REPORT_COUNT]+reports[i][ACCESS_COUNT], reports[i][REPORT_WD]); #endif gint wd = reports[i][REPORT_WD]; if (wd == app.pane1.FAMreq) { *p1altered = TRUE; //mirrored panes get the same status if (app.pane2.FAMreq == -1) *p2altered = TRUE; } else if (wd == app.pane2.FAMreq) { *p2altered = TRUE; //mirrored panes get the same status if (app.pane1.FAMreq == -1) *p1altered = TRUE; } else if (wd == app.FAMreq) { *cfgaltered = TRUE; } reports[i][REPORT_COUNT] = 0; if (access_counter == 0) reports[i][ACCESS_COUNT] = 0; } } E2_UNBLOCK app.timers[FAMPOLL_T] = g_timeout_add (POLL_MILLISECONDS, (GSourceFunc) _e2_fs_FAM_inotify_read, NULL); if (*p1altered) printd (DEBUG, "pane-1 change reported"); if (*p2altered) printd (DEBUG, "pane-2 change reported"); // if (*cfgaltered) // printd (DEBUG, "config change reported"); return; } #endif //def E2_FAM_INOTIFY emelfm2-0.4.1/src/filesystem/e2_fs_walk.c0000600000175000017500000006176610721373142017162 0ustar cairocairo/* $Id: e2_fs_walk.c 744 2007-11-22 21:36:34Z tpgww $ Copyright (C) 2005-2007 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/filesystem/e2_fs_walk.c @brief tree-walk functionality, like nftw() but with user-data and additional flags */ /** \page treewalks recursive file operations Function e2_fs_tw() is comparable to nftw(), but with user data (to avoid global variables in callback functions, so threads can be used) and extra parameters (types of status-report provided to callback functions and types of return flags from callback functions). e2_fs_tw() may be called recursively, from any downstream function. To instruct e2_fs_tw() how to perform its function, you may supply any one or more of these flags: E2TW_PHYS perform physical walk, don't look though symlinks E2TW_MOUNT report only items on same file system as the starting item E2TW_DEPTH open and process any subdir as soon as it's found, instead of after all non-subdirs have been processed (UNUSED) E2TW_ONLYDIR no F, SL or SLN reports - report only directories and stat() fails E2TW_NODIR no D, DRR or DP reports - report only non-directories and stat() fails (NS) and dirs not-opened (DL, DM, DNR) E2TW_FIXDIR if possible, repair DNR situations and then report DRR instead of DNR E2TW_QT suppress in-walker error messages e.g. after intentional DL E2TW_XQT suppress ALL in-walker error messages E2TW_XERR (when E2_VFS is defined) the first item in user_data is a GError** for returning downstream error data E2TW_DC issue a DP report after some sorts of in-walker errors, and after a callback returns STOP or a nested call returns STOP Only the first 3 of these are in nftw(). Unlike nftw(), there is no CHDIR option, as that's bad for multi-app and multi-thread contexts. So absolute paths must always be used. The effect of E2TW_DEPTH is really only on the order of "discovery", and so, on which items have been processed after an error causes premature termination of a walk. At this time, no walk is depth-first, and the code for that is disabled. As appropriate, status report code(s) provided to the specified callback function will be: E2TW_F item is not a directory or link E2TW_SL symbolic link to a file other than a directory E2TW_SLN symbolic link whose target file is non-existent E2TW_D directory E2TW_DL directory, not opened due to tree-depth limit E2TW_DM directory, not opened due to different file system E2TW_DNR unreadable directory E2TW_DP directory processed (all its contents have been reported) E2TW_NS un-stattable item E2TW_DRR formerly-unreadable directory, now repaired E2TW_D or E2TW_DRR, and E2TW_DP, will both be reported for each _opened_ directory (unless processing is aborted by a STOP or SKIP_SUBTREE message from the callback, from the D or DRR report, or aborted by some error in the walker itself). E2TW_DP will not be reported after E2TW_DL, E2TW_DM or E2TW_DNR reports. After a E2TW_DRR report, a DRRKEEP message has the same effect as CONTINUE If a walk is aborted without a E2TW_DP report, any data parked by the callback will leak unless it's cleaned by the parent routine, after the walk is finished. Callbacks can report E2TW_CLEAN, which (after a F, SL, SLN, D or DRR report) will trigger a DP callback and then stop. In a depth-first walk at least, that will probably still leak unless there is a post-walk cleanup. [Any??] in-walker error during a breadth-first walk will trigger a [DP] report to allow local cleanup, and then stop. Since the order of opening items in a dir is arbitrary, in breadth-first mode the the walker defers processing of all subdirs until all non-subdirs are done. e2_fs_tw() assumes that gtk's BGL is open/off, for the purposes of managing display updating (error messages etc). Downstream callbacks must assume that too. Error messages are displayed by the walker when E2TW_DL, E2TW_DM, E2TW_DNR, E2TW_NS conditions arise, among other things, a relevant "quiet" flag applies to the walk. */ #include "e2_fs.h" #include /* twstat pointer to function to be used for stat oprations (e2_fs_lstat() for E2TW_PHYS walks, e2_fs_stat() for others) cbfunc pointer to the function to be called for each located item user_data void pointer to user-specified data to be supplied to cbunc each time it's called (with GError** as its first member if E2TW_XERR is set) max_depth the largest number of tree levels to descend, or -1 for no limit exec_flags flags dictating the way the walk is to be performed device the system on which the starting item resides original_path newly-allocated localised path string, = CWD when the walk is started */ typedef struct _E2_TwData { #ifdef E2_VFS gint (*twstat) (VPATH *, struct stat *, GError **); #else gint (*twstat) (const gchar *, struct stat *); #endif E2_TwResult (*cbfunc) (VPATH *, const struct stat *, E2_TwStatus, gpointer); gpointer user_data; gint max_depth; //-1 for unlimited E2_FsReadWatch watchdir; E2_TwFlags exec_flags; dev_t device; } E2_TwData; /** @brief add to @a localpath any permission(s) in @a howflags that do not already apply Intended mainly for dirs but will work for non-dirs too Any error message expects BGL open NOTE for GRP and OTH this does not work, for dirs at least @param localpath absolute path of item to process, localised string @param statptr pointer to statbuf for @a localpath, as provided to the treewalk cb func @param howflags desired permission flags, R_OK W_OK and/or X_OK @return mode of @a localpath, not masked with ALLPERMS, maybe same as current mode, 0 after error */ mode_t e2_fs_tw_adjust_dirmode (VPATH *localpath, const struct stat *statptr, gint howflags) { E2_ERR_DECLARE if (!e2_fs_access3 (localpath, howflags E2_ERR_PTR())) return statptr->st_mode; E2_ERR_CLEAR mode_t pflags = 0; uid_t id = getuid (); if (id == 0 || id == statptr->st_uid) { if (howflags & R_OK) pflags |= S_IRUSR; if (howflags & W_OK) pflags |= S_IWUSR; if (howflags & X_OK) pflags |= S_IXUSR; } //NB for GRP and OTH this does not work, for dirs at least else if ((statptr->st_mode & S_IWGRP) //must already be group-writable for a member to change it && e2_fs_ingroup (statptr->st_gid)) { if (howflags & R_OK) pflags |= S_IRGRP; if (howflags & X_OK) pflags |= S_IXGRP; } else if (statptr->st_mode & S_IWOTH)//must already be other-writable for another to change it { if (howflags & R_OK) pflags |= S_IROTH; if (howflags & X_OK) pflags |= S_IXOTH; } mode_t newmode = 0; //assignment for warning prevention if (pflags != 0) newmode = statptr->st_mode | pflags; if (pflags == 0 //nothing to change || e2_fs_chmod (localpath, newmode & ALLPERMS E2_ERR_PTR())) { //X permission on dir when trying to process its contents gchar *fmt = ((howflags & X_OK) && S_ISDIR (statptr->st_mode)) ? _("Cannot change anything in %s") : _("Cannot change permissions of %s"); e2_fs_error_local (fmt, localpath E2_ERR_MSGL()); E2_ERR_CLEAR newmode = 0; } return newmode; } /** @brief determine the appropriate status report code for link @a local With relative symlinks, stat() generally fails, and anyway, can't distinguish valid links from hanging ones. Nor can lstat(). So this uses a kludgy but robust alternative approach @param localpath absolute path of link-item to process, localised string @return callbck status code in accordance with whether the link is valid */ static E2_TwResult _e2_fs_tw_link (VPATH *localpath) { E2_TwStatus code; gchar buf[PATH_MAX]; gint read = e2_fs_readlink (localpath, buf, sizeof (buf) E2_ERR_NONE()); if (read == -1) { code = E2TW_SLN; } else { buf[read] = '\0'; gchar *converted1 = F_FILENAME_FROM_LOCALE (VPSTR(localpath)); gchar *converted2 = F_FILENAME_FROM_LOCALE (buf); gchar *targetpath = e2_utils_translate_relative_path (converted1, converted2); F_FREE (converted1); F_FREE (converted2); //targetpath has a trailing /, with which access() can't cope *(targetpath + strlen (targetpath) - sizeof (gchar)) = '\0'; converted1 = F_FILENAME_TO_LOCALE (targetpath); //access() looks through links #ifdef E2_VFS VPATH data = { converted1, localpath->spacedata }; code = (e2_fs_access (&data, F_OK E2_ERR_NONE())) ? E2TW_SLN : E2TW_SL; #else code = (e2_fs_access (converted1, F_OK E2_ERR_NONE())) ? E2TW_SLN : E2TW_SL; #endif g_free (targetpath); F_FREE (converted1); } return code; } /** @brief process 'non-directory' item @a localpath, as part of a treewalk This determines the appropriate status report code for the callback, then calls it @a localpath may be the path of a link, regular file, etc @a localpath will have been stat'd or lstat'd (depending on walk-flag E2TW_PHYS) into @a statptr, before this function is called @param localpath absolute path of item to process, localised string @param statptr pointer to struct stat used for stating @a local @param twdata pointer to struct with 'constant' data for each call here @return result code in accordance with callback-function response */ static E2_TwResult _e2_fs_tw_nondir (VPATH *localpath, const struct stat *statptr, E2_TwData *twdata) { E2_TwStatus code; if (twdata->exec_flags & E2TW_PHYS) { //the prior lstat did not look through a link //or the local lstat failed //in general, relative links (valid or not) will end up here code = (S_ISLNK (statptr->st_mode)) ? _e2_fs_tw_link (localpath) : E2TW_F; } else { //the prior stat() processed a non-link, //or might, or might not, have successfully processed a link //check if it was a link struct stat statbuf2; //don't disturb statptr if (e2_fs_lstat (localpath, &statbuf2 E2_ERR_NONE())) code = E2TW_NS; //lstat failed else code = (S_ISLNK (statbuf2.st_mode)) ? _e2_fs_tw_link (localpath) : E2TW_F; } return ((*twdata->cbfunc) (localpath, statptr, code, twdata->user_data)); } /** @brief process directory @a localpath, as part of a treewalk if possible, and in accord with the walk-mode determined by flags in @a twdata, @a localpath is opened and its contents are listed and each listed item is 'reported' to the callback function recorded in @a twdata. Any subdir found among the contents will recurse into this function. Non-dirs are handled by another function, before coming here or when found here. @param localpath absolute path of _directory_ to process, localised string, with or without trailing "/" @param depth enumator of current parent's level in the tree (relative to the original parent), 0 to max_depth (if the latter is not -1) @param twdata pointer to struct with walk-parameters including callback and its data @return result code in accordance with callback-function returns, E2TW_STOP or E2TW_CONTINUE if all was well */ static E2_TwResult _e2_fs_tw_dir (VPATH *localpath, gint depth, E2_TwData *twdata) { //FIXME support an exit-code for cleanup-this-level then stop E2_TwResult result; gint newdepth, localpathlen; guint pathbuflen, bufspace; //CHECKME want these flags to be variable by any callback function ? gboolean reportdirs = !(twdata->exec_flags & E2TW_NODIR); gboolean reportnondirs = !(twdata->exec_flags & E2TW_ONLYDIR); gboolean repaired = FALSE; //unreadable dir has been fixed mode_t firstmode = 0; //original mode of an un-processable dir mode_t newmode = 0; //fixed mode of an un-processable dir, for possible reinstatement gchar *itempath, *itemname; GList *entries, *parked_dirs, *member; #ifdef E2_VFS VPATH ddata; GError **callerr; E2_ERR_DECLARE #endif struct stat statbuf; //assume it's ok to recursively stack this much data //populate a local statbuf, if we can if ((*twdata->twstat) (localpath, &statbuf E2_ERR_PTR())) { //failed (statbuf will be garbage for this call) //since cbfunc doesn't have access to the error data, as a //convenience we report to the user unless instructed otherwise if (!(twdata->exec_flags & E2TW_XQT)) e2_fs_error_local (_("Cannot get information about %s"), localpath E2_ERR_MSGL()); result = (*twdata->cbfunc) (localpath, &statbuf, E2TW_NS, twdata->user_data); #ifdef E2_VFS if (twdata->exec_flags & E2TW_XERR) { callerr = (GError **)twdata->user_data; if (callerr != NULL && *callerr == NULL) //valid, unused error data *callerr = E2_ERR_NAME; } else E2_ERR_CLEAR #endif //for an un-stattable item, bias to STOP return ((result == E2TW_CONTINUE) ? E2TW_CONTINUE : E2TW_STOP); } //check whether it's ok to open the dir if ((twdata->exec_flags & E2TW_MOUNT) && twdata->device != statbuf.st_dev) { if (!(twdata->exec_flags & (E2TW_QT | E2TW_XQT))) e2_fs_error_simple (_("Directory %s not opened"), localpath); result = (*twdata->cbfunc) (localpath, &statbuf, E2TW_DM, twdata->user_data); return ((result & E2TW_STOP) ? E2TW_STOP : E2TW_CONTINUE); //unwanted filesystem, would expect normally to continue } newdepth = depth + 1; if (twdata->max_depth != -1 && newdepth > twdata->max_depth) { if (!(twdata->exec_flags & (E2TW_QT | E2TW_XQT))) e2_fs_error_simple (_("Directory %s not opened"), localpath); result = (*twdata->cbfunc) (localpath, &statbuf, E2TW_DL, twdata->user_data); return ((result & E2TW_STOP) ? E2TW_STOP : E2TW_CONTINUE); //max depth reached, would expect normally to continue } //create a re-usable path-string buffer //since this function may recurse indefinitely, buffer is on heap, not stack localpathlen = strlen (VPSTR(localpath)); //space to append a FileInfo->filename (NAME_MAX+1), plus a few chars (/ or 0) pathbuflen = (localpathlen + NAME_MAX + 1)/8*8 + 8; #ifdef USE_GLIB2_10 itempath = g_slice_alloc ((gulong) pathbuflen); #else itempath = g_try_malloc ((gulong) pathbuflen); #endif // CHECKALLOCATEDWARNT (itempath, return E2TW_STOP;) //CHECKME support more-complex response(s) to this condition ? if (itempath == NULL) { gdk_threads_enter (); e2_utils_show_memory_message (); gdk_threads_leave (); return E2TW_STOP; } //get list of item-names in the dir entries = (GList *)e2_fs_dir_foreach (localpath, twdata->watchdir, NULL, NULL, NULL E2_ERR_PTR()); //check whether it was possible to open the dir if (E2DREAD_FAILED (entries)) { if (twdata->exec_flags & E2TW_FIXDIR) { //we want to try fixing DNR instances firstmode = statbuf.st_mode; newmode = e2_fs_tw_adjust_dirmode (localpath, &statbuf, (R_OK | X_OK)); if (newmode != firstmode && newmode != 0) { //try again entries = (GList *)e2_fs_dir_foreach (localpath, twdata->watchdir, NULL, NULL, NULL E2_ERR_PTR()); } } //was 1st or possible 2nd attempted read successful ? if (E2DREAD_FAILED (entries)) { if (!(twdata->exec_flags & E2TW_XQT)) e2_fs_error_local (_("Cannot open directory %s"), localpath E2_ERR_MSGL()); result = (*twdata->cbfunc) (localpath, &statbuf, E2TW_DNR, twdata->user_data); #ifdef E2_VFS if (twdata->exec_flags & E2TW_XERR) { callerr = (GError **)twdata->user_data; if (callerr != NULL && *callerr == NULL) //valid, unused error data *callerr = E2_ERR_NAME; } else E2_ERR_CLEAR #endif #ifdef USE_GLIB2_10 g_slice_free1 (pathbuflen, itempath); #else g_free (itempath); #endif return ((result & E2TW_STOP) ? E2TW_STOP : E2TW_CONTINUE); } repaired = TRUE; } if (reportdirs) { result = (*twdata->cbfunc) (localpath, &statbuf, (repaired) ? E2TW_DRR : E2TW_D, twdata->user_data); /* if (result & E2TW_CLEAN) //CHECKME is this reasonable after a D/DRR ? { result |= ((*twdata->cbfunc) (localpath, &statbuf, E2TW_DP, twdata->user_data)); result |= E2TW_STOP; //ensure a STOP } */ if (repaired && (result & E2TW_DRKEEP)) repaired = FALSE; //prevent any later mode reversion if (result & (E2TW_STOP | E2TW_SKIPSUB)) { if (repaired) e2_fs_chmod (localpath, firstmode E2_ERR_NONE()); e2_list_free_with_data (&entries); return (result & E2TW_STOP) ? E2TW_STOP : E2TW_CONTINUE; } } //==== DIR HAS BEEN READ & REPORTED AS SUCH #ifdef __USE_GNU itemname = mempcpy (itempath, VPSTR (localpath), localpathlen); #else memcpy (itempath, VPSTR (localpath), localpathlen); itemname = itempath + localpathlen; #endif bufspace = pathbuflen - localpathlen; if (*(itemname - sizeof (gchar)) != G_DIR_SEPARATOR) //ascii ok { *itemname = G_DIR_SEPARATOR; //ascii ok itemname++; bufspace--; } //for logging dirs in breadth-first mode, to be processed when we're ready //to open them, after all non-dirs at this level have been processed parked_dirs = NULL; #ifdef E2_VFS ddata.localpath = itempath; ddata.spacedata = localpath->spacedata; #endif //walk the list of entries in the dir (tree-depth now = newdepth) for (member = entries; member != NULL ; member = member->next) { if (strcmp ((gchar *)member->data, "..") == 0) continue; g_strlcpy (itemname, (gchar *) member->data, bufspace); #ifdef E2_VFS if ((*twdata->twstat) (&ddata, &statbuf E2_ERR_PTR())) #else if ((*twdata->twstat) (itempath, &statbuf E2_ERR_PTR())) #endif { //process stat failure //NOTE statting a hanging link to a dir does NOT fail here, glib BUG ? if (!(twdata->exec_flags & E2TW_XQT)) e2_fs_error_local (_("Cannot get information about %s"), #ifdef E2_VFS &ddata E2_ERR_MSGL()); #else itempath E2_ERR_MSGL()); #endif #ifdef E2_VFS result = (*twdata->cbfunc) (&ddata, &statbuf, E2TW_NS, twdata->user_data); if (twdata->exec_flags & E2TW_XERR) { callerr = (GError **)twdata->user_data; if (callerr != NULL && *callerr == NULL) //valid, unused error data *callerr = E2_ERR_NAME; } else E2_ERR_CLEAR #else result = (*twdata->cbfunc) (itempath, &statbuf, E2TW_NS, twdata->user_data); #endif if ((result & E2TW_CLEAN) && reportdirs) { result |= ((*twdata->cbfunc) (localpath, &statbuf, E2TW_DP, twdata->user_data)); result |= E2TW_STOP; //ensure a STOP } if (repaired && (result & E2TW_DRKEEP)) repaired = FALSE; //prevent any later mode reversion if (result & (E2TW_STOP | E2TW_SKIPSUB)) { if (repaired) e2_fs_chmod (localpath, firstmode E2_ERR_NONE()); if (parked_dirs != NULL) g_list_free (parked_dirs); e2_list_free_with_data (&entries); #ifdef USE_GLIB2_10 g_slice_free1 (pathbuflen, itempath); #else g_free (itempath); #endif return (result & E2TW_STOP) ? E2TW_STOP : E2TW_CONTINUE; } } else if (S_ISDIR (statbuf.st_mode)) { //stattable dir or (present) link-target that's a dir /* if (twdata->exec_flags & E2TW_DEPTH) { //depth-first walk, open the dir now //(start- and end-reports issued in the recursed function) if (_e2_fs_tw (itempath, newdepth, twdata) != E2TW_CONTINUE) { //NOTE no DP report for this dir if aborted downstream //i.e. NO CLEAN CURRENT LEVEL //no parks to cleanup if depth-1st walk e2_list_free_with_data (&entries); #ifdef USE_GLIB2_10 // g_slice_free1 (itempathbuflen, itempath); g_slice_free1 (buflen, itempath); #else g_free (itempath); #endif FIXME more flags processing return E2TW_STOP; } } else { */ //breadth-first walk //log the subdir for processing after all non-subdirs parked_dirs = g_list_prepend (parked_dirs, GINT_TO_POINTER (g_list_position (entries, member))); // } } else //not a dir (it could be a hanging link to a dir) if (reportnondirs) { #ifdef E2_VFS result = _e2_fs_tw_nondir (&ddata, &statbuf, twdata); #else result = _e2_fs_tw_nondir (itempath, &statbuf, twdata); #endif if ((result & E2TW_CLEAN) && reportdirs) { result |= ((*twdata->cbfunc) (localpath, &statbuf, E2TW_DP, twdata->user_data)); result |= E2TW_STOP; //ensure a STOP } if (repaired && (result & E2TW_DRKEEP)) repaired = FALSE; //prevent later mode reversion if (result & E2TW_STOP) //no SKIPSUB recognised here { if (repaired) e2_fs_chmod (localpath, firstmode E2_ERR_NONE()); if (parked_dirs != NULL) g_list_free (parked_dirs); e2_list_free_with_data (&entries); #ifdef USE_GLIB2_10 g_slice_free1 (pathbuflen, itempath); #else g_free (itempath); #endif return E2TW_STOP; } } } //in a breadth-first walk, process the dirs that haven't yet been opened //(all of them, as their order is arbitrary) result = 0; for (member = parked_dirs ; member != NULL ; member = member->next) { gint indx = GPOINTER_TO_INT (member->data); gchar *parkname = g_list_nth_data (entries, indx); g_strlcpy (itemname, parkname, bufspace); #ifdef E2_VFS result |= (_e2_fs_tw_dir (&ddata, newdepth, twdata)); #else result |= (_e2_fs_tw_dir (itempath, newdepth, twdata)); #endif } if (parked_dirs != NULL) g_list_free (parked_dirs); e2_list_free_with_data (&entries); #ifdef USE_GLIB2_10 g_slice_free1 (pathbuflen, itempath); #else g_free (itempath); #endif gboolean downfailed = (result & E2TW_STOP); /* //FIXME support cleanup then stop if (result & ?) .... if (result & E2TW_STOP) { if (repaired) e2_fs_chmod (localpath, firstmode E2_ERR_NONE()); return E2TW_STOP; } */ //==== FINISHED DIR-WALK //freshen the statbuf //(all downstream uses of the buffer are const, so shouldn't be necessary //except as another existence check ?) if ((*twdata->twstat) (localpath, &statbuf E2_ERR_PTR())) { //stat failed (and advised as such) //can't issue valid DP report CHECKME do an invalid one ? if (reportdirs && (twdata->exec_flags & E2TW_DC)) (*twdata->cbfunc) (localpath, &statbuf, E2TW_DP, twdata->user_data); if (repaired) //perms changed prior to DRR report and cb did not want this ignored { //after stat() failure, this will probably fail too e2_fs_chmod (localpath, firstmode E2_ERR_NONE()); } #ifdef E2_VFS if (twdata->exec_flags & E2TW_XERR) { callerr = (GError **)twdata->user_data; if (callerr != NULL && *callerr == NULL) //valid, unused error data *callerr = E2_ERR_NAME; } else E2_ERR_CLEAR #endif return E2TW_STOP; } else //final stat succeeded { if (reportdirs) { //issue dir-end report result = (*twdata->cbfunc) (localpath, &statbuf, E2TW_DP, twdata->user_data); if (repaired && (result & E2TW_DRKEEP)) repaired = FALSE; //prevent later mode reversion } else result = (downfailed) ? E2TW_STOP : E2TW_CONTINUE; if (repaired) //perms changed prior to DRR report and no cb wanted this ignored { if (e2_fs_chmod (localpath, firstmode E2_ERR_PTR())) { if (!(twdata->exec_flags & E2TW_XQT)) e2_fs_error_local (_("Cannot change permissions of %s"), localpath E2_ERR_MSGL()); #ifdef E2_VFS if (twdata->exec_flags & E2TW_XERR) { callerr = (GError **)twdata->user_data; if (callerr != NULL && *callerr == NULL) //valid, unused error data *callerr = E2_ERR_NAME; } else E2_ERR_CLEAR #endif return E2TW_STOP; } } } return (result & E2TW_STOP) ? E2TW_STOP : E2TW_CONTINUE; } /** @brief locate and report all items in directory tree under @a start_dir Potential error message expects gtk's BGL to be open. Downstream callbacks must assume that too. @param start_item absolute path of item (dir or non-dir) on|in which to start the walk, localised string @param callback pointer to the function to be called for each located item @param user_data pointer to user-specified data to be supplied to each call to @a callback @param max_depth the largest number of tree levels to descend, or -1 for no limit @param exec_flags flags indicating the way the walk is to be performed @return TRUE if @a start_item (and any descendants) was processed in way(s) that @a callback considered ok */ gboolean e2_fs_tw (VPATH *start_item, E2_TwResult (*callback) (), gpointer user_data, gint max_depth, E2_TwFlags exec_flags E2_ERR_ARG()) { E2_TwResult result; struct stat statbuf; gint (*twstat) (VPATH *, struct stat * #ifdef E2_VFS , GError ** #endif ); E2_ERR_BACKUP (localerr); twstat = (exec_flags & E2TW_PHYS) ? e2_fs_lstat : e2_fs_stat; if ((twstat) (start_item, &statbuf E2_ERR_SAMEARG())) { if (!(exec_flags & E2TW_XQT)) e2_fs_error_local (_("Cannot get information about %s"), start_item E2_ERR_MSGC()); result = E2TW_STOP; } else { E2_FsReadWatch watchdir = (S_ISDIR (statbuf.st_mode) && e2_fs_mount_is_fusepoint (start_item)) ? E2_DIRWATCH_YES : E2_DIRWATCH_NO; //setup walk-data for downstream functions E2_TwData data = { twstat, callback, user_data, max_depth, watchdir, exec_flags, statbuf.st_dev }; result = (S_ISDIR (statbuf.st_mode)) ? _e2_fs_tw_dir (start_item, 0, &data): _e2_fs_tw_nondir (start_item, &statbuf, &data); //should never be needed, but ... } #ifdef E2_VFS if (exec_flags & E2TW_XERR) { GError **callerr = (GError **)user_data; if (callerr != NULL && *callerr != NULL) //valid, unused error data *E2_ERR_NAME = *callerr; } #endif E2_ERR_CLEARBACKUP (localerr); return (result == E2TW_CONTINUE); } emelfm2-0.4.1/src/filesystem/e2_fs_FAM_dnotify.c0000600000175000017500000005237010655523144020357 0ustar cairocairo/* $Id: e2_fs_FAM_dnotify.c 574 2007-08-06 04:41:08Z tpgww $ Copyright (C) 2005-2007 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/filesystem/e2_fs_FAM_dnotify.c @brief functions related to file-alteration monitoring using dnotify on linux */ #include "emelfm2.h" #ifdef E2_FAM_DNOTIFY #include #include #include //#define EXTRA_MESSAGES //#define SIMPLECOUNT #ifdef SIMPLECOUNT static guint debugcount = 0; #endif //SIGRTMIN is "often blocked" //#define DNOTIFY_SIGNAL SIGRTMIN #define DNOTIFY_SIGNAL SIGRTMIN+1 sigset_t dnotify_signal_set; //the types of changes about which dnotify will report //NOTE these relate to items in the dir, seems like some //eg DN_RENAME do not apply to the dir itself #define DNOTIFY_FLAGS \ DN_ACCESS | DN_MODIFY | DN_CREATE | DN_DELETE | \ DN_RENAME | DN_ATTRIB | DN_MULTISHOT //value to be stored in array to signal an unused column #define FD_UNUSED 0 enum { REPORT_FD, REPORT_REF, REPORT_COUNT, REPORT_ROWS }; /*store for logging reports and associated stuff for up to 3 fd's (1 or 2 pane-dirs and config parent) Rows are used as per the enum above*/ static gint reports [3][REPORT_ROWS] = { {FD_UNUSED}, {FD_UNUSED}, {FD_UNUSED}}; //utf-8 pathnames for each monitored item static gchar *paths[3]; //these are for polling config file changes static gchar *localconfig; static struct stat cfgstatbuf; //define this when doing client-determined polling for received reports #define DNOTIFY_POLLING //otherwise, these support server-determined report-processing #ifndef DNOTIFY_POLLING /*Max. no of dnotify reports that we can handle here, in any batch If polling is not in force, then the received reports will be processed when there's no more pending, or when this many reports are received, whichever is sooner. Note that multiple reports can be generated for each changed item in a dir*/ #define MAX_REPORTS 1024 static GIOChannel *dnotify_read_ioc; static GIOChannel *dnotify_write_ioc; //FIXME make this something pre-emptive //G_LOCK_DEFINE (e2_dnotify); static GSource *dnotify_src; static guint dnotify_src_id; #endif /** @brief Read data piped by dnotify This is a callback for the pipe-watch source Batchwise retrieves a stream of the accumulated strings piped to dnotify by the signal handler. It is called repeatedly until the stream is finished. @param user_data UNUSED pointer to data specified when callback was connected @return TRUE, so the source remains intact */ #ifndef DNOTIFY_POLLING static gboolean _e2_fs_FAM_dnotify_pipe_handler (gpointer user_data) { /*buffer receives a stream comprising the accumulated strings piped back to dnotify, at the end of each signal-handler function call buffer size determines the user-report batch-frequency. Presently relies on the fact that piped feedback is 1 byte per report */ gchar buffer[MAX_REPORTS]; gsize buffer_bytes; //G_LOCK (e2_dnotify); g_io_channel_read_chars (dnotify_read_ioc, buffer, sizeof(buffer), &buffer_bytes, NULL); //G_UNLOCK (e2_dnotify); // printd (DEBUG, "pipe handler read %d bytes", buffer_bytes); //FIXME process the batch of received reports here, and issue reports to client return TRUE; } #endif /** @brief Handle dnotify-related signal Logs a report for the fd, for later use @param sig the signal to handle @param si pointer to signal data struct @param sig_data pointer to signal data */ static void _e2_fs_FAM_dnotify_mainsignal_handler (gint sig, siginfo_t *si, void *sig_data) { #ifdef SIMPLECOUNT debugcount++; #else //log the report, (atomic, signal-proof) register gint i, r = si->si_fd; E2_BLOCK for (i=0; i<3; i++) { if (reports[i][REPORT_FD] == r) { reports[i][REPORT_COUNT]++; break; } } E2_UNBLOCK #if defined(DEBUG_MESSAGES) && defined(EXTRA_MESSAGES) gchar *dir; # ifdef E2_VFSTMP //dir when path is non-mounted # else if (si->si_fd == curr_pane->FAMreq) dir = curr_pane->path; else if (si->si_fd == other_pane->FAMreq) dir = other_pane->path; # endif else dir = "config file"; printd (DEBUG, "dnotify signal for %s", dir); #endif #endif //def SIMPLECOUNT #ifndef DNOTIFY_POLLING /* Update the queue which causes the pipe-handler to be called CHECKME any merit in sending back a more-informative string? Later on, we get it piped back (as part of a stream) into the read buffer ... So if we change the string here, remember to change the read buffer size accordingly, to preserve the desired batch size */ g_io_channel_write_chars (dnotify_write_ioc, "+", 1, NULL, NULL); g_io_channel_flush (dnotify_write_ioc, NULL); #endif } /** @brief Handle dnotify-related signal-queue-full signal @param sig the signal to handle @param si pointer to signal data struct @param sig_data pointer to signal data */ static void _e2_fs_FAM_dnotify_overflowsignal_handler (gint sig, siginfo_t * si, void *sig_data) { //set all monitored items dirty register gint i, r = FD_UNUSED; E2_BLOCK for (i=0; i<3; i++) { if (reports[i][REPORT_FD] != r) reports[i][REPORT_COUNT]++; } E2_UNBLOCK printd (WARN, "OOPS !! dnotify signal queue overflow"); } /** @brief register directory @a path with dnotify This func can be called from within a threaded refresh @param path utf-8 string with path of item to be monitored @return the file descriptor for the dir, <0 for an error */ static gint _e2_fs_FAM_dnotify_monitor_dir (gchar *path) { //find if path is already monitored gint i, retval = -1; E2_BLOCK for (i=0; i<3; i++) { if (reports[i][REPORT_FD] != FD_UNUSED //e.g. not cancelled && paths[i] != NULL && g_str_equal (paths[i], path)) { //if so, just ref it reports[i][REPORT_REF]++; printd (DEBUG, "reffed dnotify monitoring of %s", path); retval = reports[i][REPORT_FD]; break; } } E2_UNBLOCK if (retval != -1) return retval; //otherwise register it with dnotify gchar *local = F_FILENAME_TO_LOCALE (path); gint fd = e2_fs_safeopen (local, O_RDONLY | O_NONBLOCK, 0); F_FREE (local); if (fd >= 0) { fcntl (fd, F_SETSIG, DNOTIFY_SIGNAL); if (!fcntl (fd, F_NOTIFY, DNOTIFY_FLAGS)) { //setup for report-logging E2_BLOCK for (i=0; i<3; i++) { if (reports[i][REPORT_FD] == FD_UNUSED) { reports[i][REPORT_FD] = fd; reports[i][REPORT_REF] = 1; reports[i][REPORT_COUNT] = 0; if (paths[i] != NULL) g_free (paths[i]); paths[i] = g_strdup (path); printd (DEBUG, "added dnotify monitoring of %s", path); retval = fd; break; } } E2_UNBLOCK if (retval != -1) return retval; } else //connection failed for some reason e2_fs_safeclose (fd); } printd (WARN, "failed to add dnotify monitoring of %s\n%s", path, strerror (errno)); return -1; } /** @brief de-register directory @a path with dnotify This func can be called from within a threaded refresh @param fd file descriptor number that is to be closed @return TRUE if cancellation succeeds */ static gboolean _e2_fs_FAM_dnotify_demonitor_dir (gint fd) { gboolean retval = FALSE; gint i; //check if dir not needed anymore E2_BLOCK for (i=0; i<3; i++) { if (reports[i][REPORT_FD] == fd) { if (reports[i][REPORT_REF] == 1) { fcntl (fd, F_NOTIFY, 0); if (!close (fd)) { reports[i][REPORT_FD] = FD_UNUSED; reports[i][REPORT_COUNT] = 0; //overkill ? cleared when monitoring starts printd (DEBUG, "removed dnotify monitoring of %s", paths[i]); retval = TRUE; break; } else { //close failed, need to re-register fcntl (fd, F_NOTIFY, DNOTIFY_FLAGS); printd (WARN, "failed to remove dnotify monitoring of %s", paths[i]); break; } } else { //just unref it reports[i][REPORT_REF]--; retval = TRUE; break; } } } E2_UNBLOCK return retval; } /** @brief Initialize dnotify monitoring. @return TRUE if initialization succeeded */ static gboolean _e2_fs_FAM_dnotify_init (void) { struct sigaction act; #ifndef DNOTIFY_POLLING //setup to get piped signal when it's time to process the received reports gint fds[2]; if (pipe (fds) < 0) { printd (WARN,"Could not create dnotify pipe"); return FALSE; } dnotify_read_ioc = g_io_channel_unix_new (fds[0]); g_io_channel_set_encoding (dnotify_read_ioc, NULL, NULL); g_io_channel_set_flags (dnotify_read_ioc, G_IO_FLAG_NONBLOCK, NULL); dnotify_write_ioc = g_io_channel_unix_new (fds[1]); g_io_channel_set_encoding (dnotify_write_ioc, NULL, NULL); g_io_channel_set_flags (dnotify_write_ioc, G_IO_FLAG_NONBLOCK, NULL); //FIXME make this something pre-emptive dnotify_src = g_io_create_watch (dnotify_read_ioc, G_IO_IN | G_IO_HUP | G_IO_ERR); g_source_set_callback (dnotify_src, _e2_fs_FAM_dnotify_pipe_handler, NULL, NULL); g_source_set_priority (dnotify_src, 100); g_source_set_can_recurse (dnotify_src, FALSE); dnotify_src_id = g_source_attach (dnotify_src, NULL); #endif //setup related signal-stuff act.sa_sigaction = _e2_fs_FAM_dnotify_mainsignal_handler; sigemptyset (&act.sa_mask); // sigaddset (&act.sa_mask, DNOTIFY_SIGNAL); act.sa_flags = SA_RESTART | SA_SIGINFO; sigaction (DNOTIFY_SIGNAL, &act, NULL); //catch SIGIO (which is issued when the queue fills) as well act.sa_sigaction = _e2_fs_FAM_dnotify_overflowsignal_handler; // sigemptyset (&act.sa_mask); sigaction (SIGIO, &act, NULL); //setup for signal [un]blocking // sigemptyset (&dnotify_signal_set); dnotify_signal_set = act.sa_mask; sigaddset (&dnotify_signal_set, DNOTIFY_SIGNAL); //don't block SIGIO return TRUE; } /** @brief cleanup after ending dnotify monitoring. @return */ static void _e2_fs_FAM_dnotify_abandon (void) { //don't bother cleaning up actual data, we're ending now //shutdown connections before closing channels gint i, fd; E2_BLOCK for (i=0; i<3; i++) { if ((fd = reports[i][REPORT_FD]) != FD_UNUSED) { fcntl (fd, F_NOTIFY, 0); close (fd); } } E2_UNBLOCK #ifndef DNOTIFY_POLLING GError *err = NULL; g_io_channel_shutdown (dnotify_read_ioc, FALSE, &err); g_io_channel_shutdown (dnotify_write_ioc, FALSE, &err); g_source_destroy (dnotify_src); #endif } /*************************/ /**** public functions ****/ /*************************/ /** @brief establish dnotify connection @return */ void e2_fs_FAM_connect (void) { app.monitor_type = (_e2_fs_FAM_dnotify_init ()) ? E2_MONITOR_FAM : E2_MONITOR_DEFAULT ; } /** @brief terminate dnotify connection session-end, no data freeing @return */ void e2_fs_FAM_disconnect (void) { if (app.monitor_type == E2_MONITOR_FAM) _e2_fs_FAM_dnotify_abandon (); } /** @brief change monitored directory if current-dir not also in the other pane, cancel current-dir monitoring if new-dir not also in other pane, start monitoring new-dir If new-dir is not monitored (FAM error or dir is already monitored in other pane) the FAM request for the pane is set to -1 @a olddir needs trailing '/', for valid comparing with rt->path etc @param olddir utf-8 string with absolute path of dir to stop monitoring @param rt data struct for the pane to which the cd applies, including the new dir in rt->path @return */ void e2_fs_FAM_change (gchar *olddir, E2_PaneRuntime *rt) { if (app.monitor_type == E2_MONITOR_DEFAULT) return; //cancel monitoring of current dir if that was happening //at session-start, both panes have rt->FAMreq == -1 if (rt->FAMreq != -1) { //use the request for this pane _e2_fs_FAM_dnotify_demonitor_dir (rt->FAMreq); //default flag = no-monitoring this pane rt->FAMreq = -1; } E2_PaneRuntime *ort = (rt == curr_pane) ? other_pane : curr_pane; #ifdef E2_VFSTMP //dir when path is non-mounted #else if (g_str_equal (olddir, ort->path)) #endif { //panes are showing same dir now //connect to the other pane if need be, to get correct pane id //when monitoring if (ort->FAMreq == -1) { #ifdef E2_VFSTMP //dir when path is non-mounted #else ort->FAMreq = _e2_fs_FAM_dnotify_monitor_dir (ort->path); // printd (DEBUG, "started monitoring of other dir %s", ort->path); #endif } } //now hoookup to the new dir, if it's not going already #ifdef E2_VFSTMP //dirs when path is non-mounted #else if (!g_str_equal (ort->path, rt->path)) //NB panes may be same at session start { //new dir not already monitored rt->FAMreq = _e2_fs_FAM_dnotify_monitor_dir (rt->path); // printd (DEBUG, "started monitoring of this dir %s", rt->path); } #endif else { rt->FAMreq = -1; // printd (DEBUG, "skipped monitoring of this dir %s", rt->path); } } /** @brief get rid of last pending change-report for directory @a path Used as part of filelist refreshing There is no actual change if the fd for the relevant pane is -1 which means there is no monitoring anyway @param path utf-8 string with path of dir to b processed @return */ void e2_fs_FAM_clean_reports (gchar *path) { if (app.monitor_type == E2_MONITOR_DEFAULT) return; usleep (20000); //wait for some possible interrupt(s) #ifdef E2_VFSTMP //FIXME dir when not mounted local #else gint fd = (g_str_equal (curr_view->dir, path)) ? #endif curr_pane->FAMreq : other_pane->FAMreq; if (fd != -1) { //dir was being monitored before //decrement its reports log gint i; E2_BLOCK for (i=0; i<3; i++) { if (reports[i][REPORT_FD] == fd) { if (reports[i][REPORT_COUNT] > 0) reports[i][REPORT_COUNT]--; break; } } E2_UNBLOCK } } /** @brief Maybe get rid of last pending change-report for directory NOT @a path Adjusts the report-count for the other pane, if that's the parent of @a path Used as part of filelist refreshing There is no actual change if the fd for the relevant pane is -1 which means there is no monitoring anyway @param path utf-8 string with path of dir to b processed @return */ /* void e2_fs_FAM_clean_other_reports (gchar *path) { if (app.monitor_type == E2_MONITOR_DEFAULT) return; usleep (20000); //wait for some possible interrupt(s) #ifdef E2_VFSTMP //FIXME dir when not mounted local #else gchar *otherdir = (g_str_equal (curr_view->dir, path)) ? other_view->dir : curr_view->dir; #endif if (g_str_has_prefix (path, otherdir)) { //make sure it's a direct parent gchar *s = path + strlen (otherdir); if (*s == '\0') otherdir = NULL; //paths are the same else { s = strchr (s, G_DIR_SEPARATOR); if (*(s+1) != '\0') otherdir = NULL; //there's more than 1 level between the 2 } } else otherdir = NULL; if (otherdir != NULL) { #ifdef E2_VFSTMP //FIXME dir when not mounted local #else fd = (g_str_equal (curr_view->dir, otherdir)) ? #endif curr_pane->FAMreq : other_pane->FAMreq; if (fd != -1) { //dir was being monitored before //clear its reports log gint i; E2_BLOCK for (i=0; i<3; i++) { if (reports[i][REPORT_FD] == fd) { if (reports[i][REPORT_COUNT] > 0) reports[i][REPORT_COUNT]--; break; } } E2_UNBLOCK } } } */ /** @brief begin dnotify monitoring of directory @a path Used as part of filelist refreshing (possibly threaded), as part of a pair with e2_fs_FAM_cancel_monitor_dir () Any prior reports for the dir are cleared There is no actual change if the fd for the relevant pane is -1 which means there was no monitoring before the cancellation @param path utf-8 string with path of dir to be resumed @return */ /*void e2_fs_FAM_monitor_dir (gchar *path) { E2_PaneRuntime *rt = #ifdef E2_VFSTMP //dir when path is non-mounted #else (g_str_equal (curr_pane->path, path)) ? #endif curr_pane : other_pane; if (rt->FAMreq != -1) { //dir was being monitored before //so start it again rt->FAMreq = _e2_fs_FAM_dnotify_monitor_dir (path); // printd (DEBUG, "(as part of a resumption)"); } } */ /** @brief cancel dnotify monitoring of directory @a path Used as part of filelist refreshing (possibly threaded), as part of a pair with e2_fs_FAM_monitor_dir () There is no actual change if the fd for the relevant pane is -1 which means there is no monitoring anyway @param path utf-8 string with path of dir to be suspended @return */ /*void e2_fs_FAM_cancel_monitor_dir (gchar *path) { E2_PaneRuntime *rt = #ifdef E2_VFSTMP //dir when path is non-mounted #else (g_str_equal (curr_pane->path, path)) ? #endif curr_pane : other_pane; if (rt->FAMreq != -1) { //dir was being monitored before _e2_fs_FAM_dnotify_demonitor_dir (rt->FAMreq); // printd (DEBUG, "(as part of a suspension)"); } } */ /** @brief setup monitoring of config file @return TRUE if the connection was successfully established */ gboolean e2_fs_FAM_monitor_config (void) { gchar *config_file = g_build_filename (e2_cl_options.config_dir, default_config_file, NULL); gchar *config_parent = g_path_get_dirname (config_file); gboolean retval = FALSE; if (!g_str_equal (config_parent, ".")) { app.FAMreq = _e2_fs_FAM_dnotify_monitor_dir (config_parent); if (app.FAMreq >= 0) retval = TRUE; } //setup to check for irrelevant reports about config file if (localconfig == NULL) localconfig = D_FILENAME_TO_LOCALE (config_file); stat (localconfig, &cfgstatbuf); //ok to traverse link, ok to fail FIXME vfs if (retval) printd (DEBUG, "dnotify init for %s succeeded", config_file); else printd (WARN, "dnotify init for %s failed", config_file); g_free (config_file); g_free (config_parent); return retval; } /** @brief cancel monitoring of config file @return TRUE if the connection was successfully removed */ gboolean e2_fs_FAM_cancel_monitor_config (void) { gchar *config_file = g_build_filename (e2_cl_options.config_dir, default_config_file, NULL); gchar *config_parent = g_path_get_dirname (config_file); gboolean retval = (!g_str_equal (config_parent, ".") && _e2_fs_FAM_dnotify_demonitor_dir (app.FAMreq)); if (retval) printd (DEBUG, "dnotify cancel for %s succeeded", config_file); else printd (WARN, "dnotify cancel for %s failed", config_file); g_free (config_file); g_free (config_parent); return retval; } /** @brief poll monitor to check if any monitored thing has changed This just updates flags, to signal that refresh is needed For a pane, update is needed if any directory content has changed, or if parent dir is not accessible/readable or gone A missing config file is not reported (as we can't reload it anyway) @param p1altered pointer to location to store T/F for whether pane 1 needs refresh @param p2altered pointer to location to store T/F for whether pane 2 needs refresh @param cfgaltered pointer to location to store T/F for whether config needs refresh @return */ void e2_fs_FAM_poll (gboolean *p1altered, gboolean *p2altered, gboolean *cfgaltered) { #ifdef SIMPLECOUNT if (debugcount > 0) { printd (DEBUG, "%d reports received", debugcount); debugcount = 0; } *p1altered = *p2altered = *cfgaltered = FALSE; return; #else //make sure the monitored dir(s) are still valid //dnotify will not have reported inaccessible/renamed/deleted dir(s) //access () traverses links (ok) and does not change atime (ok) #ifdef E2_VFSTMP //FIXME dir when not mounted local #else gchar *local = F_FILENAME_TO_LOCALE (app.pane1.path); #endif *p1altered = e2_fs_access (local, R_OK | X_OK E2_ERR_PTR()); F_FREE (local); #ifdef E2_VFSTMP //FIXME dir when not mounted local #else local = F_FILENAME_TO_LOCALE (app.pane2.path); #endif *p2altered = e2_fs_access (local, R_OK | X_OK E2_ERR_PTR()); F_FREE (local); //not interested whether config is still present *cfgaltered = FALSE; //block DNOTIFY_SIGNAL sigprocmask (SIG_BLOCK, &dnotify_signal_set, NULL); //interrogate and clear reports logs gint i; E2_BLOCK for (i=0; i<3; i++) { if (reports[i][REPORT_COUNT] > 0 && reports[i][REPORT_FD] != FD_UNUSED) { // gchar *path = g_hash_table_lookup (fd_hash, // GINT_TO_POINTER (reports[i][REPORT_FD])); #ifdef EXTRA_MESSAGES printd (DEBUG, "%d report(s) for %s", reports[i][REPORT_COUNT], paths[i]); #endif reports[i][REPORT_COUNT] = 0; gint fd = reports[i][REPORT_FD]; if (fd == app.pane1.FAMreq) { *p1altered = TRUE; // printd (DEBUG, "pane 1 to be refreshed"); //mirrored panes need to get the same status if (app.pane2.FAMreq == -1) { *p2altered = TRUE; // printd (DEBUG, "pane 2 to be refreshed"); } } else if (fd == app.pane2.FAMreq) { *p2altered = TRUE; // printd (DEBUG, "pane 2 to be refreshed"); //mirrored panes need to get the same status if (app.pane1.FAMreq == -1) { *p1altered = TRUE; // printd (DEBUG, "pane 1 to be refreshed"); } } else if (fd == app.FAMreq) { struct stat sb; //we do NOT want a dirty-report if the config file is gone now ! if (!stat (localconfig, &sb)) //look thru link FIXME vfs { if ( sb.st_mtime != cfgstatbuf.st_mtime // || sb.st_size != cfgstatbuf.st_size // || sb.st_ino != cfgstatbuf.st_ino ) { cfgstatbuf = sb; *cfgaltered = TRUE; printd (DEBUG, "changed config file"); } } } } } E2_UNBLOCK //unblock DNOTIFY_SIGNAL sigprocmask (SIG_UNBLOCK, &dnotify_signal_set, NULL); return; #endif //def SIMPLECOUNT } #endif //def E2_FAM_DNOTIFY emelfm2-0.4.1/src/filesystem/e2_fs_mount.c0000600000175000017500000010174210776374631017371 0ustar cairocairo/* $Id: e2_fs_mount.c 853 2008-04-07 10:38:17Z tpgww $ Copyright (C) 2004-2007 Florian Zaehringer Copyright (C) 1999-2004 Bill Wilson This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/filesystem/e2_fs_mount.c @brief filesystem mountpoint related functions Functions dealing with fstab / mtab have in part been sourced from gkrellm (http://gkrellm.net) written by Bill Wilson. */ #include "emelfm2.h" #ifdef E2_FS_MOUNTABLE #include #include //shorthand for bsd-like os's supported here #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) #define __E2BSD__ #endif //some file-system identifiers (from , which isn't generally available) #ifndef MSDOS_SUPER_MAGIC # define MSDOS_SUPER_MAGIC 0x4d44 #endif #ifndef SMB_SUPER_MAGIC # define SMB_SUPER_MAGIC 0x517B #endif #ifndef USBDEVICE_SUPER_MAGIC # define USBDEVICE_SUPER_MAGIC 0x9fa2 #endif //in general, we support [u]mount completion #define E2_FS_MOUNTABLE // OSX 10.4 ("Tiger") automounts all devices. Unlike its predecessors, it uses statvfs() #if defined(darwin) #include #include #ifdef HAVE_SYS_STATVFS_H #undef E2_FS_MOUNTABLE #endif #endif //other exclusions go here ... # if defined(__linux__) # include # include # elif defined(__E2BSD__) //maybe this should just be defined(__FreeBSD__) || defined(__OpenBSD__) # include # include # include # elif defined(__solaris__) || defined(sco) //CHECKME sco ?? svr5 in general ?? # include # include # else # include # include /*Some systems use statfs() to provide information about mounted file systems, other systems use statvfs(). The header files used with these functions vary between systems. e.g. HPUX statfs() with sys/vfs.h SunOS statfs() with sys/vfs.h Solaris statvfs() with sys/statvfs.h */ # include # include # ifdef HAVE_SYS_STATVFS_H # include # endif # ifdef HAVE_SYS_VFS_H # include # endif /*#ifdef HAVE_SYS_STATVFS_H #define STATFS statvfs #else #define STATFS statfs #endif ... struct STATFS sfs; ... rc = STATFS(filename, &sfs); */ # endif //which os-specifis includes //test which mounted devices should be included in the list //of unmountable ones //CHECKME does this need to be os-specific ? if so, how ? # if defined(__linux__) || defined(__E2BSD__) # define E2_TEST_MOUNTED_DEVICE \ if ( strcmp (dev, "none") \ && strcmp (dev, "/dev") \ && strcmp (type, "proc") \ && strcmp (type, "sysfs") \ && strcmp (type, "fuse") \ && strcmp (dir, G_DIR_SEPARATOR_S)) /*no need to support umounting '/' */ \ # else # define E2_TEST_MOUNTED_DEVICE \ if ( strcmp (dev, "none") \ && strcmp (dev, "/dev") \ && strcmp (type, "sysfs") \ && strcmp (type, "proc") \ && strcmp (dir, G_DIR_SEPARATOR_S)) /*no need to support umounting '/' */ \ # endif /* these should be handled by dev="none" && strcmp (type, "devpts") && strcmp (type, "proc") && strcmp (type, "usbdevfs") && strcmp (type, "usbfs") && strcmp (type, "sysfs") && strstr (dir, "/dev/") */ /* permission-checking disabled pending doing it properly, if not forever gchar *opt = mounted->mnt_opts; if (!permcheck || _e2_complete_mount_permission (dir, opt)) */ /*for bsd's, which mount options are relevant ? how do we check them ? mounted[i].f_flags //mount flags mounted[i].f_owner //owner */ /* there may be things blocking an unmount eg monitoring with dnotify */ //finesse mounted device name before inclusion in the list of //unmountable devices # define E2_PROCESS_MOUNTED_DEVICE //strip any trailing / from the mountpoint name // s = strrchr (dir, G_DIR_SEPARATOR); // if (s != NULL && s != dir && *(s+1) == '\0') // *s = '\0'; //test which unmounted devices should be included in the list //of mountable ones //CHECKME does this need to be os-specific ? if so, how ? # define E2_TEST_UNMOUNTED_DEVICE \ if ( strcmp (dev, "none") \ && strcmp (type, "swap") \ && strcmp (type, "proc") \ && strcmp (type, "sysfs") \ && strcmp (type, "ignore") \ && strcmp (dir, G_DIR_SEPARATOR_S) /*no need to support mounting '/' */ \ && type[0] != '\0' ) /* // && g_file_test (dir, G_FILE_TEST_IS_DIR) don't bother checking for this (slow) these should be handled by dev="none" && strcmp (type, "devpts") && strcmp (type, "proc") && strcmp (type, "sysfs") && strcmp (type, "usbdevfs") && strcmp (type, "usbfs") && strstr (dir, "/dev/") == NULL CHECKME sometimes, test may need to include strncmp (dev, "/dev", 4) realpath (dev, realdev) != NULL gchar realdev[PATH_MAX]; */ /* permission-checking disabled pending doing it properly, if not forever gchar *opt = fs.vfs_mntopts; if (!permcheck || _e2_complete_mount_permission (dir, opt)) */ //finesse unmounted device name before inclusion in the list of //mountable devices # define E2_PROCESS_UNMOUNTED_DEVICE //strip any trailing / from the mountpoint name // s = strrchr (dir, G_DIR_SEPARATOR); // if (s != NULL && s != dir && *(s+1) == '\0') // *s = '\0'; //guint uid; //time_t fstab_mtime; /** @brief check whether @a localpath is a filesystem mountpoint @param localpath localised absolute path string @return TRUE if @a localpath is a mountpoint */ gboolean e2_fs_mount_is_mountpoint (VPATH *localpath) { struct stat sb; #ifdef E2_VFS if (localpath->spacedata != NULL) return FALSE; //mountpoints are always local #endif if (e2_fs_lstat (localpath, &sb E2_ERR_NONE()) || !S_ISDIR (sb.st_mode)) return FALSE; gboolean matched = FALSE; #ifdef E2_VFS gchar *utf = F_FILENAME_FROM_LOCALE (VPSTR(localpath)); #else gchar *utf = F_FILENAME_FROM_LOCALE (localpath); #endif GList *points = e2_fs_mount_get_mounts_list (); if (points != NULL) { matched = (g_list_find_custom (points, utf, (GCompareFunc) e2_list_strcmp) != NULL); e2_list_free_with_data (&points); } #if defined(__linux__) || defined(__FreeBSD__) if (!matched) { points = e2_fs_mount_get_fusemounts_list (); if (points != NULL) { matched = (g_list_find_custom (points, utf, (GCompareFunc) e2_list_strcmp) != NULL); e2_list_free_with_data (&points); } } #endif F_FREE (utf); return matched; } /* * @brief find mountpoint dir whose device is @a device @param device localised string with device of mountpoint to be found @return newly allocated string with mountpoint, or NULL upon error or no match */ /*gchar *e2_fs_mount_get_mountpoint_for_device (const gchar *device) { gchar *mountpath = NULL; //CHECKME lock data file while accessing it ?? #if defined(__E2BSD__) || defined(__linux__) struct fstab *fs; if (!setfsent ()) return NULL; while ((fs = getfsent ()) != NULL) { if (strcmp (fs->fs_spec, device) == 0) { mountpath = g_strdup (fs->fs_file); break; } } endfsent (); #elif defined(__solaris__) || defined(sco) //CHECKME sco struct vfstab fs; E2_FILE *f; if ((f = e2_fs_open_stream ("/etc/vfstab", "r")) == NULL) return NULL; while (getvfsent (f, &fs) == 0) { if (strcmp (fs.vfs_special, device) == 0) { mountpath = g_strdup (fs.vfs_mountp); break; } } e2_fs_close_stream (f); #elif defined(hpux) struct mntent *mountable; E2_FILE *f; //handle PFS if necessary const gchar *fstab = (access("/etc/pfs_fstab", F_OK)) ? "/etc/fstab" : "/etc/pfs_fstab"; if ((f = setmntent (fstab, "r")) == NULL) return NULL; while ((mountable = getmntent (f)) != NULL) { if (strcmp (mountable->mnt_fsname, device) == 0) { mountpath = g_strdup (mounted->mnt_dir); break; } } endmntent (f); #elif defined(__svr4__) struct mnttab mountable; E2_FILE *f; if ((f = e2_fs_open_stream ("/etc/fstab", "r")) == NULL) return NULL; while (getmntent (f, &mountable) == 0) { if (strcmp (mountable.mnt_special, device) == 0) { mountpath = g_strdup (mountable.mnt_mountp); break; } } e2_fs_close_stream (f); #else struct mntent *mountable; E2_FILE *f; if ((f = setmntent ("/etc/fstab", "r" )) == NULL) return NULL; while ((mountable = getmntent (f)) != NULL) //data is not thread- or process-safe { if (strcmp (mountable->mnt_fsname, device) == 0) { mountpath = g_strdup (mountable->mnt_dir); break; } } endmntent (f); #endif //which OS return mountpath; } */ /** @brief add @a dir to list @a mounts The string is copied, with conversion to utf-8 if need be @param dir localised string with path of mountpoint to be added @param points store of pointer to the list to be updated @return */ static void _e2_fs_mount_add_to_list (gchar *dir, GList **points) { gchar *utf = D_FILENAME_FROM_LOCALE (dir); *points = g_list_append (*points, utf); } /** @brief check whether @a localpath is a fuse mountpoint @param localpath localised absolute path string @return TRUE if @a localpath is a fuse mountpoint */ gboolean e2_fs_mount_is_fusepoint (VPATH *localpath) { #if defined(__linux__) || defined(__FreeBSD__) # ifdef E2_VFS if (localpath->spacedata != NULL) return FALSE; //mountpoints are always local # endif gboolean retval; #ifdef __linux__ struct mntent *mounted; E2_FILE *f; /* When the linux proc filesystem is mounted, the files /etc/mtab and /proc/mounts have very similar contents. The former has somewhat more information (CHECKME), such as the mount options used, but is not necessarily up-to-date */ if ((f = setmntent ("/proc/mounts", "r")) == NULL && (f = setmntent ("/etc/mtab", "r")) == NULL) return FALSE; retval = FALSE; while ((mounted = getmntent (f)) != NULL) { if (!strcmp (mounted->mnt_type, "fuse") && g_str_has_prefix (VPCSTR (localpath), mounted->mnt_dir)) { retval = TRUE; break; } } endmntent (f); #elif defined(__FreeBSD__) retval = FALSE; struct statfs *mounted; //NOTE: data provided by this is not process- or thread-safe gint i, count = getmntinfo (&mounted, MNT_NOWAIT); for (i=0; imnt_type, "fuse")) _e2_fs_mount_add_to_list (mounted->mnt_dir, &mounts_list); } endmntent (f); #elif defined(__FreeBSD__) struct statfs *mounted; //NOTE: data provided by this is not process- or thread-safe gint i, count = getmntinfo (&mounted, MNT_NOWAIT); for (i=0; imnt_dir); if (strncmp (local, mounted->mnt_dir, current_length) == 0) { thisdev = mounted->mnt_fsname; if (current_length == local_length //they'll both be 1 || current_length == local_length - 1) //same except for trailer { //we can stop immediately if this type matches g_free (bestdev); bestdev = g_strdup (thisdev); break; } else if (current_length > longest_sofar) { longest_sofar = current_length; g_free (bestdev); bestdev = g_strdup (thisdev); } else if (current_length == longest_sofar) { //FIXME decide which one is better to keep } } } endmntent (f); #elif defined(__E2BSD__) struct statfs *mounted; clean = e2_utils_unquote_string (utfpath); if (clean == NULL) return NULL; local = F_FILENAME_TO_LOCALE (clean); local_length = strlen (local); //length includes trailing / longest_sofar = 0; bestdev = NULL; //NOTE: data provided by this is not process- or thread-safe gint i, count = getmntinfo (&mounted, MNT_NOWAIT); for (i=0; i longest_sofar) { longest_sofar = current_length; g_free (bestdev); bestdev = g_strdup (thisdev); } else if (current_length == longest_sofar) { //FIXME decide which one is better to keep } } } #elif defined(__solaris__) || defined(sco) //CHECKME sco? struct mnttab mounted; E2_FILE *f; gint result; if ((f = e2_fs_open_stream ("/etc/mnttab", "r")) == NULL) return NULL; clean = e2_utils_unquote_string (utfpath); if (clean == NULL) return NULL; local = F_FILENAME_TO_LOCALE (clean); local_length = strlen (local); //length includes trailing / longest_sofar = 0; bestdev = NULL; while ((result = getmntent (f, &mounted)) == 0) { current_length = strlen (mounted.mnt_mountp); if (strncmp (local, mounted.mnt_mountp, current_length) == 0) { thisdev = mounted.mnt_special; if (current_length == local_length //they'll both be 1 || current_length == local_length - 1) //same except for trailer { //we can stop immediately if this type matches g_free (bestdev); bestdev = g_strdup (thisdev); break; } else if (current_length > longest_sofar) { longest_sofar = current_length; g_free (bestdev); bestdev = g_strdup (thisdev); } else if (current_length == longest_sofar) { //FIXME decide which one is better to keep } } } e2_fs_close_stream (f); #elif defined(hpux) struct mntent *mounted; E2_FILE *f; const gchar *mtab = (e2_fs_access ("/etc/pfs_mtab", F_OK E2_ERR_PTR())) ? "/etc/mtab" : "/etc/pfs_mtab"; if ((f = setmntent (mtab, "r")) == NULL) return NULL; clean = e2_utils_unquote_string (utfpath); if (clean == NULL) return NULL; local = F_FILENAME_TO_LOCALE (clean); local_length = strlen (local); //length includes trailing / longest_sofar = 0; bestdev = NULL; while ((mounted = getmntent (f)) != NULL) { current_length = strlen (mounted->mnt_dir); if (strncmp (local, mounted->mnt_dir, current_length) == 0) { thisdev = mounted->mnt_fsname; if (current_length == local_length //they'll both be 1 || current_length == local_length - 1) //same except for trailer { //we can stop immediately if this type matches g_free (bestdev); bestdev = g_strdup (thisdev); break; } else if (current_length > longest_sofar) { longest_sofar = current_length; g_free (bestdev); bestdev = g_strdup (thisdev); } else if (current_length == longest_sofar) { //FIXME decide which one is better to keep } } } endmntent (f); #elif defined(__svr4__) struct mnttab mounted; E2_FILE *f; gint result; if ((f = e2_fs_open_stream ("/etc/mtab", "r") == NULL) return NULL; clean = e2_utils_unquote_string (utfpath); if (clean == NULL) return NULL; local = F_FILENAME_TO_LOCALE (clean); local_length = strlen (local); //length includes trailing / longest_sofar = 0; bestdev = NULL; while ((result = getmntent (f, &mounted)) == 0) { current_length = strlen (mounted.f_mntonname); if (strncmp (local, mounted.f_mntonname, current_length) == 0) { thisdev = mounted.f_mntfromname; if (current_length == local_length //they'll both be 1 || current_length == local_length - 1) //same except for trailer { //we can stop immediately if this type matches g_free (bestdev); bestdev = g_strdup (thisdev); break; } else if (current_length > longest_sofar) { longest_sofar = current_length; g_free (bestdev); bestdev = g_strdup (thisdev); } else if (current_length == longest_sofar) { //FIXME decide which one is better to keep } } } e2_fs_close_stream (f); #else //other unix-like os's do it like linux, but without /proc/mount //CHECKME sometimes with a different filename ?? struct mntent *mounted; E2_FILE *f; if ((f = setmntent ("/etc/mtab", "r")) == NULL) return NULL; clean = e2_utils_unquote_string (utfpath); if (clean == NULL) return NULL; local = F_FILENAME_TO_LOCALE (clean); local_length = strlen (local); //length includes trailing / longest_sofar = 0; bestdev = NULL; while ((mounted = getmntent (f)) != NULL) { current_length = strlen (mounted->mnt_dir); if (strncmp (local, mounted->mnt_dir, current_length) == 0) { thisdev = mounted->mnt_fsname; if (current_length == local_length //they'll both be 1 || current_length == local_length - 1) //same except for trailer { //we can stop immediately if this type matches g_free (bestdev); bestdev = g_strdup (thisdev); break; } else if (current_length > longest_sofar) { longest_sofar = current_length; g_free (bestdev); bestdev = g_strdup (thisdev); } else if (current_length == longest_sofar) { //FIXME decide which one is better to keep } } } endmntent (f); #endif //which OS //========================= g_free (clean); F_FREE (local); return bestdev; } #endif //def E2_HAL /** @brief create list of mounted partitions in the native filesystem The list is created by the appropriate os-specifc protocol @return list of mounted partitions (utf-8 strings), or NULL */ GList *e2_fs_mount_get_mounts_list (void) //gboolean permcheck) { GList *mounts_list = NULL; //FIXME lock the data source while accessing it #if defined(__linux__) struct mntent *mounted; E2_FILE *f; /* When the linux proc filesystem is mounted, the files /etc/mtab and /proc/mounts have very similar contents. The former has somewhat more information (CHECKME), such as the mount options used, but is not necessarily up-to-date */ if ((f = setmntent ("/proc/mounts", "r")) == NULL) if ((f = setmntent ("/etc/mtab", "r")) == NULL) return NULL; while ((mounted = getmntent (f)) != NULL) { //check the mounted device meets our needs //need to take a copy if string is altered gchar *dev = mounted->mnt_fsname; gchar *dir = mounted->mnt_dir; gchar *type = mounted->mnt_type; E2_TEST_MOUNTED_DEVICE { E2_PROCESS_MOUNTED_DEVICE _e2_fs_mount_add_to_list (dir, &mounts_list); } } endmntent (f); #elif defined(__E2BSD__) struct statfs *mounted; //NOTE: data provided by this is not process- or thread-safe gint i, count = getmntinfo (&mounted, MNT_NOWAIT); for (i=0; imnt_fsname; gchar *dir = mounted->mnt_dir; // gchar *type = mounted->mnt_type; E2_TEST_MOUNTED_DEVICE { E2_PROCESS_MOUNTED_DEVICE _e2_fs_mount_add_to_list (dir, &mounts_list); } } endmntent (f); #elif defined(__svr4__) struct mnttab mounted; E2_FILE *f; if ((f = e2_fs_open_stream ("/etc/mtab", "r") == NULL) return NULL; while (getmntent (f, &mounted) ==0) { gchar *dev = mounted.f_mntfromname; gchar *dir = mounted.f_mntonname; // gchar *type = mounted.f_fstypename; E2_TEST_MOUNTED_DEVICE { E2_PROCESS_MOUNTED_DEVICE _e2_fs_mount_add_to_list (dir, &mounts_list); } } e2_fs_close_stream (f); #else //other unix-like os's do it like linux, but without /proc/mount //CHECKME sometimes with a different filename ?? struct mntent *mounted; E2_FILE *f; if ((f = setmntent ("/etc/mtab", "r")) == NULL) return NULL; while ((mounted = getmntent (f)) != NULL) { //check the mounted device meets our needs //need to take a copy if string is altered gchar *dev = mounted->mnt_fsname; gchar *dir = mounted->mnt_dir; // gchar *type = mounted->mnt_type; E2_TEST_MOUNTED_DEVICE { E2_PROCESS_MOUNTED_DEVICE _e2_fs_mount_add_to_list (dir, &mounts_list); } } endmntent (f); #endif //which OS return mounts_list; } /** @brief create list of mountable partitions in the native filesystem The list is created by the appropriate os-specifc protocol @return list of partitions (utf-8 strings), or NULL */ GList *e2_fs_mount_get_mountable_list (void) //gboolean permcheck) { GList *fstab_list = NULL; //CHECKME lock data file while accessing it ?? #if defined(__E2BSD__) || defined(__linux__) struct fstab *fs; if (!setfsent ()) return NULL; while ((fs = getfsent ()) != NULL) { gchar *dev = fs->fs_spec; gchar *dir = fs->fs_file; gchar *type = fs->fs_vfstype; /* copy strings if they need to be modified g_strlcpy (dev, fs->fs_spec, sizeof(dev)); g_strlcpy (dir, fs->fs_file, sizeof(dir)); g_strlcpy (type, fs->fs_vfstype, sizeof(type)); _e2_complete_mount_fix_fstab_name (dev); _e2_complete_mount_fix_fstab_name (dir); _e2_complete_mount_fix_fstab_name (type); */ E2_TEST_UNMOUNTED_DEVICE { E2_PROCESS_UNMOUNTED_DEVICE _e2_fs_mount_add_to_list (dir, &fstab_list); } } endfsent (); #elif defined(__solaris__) || defined(sco) //CHECKME sco struct vfstab fs; E2_FILE *f; if ((f = e2_fs_open_stream ("/etc/vfstab", "r")) == NULL) return NULL; while (getvfsent (f, &fs) == 0) { gchar *dev = fs.vfs_special; gchar *dir = fs.vfs_mountp; // gchar *type = fs.vfs_fstype; E2_TEST_UNMOUNTED_DEVICE { E2_PROCESS_UNMOUNTED_DEVICE _e2_fs_mount_add_to_list (dir, &fstab_list); } } e2_fs_close_stream (f); #elif defined(hpux) struct mntent *mountable; E2_FILE *f; //handle PFS if necessary const gchar *fstab = (access("/etc/pfs_fstab", F_OK)) ? "/etc/fstab" : "/etc/pfs_fstab"; if ((f = setmntent (fstab, "r")) == NULL) return NULL; while ((mountable = getmntent (f)) != NULL) { gchar *dev = mountable->mnt_fsname; gchar *type = mountable->mnt_type; E2_TEST_UNMOUNTED_DEVICE { E2_PROCESS_UNMOUNTED_DEVICE _e2_fs_mount_add_to_list (dir, &fstab_list); } } endmntent (f); #elif defined(__svr4__) struct mnttab mountable; E2_FILE *f; if ((f = e2_fs_open_stream ("/etc/fstab", "r")) == NULL) return NULL; while (getmntent (f, &mountable) == 0) { gchar *dev = mountable.mnt_special; gchar *dir = mountable.mnt_mountp; E2_TEST_UNMOUNTED_DEVICE { E2_PROCESS_UNMOUNTED_DEVICE _e2_fs_mount_add_to_list (dir, &fstab_list); } } e2_fs_close_stream (f); #else struct mntent *mountable; E2_FILE *f; if ((f = setmntent ("/etc/fstab", "r" )) == NULL) return NULL; while ((mountable = getmntent (f)) != NULL) //data is not thread- or process-safe { gchar *dev = mountable->mnt_fsname; gchar *dir = mountable->mnt_dir; // gchar *type = mountable->mnt_type; // gchar *opts = mountable->mnt_opts; E2_TEST_UNMOUNTED_DEVICE { E2_PROCESS_UNMOUNTED_DEVICE _e2_fs_mount_add_to_list (dir, &fstab_list); } } endmntent (f); #endif //which OS return fstab_list; } #ifdef E2_HAL /** @brief check whether device on which @a utfpath resides is removable @param utfpath absolute path string, utf8, may be quoted, and/or have spaces and/or trailing separator This is intended only for native dirs, so no VPATH involved It analyses mounted-devices data, so can only be used when @a utfpath is mounted @return TRUE if the dir is on a removable device */ gboolean e2_fs_mount_is_ejectable (const gchar *utfpath) { gboolean retval; gchar *device = _e2_fs_get_mounted_device (utfpath); if (device != NULL) { retval = e2_hal_device_is_ejectable (device); g_free (device); } else retval = FALSE; return retval; } #endif /** @brief check whether device on which @a utfpath resides is removable @param utfpath absolute path string, utf8, may be quoted, and/or have spaces and/or trailing separator This is intended only for native dirs, so no VPATH involved It analyses mounted-devices data, so can only be used when @a utfpath is mounted @return TRUE if the dir is on a removable device */ gboolean e2_fs_mount_is_removable (const gchar *utfpath) { gboolean retval; #ifdef E2_HAL gchar *device = _e2_fs_get_mounted_device (utfpath); if (device != NULL) { retval = e2_hal_device_is_removable (device); g_free (device); } else retval = FALSE; #else //FIXME //=========== OS-specific stuff ============= /*#if defined(__linux__) #elif defined(__E2BSD__) #elif defined(__solaris__) || defined(sco) #elif defined(hpux) #elif defined(__svr4__) #else #endif //which OS */ //========================= retval = FALSE; //default #endif return retval; } /** @brief check whether the filesystem associated with @a view uses case-sensitive paths/names MS-DOS/Windows FAT* are case-insensitive, but so-called "long" names are case-preserving. NTFS can be fully case sensitive or just case preserving. Classic Mac OS was case-insensitive but case-preserving. HFS+ on Mac OS X is probably the same. (its tag is "hfsplus") Mac HFSX is fully case senstive. Samba servers usually exhibit case-insensitive behaviour, regardless of the actual fs properties. For our purposes here, probably best to treat treat -preserving as -sensitive. @param view pointer to view data struct @return TRUE if paths/names are case-sensitive (usually the case) */ gboolean e2_fs_mount_is_cased (ViewInfo *view) { if (view->case_sensitive_names == E2FSCASE_UNKNOWN) { #ifdef E2_VFSTMP if (view->case_sensitive_names == E2FSCASE_VIRTUAL) { figure it out } else { #endif gchar *local = F_FILENAME_TO_LOCALE (view->dir); //=========== OS-specific stuff ============= #if defined(__linux__) #include //#include only in glibc-devel struct statfs buf; retry: if (statfs (local, &buf)) { if (errno == EINTR) goto retry; view->case_sensitive_names = E2FSCASE_NEVER; } else { switch (buf.f_type) { case MSDOS_SUPER_MAGIC: case SMB_SUPER_MAGIC: view->case_sensitive_names = E2FSCASE_ANY; break; default: view->case_sensitive_names = E2FSCASE_SENSITIVE; break; } } #ifdef E2_HAL if (e2_fs_mount_is_removable (view->dir)) //removable device #endif view->case_sensitive_names |= E2FSCASE_NOCACHE; #elif defined(__E2BSD__) #include #include struct statfs buf; if (statfs (local, &buf)) { view->case_sensitive_names = E2FSCASE_NEVER; } else { gchar *type = buf.f_fstypename; if (strcmp (type, "vfat") == 0) view->case_sensitive_names = E2FSCASE_ANY; else view->case_sensitive_names = E2FSCASE_SENSITIVE; } #ifdef E2_HAL if (e2_fs_mount_is_removable (view->dir)) //on removable device #endif view->case_sensitive_names |= E2FSCASE_NOCACHE; #elif defined(__solaris__) || defined(sco) || defined(hpux) || defined(__svr4__) #include #include struct statvfs buf; if (statvfs (local, &buf)) { view->case_sensitive_names = E2FSCASE_NEVER; } else { gchar *type = buf.f_basetype; if (strcmp (type, "vfat") == 0) view->case_sensitive_names = E2FSCASE_ANY; else view->case_sensitive_names = E2FSCASE_SENSITIVE; } #ifdef E2_HAL if (e2_fs_mount_is_removable (view->dir)) //on removable device #endif view->case_sensitive_names |= E2FSCASE_NOCACHE; #else #include #include struct statfs buf; if (statfs (local, &buf)) { view->case_sensitive_names = E2FSCASE_NEVER; } else { gchar *type = buf.f_fstypename; if (strcmp (type, "vfat") == 0) view->case_sensitive_names = E2FSCASE_ANY; else view->case_sensitive_names = E2FSCASE_SENSITIVE; } #ifdef E2_HAL if (e2_fs_mount_is_removable (view->dir)) //on removable place #endif view->case_sensitive_names |= E2FSCASE_NOCACHE; #endif //which OS //========================= F_FREE (local); #ifdef E2_VFSTMP } #endif } return (view->case_sensitive_names == E2FSCASE_SENSITIVE || view->case_sensitive_names == E2FSCASE_SENSITIVENOW); } /** @brief register mountpoint-related actions @return */ void e2_fs_mount_actions_register (void) { gchar *action = g_strconcat (_A(57),".",_A(24), NULL); e2_action_register (action, E2_ACTION_TYPE_ITEM, e2_menu_create_mounts_menu, NULL, TRUE, E2_ACTION_EXCLUDE_MENU, NULL); } #endif //def E2_FS_MOUNTABLE emelfm2-0.4.1/src/filesystem/e2_fs.c0000600000175000017500000024205511007714656016143 0ustar cairocairo/* $Id: e2_fs.c 864 2008-05-05 23:35:10Z tpgww $ Copyright (C) 2005-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/filesystem/e2_fs.c @brief filesystem I/O functions */ /** \page filesystem interactions with the file system ToDo - describe how this happens \section vfs the virtual filesystem Not implemented yet. */ #include "emelfm2.h" #include #include #include #include #include #include #include #if defined(__FreeBSD__) || defined(__OpenBSD__) # include # include #else # include #endif #ifndef MNT_LOCAL # include #endif #include "e2_fs.h" #include "e2_dialog.h" #ifdef E2_VFS #include "e2_plugins.h" #endif #if defined(__linux__) || defined(__FreeBSD__) #include "e2_complete.h" #endif typedef struct _E2_DRFuncArgs { const gchar *localpath; //absolute path of dir to process, localised string FIXME gboolean slowdir; //TRUE to process dir with thread and timeout monitoring gpointer callback; //function to call for each processed item, or NULL to append item's name to list gpointer cb_data; //pointer to data to send to @a callback GDestroyNotify free_data_func; //function to call at end of scan, to clean @a cb_data, or NULL } E2_DRFuncArgs; #if 0 /** @brief check if any x permission flag is set in @a mode @param mode @return TRUE if any x flag is set */ //FIXME this test should be for current user only, cf writable test static gboolean _e2_fs_S_ISEXE (mode_t mode) { if ((S_IXUSR & mode) || (S_IXGRP & mode) || (S_IXOTH & mode)) return TRUE; return FALSE; } #endif //0 #ifdef E2_VFSTMP //FIXME may need to check coding for each displayed dir, if that's possible #endif #ifndef E2_FILES_UTF8ONLY //check whether a character encoding is compatible with utf static gboolean _e2_fs_charset_is_utf (gchar *filecoding) { return (!strcmp (filecoding, "UTF-8") || strstr (filecoding, "ASCII") != NULL || !strcmp (filecoding, "ANSI_X3.4-1968") || !strcmp (filecoding, "646") || !strcmp (filecoding, "ISO646") || !strcmp (filecoding, "ISO_646.IRV")); } //check whether the locale language is one of the english variants static gboolean _e2_fs_language_is_english (void) { const gchar *lang = g_getenv ("LANGUAGE"); if (lang == NULL) lang = g_getenv ("LANG"); return (lang != NULL && strstr (lang, "en_") != NULL); } /** @brief check whether the native filesystem encoding is utf-8 (or ascii), and setup for conversion accordingly @return */ void e2_fs_check_coding (void) { gchar *filecoding; app.utf8_filenames = FALSE; //1st, check what the user specified about encoding if (e2_cl_options.encoding != NULL) app.utf8_filenames = _e2_fs_charset_is_utf (e2_cl_options.encoding); //otherwise, try to find something from the environment ... else { //for glib, $G_FILENAME_ENCODING has priority over $G_BROKEN_FILENAMES filecoding = (gchar *) g_getenv ("G_FILENAME_ENCODING"); if (filecoding == NULL) { //check what the locale data knows about the encoding filecoding = (gchar *) g_getenv ("G_BROKEN_FILENAMES"); if (filecoding != NULL) { filecoding = nl_langinfo (CODESET); app.utf8_filenames = _e2_fs_charset_is_utf (filecoding); } else app.utf8_filenames = _e2_fs_language_is_english (); } else if (g_str_equal (filecoding, "@locale")) { const gchar *charset; app.utf8_filenames = g_get_charset (&charset) //we assume locales using english language have ascii filenames //(which are compatible with utf) || _e2_fs_language_is_english (); } else { //FIXME maybe a list, check only its 1st member app.utf8_filenames = _e2_fs_charset_is_utf (filecoding) || _e2_fs_language_is_english (); } } //set pointers to conversion functions if (app.utf8_filenames) { e2_display_from_locale = e2_fname_to_locale = e2_fname_from_locale = e2_utf8_not_converted; e2_fname_dupto_locale = e2_fname_dupfrom_locale = g_strdup; e2_fname_free = e2_utf8_not_freed; } else { e2_display_from_locale = g_filename_display_name; e2_fname_to_locale = e2_fname_dupto_locale = e2_utf8_filename_to_locale; e2_fname_from_locale = e2_fname_dupfrom_locale = e2_utf8_filename_from_locale; e2_fname_free = g_free; } } #endif //ndef E2_FILES_UTF8ONLY /** @brief check whether @a localpath exists Can't use the function access(), which (for glibc at least) looks through links Errors other than non-readable are ignored NOTE probably this does not work properly on FAT*, which are case-insensitive @param localpath item to test, absolute localised string @return 0 (FALSE) if @a localpath exists (like access(F_OK)), else -1 */ gint e2_fs_access2 (VPATH *localpath E2_ERR_ARG()) { struct stat statbuf; // if (e2_fs_mount_is_cased (view)) return (e2_fs_lstat (localpath, &statbuf E2_ERR_SAMEARG())); /* else { can't determine combinations of ucase and lcase chars to stat what else ?? } */ } /** @brief check whether @a localpath can be 'used' in a manner consistent with flags in @a howflags This mimics the stdio function access(), except that it does not look through links, as does access() (in glibc at least). @param localpath localised name of file to test @param howflags or'd combination of R_OK, W_OK, X_OK and/or F_OK @return 0 (FALSE) if all @a howflags condition(s) are satisfied (like access()), else -1 */ gint e2_fs_access3 (VPATH *localpath, gint howflags E2_ERR_ARG()) { struct stat statbuf; if (e2_fs_lstat (localpath, &statbuf E2_ERR_SAMEARG())) return -1; else if (! S_ISLNK (statbuf.st_mode)) return (e2_fs_access (localpath, howflags E2_ERR_SAMEARG())); static uid_t myuid = -1; static gid_t mygid = -1; if (myuid == -1) myuid = getuid (); if (mygid == -1) mygid = getgid (); gboolean result = FALSE; if (howflags & R_OK) { if (statbuf.st_mode & S_IROTH) result = TRUE; else if (statbuf.st_mode & S_IRUSR) result = ((myuid == 0) || (myuid == statbuf.st_uid)); else if (statbuf.st_mode & S_IRGRP) result = ((myuid == 0) || e2_fs_ingroup (statbuf.st_gid)); if (!result) return -1; } if (howflags & W_OK) { if (statbuf.st_mode & S_IWOTH) result = TRUE; else if (statbuf.st_mode & S_IWUSR) result = ((myuid == 0) || (myuid == statbuf.st_uid)); else if (statbuf.st_mode & S_IWGRP) result = ((myuid == 0) || e2_fs_ingroup (statbuf.st_gid)); if (!result) return -1; } if (howflags & X_OK) { if (statbuf.st_mode & S_IXOTH) result = TRUE; else if (statbuf.st_mode & S_IXUSR) result = ((myuid == 0) || (myuid == statbuf.st_uid)); else if (statbuf.st_mode & S_IXGRP) result = ((myuid == 0) || e2_fs_ingroup (statbuf.st_gid)); if (!result) return -1; } //we already know the link exists, no need for F_OK test return 0; } /** @brief check whether the current user may modify @a localpath This is more than just a simple X-flag check This does not look through links (target may not exist) @param localpath localised string with full path of item to check @return TRUE if the user has write authority for @a localpath */ gboolean e2_fs_check_write_permission (VPATH *localpath E2_ERR_ARG()) { #ifdef E2_VFS if (!e2_fs_item_is_mounted (localpath)) { # ifdef E2_VFSTMP //detect virtual items that are never writable by any user //e.g. a local archive without write/change functionality //FIXME handle archive where can write file but cannot chmod etc # endif return FALSE; } #endif if (!e2_fs_access3 (localpath, W_OK E2_ERR_SAMEARG())) return TRUE; //write permission is actually set //otherwise, check whether we can change it anyway struct stat statbuf; if (e2_fs_lstat (localpath, &statbuf E2_ERR_SAMEARG())) return FALSE; else if (S_ISLNK (statbuf.st_mode)) return FALSE; //link permissions are thoroughly evaluated in access3(), no point in repeating //CHECKME which of these are actually checked in access() ?? uid_t id = getuid (); if (id == 0 || id == statbuf.st_uid) return TRUE; //NB for GRP and OTH this does not work, for dirs at least else if ((statbuf.st_mode & S_IWGRP) //must already be group-writable for a member to change it && e2_fs_ingroup (statbuf.st_gid)) return TRUE; else if (statbuf.st_mode & S_IWOTH)//must already be other-writable for another to change it return TRUE; return FALSE; } /** @brief determine whether the current user is a member of group whose id is @a gid This is mainly to check whether to manipulate USR, GRP or OTH permissions @param gid a group id @return TRUE if current user is part of the group */ gboolean e2_fs_ingroup (gid_t gid) { gid_t mygid = getgid (); if (mygid == gid) return TRUE; struct passwd *pw_buf; struct group *grp_buf; gboolean ingroup = FALSE; pw_buf = getpwuid (getuid ()); if (pw_buf != NULL) { grp_buf = getgrgid (gid); if (grp_buf != NULL && grp_buf->gr_mem != NULL) { gchar *myname = g_strdup (pw_buf->pw_name); gint i = 0; while (grp_buf->gr_mem [i] != NULL) { if (g_str_equal (grp_buf->gr_mem [i], myname)) { ingroup = TRUE; break; } i++; } g_free (myname); } } return ingroup; } /** @brief execute the file(1) command on @a localpath, and search each line of its output for @a string Used in e2_fs_is_text and e2_fs_is_executable @param localpath localised name of file to test @param string ascii string for which to search, in the output from 'find' @return TRUE if @a string is found */ static gboolean _e2_fs_grep_file_output (VPATH *localpath, gchar *string E2_ERR_ARG()) { #ifdef E2_VFS if (e2_fs_item_is_mounted (localpath)) { #endif gchar line[PATH_MAX + 20]; //CHECKME weirdness when > 1 opening of the same file, //avoided by setting the cwd before opening the pipe gchar *local = F_FILENAME_TO_LOCALE (curr_view->dir); #ifdef E2_VFS VPATH data = { local, NULL }; //always local if (e2_fs_chdir_local (&data E2_ERR_NONE())) #else if (e2_fs_chdir_local (local E2_ERR_NONE())) #endif { // FIXME warn user F_FREE (local); return FALSE; } F_FREE (local); size_t bsize = E2_MAX_LEN; #ifdef USE_GLIB2_10 gchar *buf = (gchar *) g_slice_alloc ((gulong) bsize); #else gchar *buf = (gchar *) g_try_malloc ((gulong) bsize); #endif CHECKALLOCATEDWARN (buf, return FALSE;); //not g_strdup_printf or g_snprintf (in case they don't like localised text??) snprintf (line, sizeof (line), ("file \"%s\""), //do not translate VPSTR(localpath)); E2_FILE *pipe = e2_fs_open_pipe (line); if (pipe == NULL) { #ifdef USE_GLIB2_10 g_slice_free1 (bsize, buf); #else g_free (buf); #endif return FALSE; } gboolean retval = FALSE; #ifdef __USE_GNU while (getdelim (&buf, &bsize, '\n', pipe) > 1) //not an empty line #else while (fgets (buf, bsize, pipe) != NULL && *buf != '\n') #endif { if (strstr (buf, string)) { retval = TRUE; break; } } e2_fs_pipe_close (pipe); #ifdef USE_GLIB2_10 g_slice_free1 (bsize, buf); #else g_free (buf); #endif return retval; #ifdef E2_VFS } else //item is virtual { /* if (E2_ERR_NAME != NULL && *E2_ERR_NAME != NULL) { g_error_free (*E2_ERR_NAME); //just in case? *E2_ERR_NAME = NULL; } */ # ifdef E2_VFSTMP //get information about virtual item, how ?? # endif return FALSE; } #endif } /** @brief test whether @a localpath is a text file @param localpath localised name of file to test @return TRUE if it is a text file */ gboolean e2_fs_is_text (VPATH *localpath E2_ERR_ARG()) { #ifdef E2_VFS if (e2_fs_item_is_mounted (localpath)) return _e2_fs_grep_file_output (localpath, "text" E2_ERR_SAMEARG()); //do not translate else //item is virtual { # ifdef E2_VFSTMP //FIXME do this some other way for virtual items e.g. test mimetype # endif return FALSE; } #else return _e2_fs_grep_file_output (localpath, "text" E2_ERR_SAMEARG()); //do not translate #endif } /** @brief test whether @a info belongs to an executable item If the item is a symlink, its target will be tested @param info pointer to a FileInfo data struct @param view pointer to view data struct from which to get path info for @a info @return TRUE if system reports x permission, and the file (1) command says it's executable */ gboolean e2_fs_is_executable (FileInfo *info, ViewInfo *view) { gboolean retval = FALSE; gchar *local = e2_utils_dircat (view, info->filename, TRUE); #ifdef E2_VFS VPATH ddata = { local, view->spacedata }; VPATH *localpath = &ddata; #else VPATH *localpath = local; #endif if (!e2_fs_access (localpath, X_OK E2_ERR_NONE())) { //permission is ok if (S_ISLNK (info->statbuf.st_mode)) retval = TRUE; else { //non-link, double check ... #ifdef E2_VFS if (e2_fs_item_is_mounted (localpath)) { #endif retval = _e2_fs_grep_file_output (localpath, "executable" E2_ERR_NONE ()); //string not to be translated #ifdef E2_VFS } else //item is virtual { /* if (E2_ERR_NAME != NULL && *E2_ERR_NAME != NULL) { g_error_free (*E2_ERR_NAME); //just in case? *E2_ERR_NAME = NULL; } */ # ifdef E2_VFSTMP //FIXME full path for vfs item //FIXME do this some other way for virtual items e.g. test mimetype # endif retval = FALSE; } #endif } } g_free (local); return retval; } /* * @brief check whether @a path is a link @param path localised path string @return TRUE if @a info belongs to a directory or a link to a directory */ /*gboolean e2_fs_is_link (gchar *local_path) { struct stat statbuf; return (!e2_fs_lstat (local_path, &statbuf E2_ERR_NONE()) && S_ISLNK (statbuf.st_mode)); } */ /** @brief check whether item in active pane is a directory or a link to one This assumes that the item's mode flags have been gathered using lstat(), not stat() (i.e. no link look-through) @param info ptr to FileInfo struct for the item @param view pointer to view data struct from which to get path info for @a info @return TRUE if @a info belongs to a directory or a link to a directory */ gboolean e2_fs_is_dir (FileInfo *info, ViewInfo *view) { //CHECKME want to return any vfs error ? if ( S_ISDIR (info->statbuf.st_mode) ) return TRUE; if ( S_ISLNK(info->statbuf.st_mode) ) { struct stat statbuf; gchar *localpath = e2_utils_dircat (view, info->filename, TRUE); #ifdef E2_VFS VPATH data = { localpath, view->spacedata }; gboolean ok = !e2_fs_stat (&data, &statbuf E2_ERR_NONE()); #else gboolean ok = !e2_fs_stat (localpath, &statbuf E2_ERR_NONE()); #endif g_free (localpath); if (ok && S_ISDIR (statbuf.st_mode)) return TRUE; } return FALSE; } /* * @brief inline version of func that checks whether item in active pane is a directory or a link to one NOW CONVERTED TO AN EQUIVALENT DEFINE This assumes that the mode flags have been gathered using lstat, not stat (ie no link look-through) Item needs to be in active pane because no path is added to info->filename @param info FileInfo structure @param statbuf pointer to statbuf to use for statting @a info ->filename @return TRUE if @a info belongs to a directory or a link to a directory */ /*inline gboolean e2_fs_is_dir_fast (FileInfo *info, struct stat *statbuf) { if (S_ISDIR (info->statbuf.st_mode) ) return TRUE; if (S_ISLNK(info->statbuf.st_mode) ) { FIXME absolute path for stat() if (!stat (info->filename, statbuf) //look thru the link && S_ISDIR (statbuf->st_mode)) return TRUE; } return FALSE; } */ /** @brief check whether @a localpath is a directory or a link to one @param localpath localised absolute path string @return TRUE if @a localpath is a directory or a link to a directory */ gboolean e2_fs_is_dir3 (VPATH *localpath E2_ERR_ARG()) { struct stat statbuf; return (!e2_fs_stat (localpath, &statbuf E2_ERR_SAMEARG()) && S_ISDIR (statbuf.st_mode)); } /** @brief make directory @a localpath regardless of whether all ancestors exist This only works for single-byte path-separator characters @param localpath localised absolute path string @param mode octal permissions of new dir e.g. 0777 or 0644 @return FALSE if @a creation succeeds (like mkdir) */ gboolean e2_fs_recurse_mkdir (VPATH *localpath, gint mode E2_ERR_ARG()) { struct stat sb; gchar c; gchar *s; #ifdef E2_VFS VPATH data; data = *localpath; #endif s = VPSTR(localpath); //skips check on fs root dir while ((s = strchr (s+1, G_DIR_SEPARATOR)) != NULL) { c = *s; *s = '\0'; #ifdef E2_VFS if (e2_fs_stat (&data, &sb E2_ERR_SAMEARG())) //thru links, not e2_fs_, FIXME vfs #else if (e2_fs_stat (localpath, &sb E2_ERR_SAMEARG())) //thru links, not e2_fs_, FIXME vfs #endif { if (E2_ERR_PISNOT (ENOENT) || e2_fs_mkdir (localpath, mode E2_ERR_NONE())) //FIXME vfs { *s = c; #ifdef E2_VFS if (E2_ERR_NAME != NULL && *E2_ERR_NAME != NULL) { g_error_free (*E2_ERR_NAME); *E2_ERR_NAME = NULL; } #endif return TRUE; } } else if (! S_ISDIR (sb.st_mode)) { *s = c; return TRUE; } *s = c; } //check last or only segment if (e2_fs_stat (localpath, &sb E2_ERR_SAMEARG())) //thru links, not e2_fs_, { if (E2_ERR_PISNOT (ENOENT) || e2_fs_mkdir (localpath, mode E2_ERR_NONE())) //FIXME vfs { #ifdef E2_VFS if (E2_ERR_NAME != NULL && *E2_ERR_NAME != NULL) { g_error_free (*E2_ERR_NAME); *E2_ERR_NAME = NULL; } #endif return TRUE; } } return FALSE; } /** @brief change system directory to @a utfpath, if possible @a utfpath may be a link, in which case it will be traversed Error message expects BGL to be closed @param path utf-8 string with path to directory to be opened @return TRUE if @a path is opened ok */ gboolean e2_fs_chdir (gchar *utfpath E2_ERR_ARG()) { #ifdef E2_VFSTMP //what about incomplete mount of fuse-dir #endif gboolean retval; E2_ERR_BACKUP (localerr); gchar *local = F_FILENAME_TO_LOCALE (utfpath); #ifdef E2_VFS VPATH data = { local, NULL }; if (e2_fs_chdir_local (&data E2_ERR_SAMEARG())) #else if (e2_fs_chdir_local (local)) #endif { //go there failed #ifdef E2_VFS e2_output_print_error ((*E2_ERR_NAME)->message, FALSE); #else e2_output_print_strerrno (); #endif retval = FALSE; } else retval = TRUE; E2_ERR_CLEARBACKUP (localerr); F_FREE (local); return retval; } /** @brief check whether the user is able to change CWD to the directory @a utfpath This tests @a utfpath for existence, is-a-directory, is-executable and readable If @a utfpath is a link, it will be traversed Error message expects BGL to be closed @param utfpath utf-8 string with path of directory to be checked @return TRUE if @a path is ok to cd to, or is not mounted-local */ gboolean e2_fs_cd_isok (gchar *utfpath E2_ERR_ARG()) { gchar *message; #ifdef E2_VFSTMP //FIXME relevant spacedata to open ? #endif gchar *local = F_FILENAME_TO_LOCALE (utfpath); #ifdef E2_VFS VPATH data = { local, NULL }; gboolean success = !e2_fs_access (&data, F_OK E2_ERR_SAMEARG()); #else gboolean success = !e2_fs_access (local, F_OK E2_ERR_SAMEARG()); #endif if (success) { #ifdef E2_VFS if (!e2_fs_is_dir3 (&data E2_ERR_SAMEARG())) #else if (!e2_fs_is_dir3 (local E2_ERR_SAMEARG())) #endif { message = g_strdup_printf (_("'%s' is not a directory"), utfpath); success = FALSE; } #ifdef E2_VFS else if (e2_fs_access (&data, R_OK | X_OK E2_ERR_SAMEARG())) #else else if (e2_fs_access (local, R_OK | X_OK E2_ERR_SAMEARG())) #endif { message = g_strdup_printf (_("Cannot access directory '%s' - No permission"), utfpath); success = FALSE; } } else if (e2_fs_dir_is_native (utfpath)) message = g_strdup_printf (_("Directory '%s' does not exist"), utfpath); else { #ifdef E2_VFS if (E2_ERR_NAME != NULL && *E2_ERR_NAME != NULL) { g_error_free (*E2_ERR_NAME); *E2_ERR_NAME = NULL; } #endif success = TRUE; //missing path ok when it's non-mounted } if (!success) { e2_output_print_error (message, TRUE); } F_FREE (local); return success; } /** @brief check whether directory @a utfpath is valid, and optionally, accessible, and if not, pick a fallback If @a utfpath refers to a link, it will be traversed. If test(s) are not satisfied, @a path is set to a different path string, pointing to a place up the tree branch that does pass the test(s) If a different path string is returned, the old one is freed, of course @param utfpath store for utf-8 path string which may be invalid, must be freeable @param accessible TRUE to find a path that has X and R permissions, FALSE if don't care about that @return TRUE if @a path meets the specified test(s), FALSE if a replacement has been provided */ gboolean e2_fs_get_valid_path (gchar **utfpath, gboolean accessible E2_ERR_ARG()) { #ifdef E2_VFSTMP //FIXME relevant path to open ? walkup *utfpath, past its root if vdir, //revert to local space if vfs fails //caller will want a PlaceInfo * and a message about any change of place get relevant spacedata #endif #ifdef E2_VFS VPATH data; #endif gchar *local = D_FILENAME_TO_LOCALE (*utfpath); gchar *freeme, *p; #ifdef E2_VFSTMP //what about incomplete mount of fuse-dir #endif g_strchug (local); if (*local != '\0') { //CHECKME do a full interpretation here ? if (local[0] == '~') //home dir check { freeme = local; if (local[1] == '\0') { local = g_strdup (g_get_home_dir ()); g_free (freeme); } else if (local[1] == G_DIR_SEPARATOR) { local = g_strconcat (g_get_home_dir (), local + 1, NULL); g_free (freeme); } } #ifdef E2_VFS data.localpath = local; data.spacedata = NULL; if (e2_fs_is_dir3 (&data E2_ERR_NONE()) && (!accessible || !e2_fs_access (&data, R_OK | X_OK E2_ERR_NONE()))) #else if (e2_fs_is_dir3 (local E2_ERR_NONE()) && (!accessible || !e2_fs_access (local, R_OK | X_OK E2_ERR_NONE()))) #endif { g_free (local); return TRUE; } } //walk back up the tree branch until we find an acceptable dir while (e2_utils_get_parent_path (local, TRUE)) { #ifdef E2_VFSTMP //FIXME new path may be virtual item eg archive ? #endif #ifdef E2_VFS // data.localpath = local; e2_utils_get_parent_path() doesn't change path if (e2_fs_is_dir3 (&data E2_ERR_NONE()) && (!accessible || !e2_fs_access (&data, R_OK | X_OK E2_ERR_NONE()))) #else if (e2_fs_is_dir3 (local E2_ERR_NONE()) && (!accessible || !e2_fs_access (local, R_OK | X_OK E2_ERR_NONE()))) #endif { //replace the invalid path string with the valid one p = D_FILENAME_FROM_LOCALE (local); //copy in case not changed by macro g_free (*utfpath); *utfpath = p; g_free (local); return FALSE; } } //whole branch bad, revert to default #ifdef E2_VFSTMP //FIXME relevant default path to open ? #endif // p = g_strdup (G_DIR_SEPARATOR_S); p = g_strconcat (g_get_home_dir (), G_DIR_SEPARATOR_S, NULL); g_free (*utfpath); *utfpath = D_FILENAME_FROM_LOCALE (p); //copy in case not changed by macro g_free (p); g_free (local); return FALSE; } /** @brief complete a directory string in @a entry after a keypress @a pressed_char This works only for native directories @param entry the entry widget to be updated @param pressed_char utf8-string form of pressed key which triggered the completion @param pane enumerator of pane to use for default dir, 1,2,0=current @return TRUE if completion was performed */ static gboolean _e2_fs_complete_dir (GtkWidget *entry, gchar *pressed_char, guint pane) { gboolean retval; //start is characters, not bytes gint start = gtk_editable_get_position (GTK_EDITABLE (entry)); gint pos = start + 1; //+1 to include the key just pressed gchar *entrytext = gtk_editable_get_chars (GTK_EDITABLE (entry), 0, start); gchar *text = g_strconcat (entrytext, pressed_char, NULL); gchar *utfdir; if (g_path_is_absolute (text)) utfdir = g_path_get_dirname (text); else { switch (pane) { case E2PANE1: utfdir = app.pane1_view.dir; break; case E2PANE2: utfdir = app.pane2_view.dir; break; // case E2PANECUR: default: utfdir = curr_view->dir; break; } gchar *freeme = g_strconcat (utfdir, text, NULL); utfdir = g_path_get_dirname (freeme); g_free (freeme); } #ifdef E2_VFSTMP //CHECKME enable vfs completion ?? #endif if (e2_fs_dir_is_native (utfdir)) { GList *found = NULL; retval = (e2_complete_str (&text, &pos, &found, E2_COMPLETE_FLAG_DIRS, pane) > 0); if (retval) { g_free (entrytext); entrytext = gtk_editable_get_chars (GTK_EDITABLE (entry), start, -1); gchar *newtext = g_strconcat (text, entrytext, NULL); gtk_entry_set_text (GTK_ENTRY (entry), newtext); gtk_editable_set_position (GTK_EDITABLE (entry), pos); g_free (newtext); e2_list_free_with_data (&found); } } else retval = FALSE; g_free (utfdir); g_free (entrytext); g_free (text); return retval; } /** @brief auto-complete a directory path string in @a entry after a keypress @a keyval Completion is performed (or not) according to the relevant config option This does not check for a valid key (non-modifier < 0xF000, >0xFFFF) - that should be done before calling here @param entry the entry widget to be updated @param keyval code of pressed key which triggered the completion @param pane enumerator of pane to use for default dir, 1,2,0=current @return TRUE if completion was performed */ gboolean e2_fs_complete_dir (GtkWidget *entry, guint keyval, guint pane) { guint32 unikey = gdk_keyval_to_unicode (keyval); if (unikey > 0) { gchar keystring[8]; gint bytes = g_unichar_to_utf8 (unikey, keystring); *(keystring+bytes) = '\0'; gint type = e2_option_sel_get ("dir-line-completion"); switch (type) { case 1: //insert completed text, and move after it if (_e2_fs_complete_dir (entry, keystring, pane)) return TRUE; break; case 2: //select completed text { gint start, end, newpos, oldpos; if (gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), &start, &end)) { //when there's a selection, cursor position = end, fix that oldpos = start; gtk_editable_delete_text (GTK_EDITABLE (entry), start, end); } else oldpos = gtk_editable_get_position (GTK_EDITABLE (entry)); if (_e2_fs_complete_dir (entry, keystring, pane)) { newpos = gtk_editable_get_position (GTK_EDITABLE (entry)); gtk_editable_select_region (GTK_EDITABLE (entry), oldpos+1, newpos); return TRUE; } } break; default: //no auto-completion break; } } return FALSE; } /** @brief evaluate whether @a utfpath is a native dir Native means local and mounted, not fuse or other vfs This is for tailoring pre-change-dir and pre-refresh-dir checking @param utfpath absolute path of dir to check, utf-8 string @return TRUE if @a utfpath is or seems to be native */ gboolean e2_fs_dir_is_native (gchar *utfpath) { #if defined(__linux__) || defined(__FreeBSD__) GList *fusemounts, *member; fusemounts = e2_fs_mount_get_fusemounts_list (); for (member = fusemounts ; member != NULL; member = member->next) { //member->data is utf with no trailing "/" if (g_str_has_prefix (utfpath, (gchar *)member->data)) break; //path is a descendant of a fuse mountpoint } if (fusemounts != NULL) e2_list_free_with_data (&fusemounts); if (member != NULL) return FALSE; #endif #ifdef MNT_LOCAL gint result; gchar *localpath; struct statfs fstatus; localpath = F_FILENAME_TO_LOCALE (utfpath); result = statfs (localpath, &fstatus); F_FREE (localpath); if (result) { if (result == ESTALE) { printd (DEBUG, "%s resides in an unmounted VFS", utfpath); //CHECKME mount it ? } return TRUE; //any operation on utfpath will fail ASAP } if (!(fstatus.f_flags & MNT_LOCAL)) return FALSE; #else struct statvfs fstatus; gchar *localpath; localpath = F_FILENAME_TO_LOCALE (utfpath); retry: if (statvfs (localpath, &fstatus)) { if (errno == EINTR) goto retry; // if (errno == ENOLINK) // re-mount it ?? F_FREE (localpath); return FALSE; } F_FREE (localpath); #endif //FIXME MORE TESTS HERE e.g. //from string contents e.g. "//:" //from plugin data //from vfs history data //from other e2 data return TRUE; } #ifdef E2_VFS /* * @brief thread-function to read all of a virtual directory @a view ->dir has the target path To eliminate BGL-racing, no UI-change from here @param view ptr to data struct for the view to be updated @return list of FileInfo's for entries (maybe NULL), or error code if a problem occurred */ /*static gpointer _e2_fs_read_virtual_dir (E2_DRFuncArgs *data) { e2_utils_block_thread_signals (); //block all allowed signals to this thread gint oldtype = 0; pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype); gchar *itemname; gboolean (*processor) (gchar *, gchar *, GList **, gpointer) = data->callback; GList *member, *entries, *wanted; //this will leak list data and cb_data if that's supposed to be cleared here pthread_cleanup_push ((gpointer)g_list_free, (gpointer)entries); // E2_BLOCK //don't want this interrupted #ifdef E2_VFSTMP THIS NEEDS WORK //populate list using vfs read dir function entries = (GList *)e2_gvfs_dir_foreach (data->localpath, data->callback, data->cb_data); #else entries = NULL; #endif if (!E2DREAD_FAILED(entries)) { wanted = NULL; for (member = entries; member != NULL; member = member->next) { itemname = (gchar *)member->data; //one item we're not interested in if (itemname[0] != '.' || itemname[1] != '\0') { if (processor != NULL) { if (!processor (data->localpath, itemname, &entries, data->cb_data)) break; } else wanted = g_list_append (entries, g_strdup (itemname)); } } } // E2_UNBLOCK e2_list_free_with_data (&entries); if (data->free_data_func != NULL) data->free_data_func (data->cb_data); pthread_cleanup_pop (0); //free entries list pthread_setcanceltype (oldtype, NULL); return wanted; } */ #endif /** @brief synchronous or thread-function to read all of a mounted directory This is called synchronously when @a view ->fstype is FS_LOCAL, or as a thread when @a view ->fstype is FS_FUSE To eliminate BGL-racing, no BGL- or UI-change here @a view ->dir has the target path @param view ptr to data struct for the view to be updated @return list of FileInfo's for entries (maybe NULL), or error code if a problem occurred */ static gpointer _e2_fs_read_mounted_dir (E2_DRFuncArgs *data) { /* would be nice to prevent access-time updates purely due to refreshing, but the atime really does change as a result of the refresh process, and if we were to revert it, the ctime would be changed instead times are immediately shown in the other pane if it's the parent of this one if we could suspend monitoring of such parent dir, we must not lose any prior reports for there QUERY can the changed atime itself trigger a report which causes a subsequent refresh, creating an endless cycle? especially with multiple threads and/or processors ? Assuming it can do so, we clear the reports 'queue' after the dir is opened Would be nice if we could just clear any report(s) due to the refresh per se - but the reporting is not that detailed */ /*#ifdef E2_FAM_KERNEL if (view->refresh) e2_fs_FAM_cancel_monitor_dir (view->dir); #endif */ /*#if defined (E2_FAM_DNOTIFY) //cut down on spurious context changes //block DNOTIFY_SIGNAL sigprocmask (SIG_BLOCK, &dnotify_signal_set, NULL); #endif */ gboolean threaded = data->slowdir; //monitoring needed, so this func is a thread if (threaded) e2_utils_block_thread_signals (); //block all allowed signals to this thread DIR *dp = opendir (data->localpath); if (dp == NULL) { printd (WARN, "Unable to open directory: %s", data->localpath); return GINT_TO_POINTER (E2DREAD_DNR); } gint oldtype = 0; if (threaded) { pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype); // pthread_cleanup_push ((gpointer)closedir, (gpointer)dp); can't be conditional or inside braces } struct dirent *entryptr; //reportedly, some systems do not provide enough name-space union { struct dirent entry; gchar b [offsetof (struct dirent, d_name) + NAME_MAX + 1]; } u; #ifdef E2_VFS VPATH ddata; ddata.spacedata = NULL; //this func only handles local dirs #endif gboolean (*processor) (VPATH *, const gchar *, GList **, gpointer) = data->callback; GList *entries = NULL; // if (threaded) // pthread_cleanup_push ((gpointer)g_list_free, (gpointer)entries); //this will leak list data errno = 0; while (!readdir_r (dp, &u.entry, &entryptr) && entryptr != NULL) { //#ifdef _DIRENT_HAVE_D_RECLEN //reportedly, some systems do not terminate the name string //BUT this is not liked by the compiler ... // *((gchar *) (&u.entry + u.entry.d_reclen)) = '\0'; //maybe this is better ? // gpointer s = &u.entry; // s += u.entry.d_reclen; // *(gchar *)s = '\0'; //#endif //one item we're not interested in if (u.entry.d_name[0] != '.' || u.entry.d_name[1] != '\0') { if (processor != NULL) { #ifdef E2_VFS ddata.localpath = data->localpath; if (!processor (&ddata, u.entry.d_name, &entries, data->cb_data)) #else if (!processor (data->localpath, u.entry.d_name, &entries, data->cb_data)) #endif break; } else //order is irrelevant, prepend is faster entries = g_list_prepend (entries, g_strdup (u.entry.d_name)); } } if (errno == EBADF) { e2_list_free_with_data (&entries); entries = GINT_TO_POINTER (E2DREAD_NS); } closedir (dp); // E2_UNBLOCK if (data->free_data_func != NULL) data->free_data_func (data->cb_data); if (threaded) //this func is a thread { // pthread_cleanup_pop (0); //free entries list // pthread_cleanup_pop (0); //close dp pthread_setcanceltype (oldtype, NULL); } return entries; } /** @brief callback for responses from too-slow-dialog @a dialog This approach eliminates gtk_main() (which hates being aborted) from the dialog @param dialog the dialog from which the response was initiated @param response the response enumerator @param data task-data specified when the callback was connected @return */ static void _e2_fs_slowread_response_cb (GtkDialog *dialog, gint response, E2_DRead *data) { pthread_t ID; GtkWidget *wid; switch (response) { case GTK_RESPONSE_NO: //abort the operation ID = data->aid; data->aid = 0; if (ID > 0) //operation still not finished { pthread_cancel (ID); //shutdown action thread printd (DEBUG,"dir read thread aborted by user"); //after this cancellation, the main thread will cleanup } break; case E2_RESPONSE_YESTOALL: //no more reminders //CHECKME pthread_mutex_lock (&?_mutex); wid = data->dialog; //race-management data->dialog = NULL; if (wid != NULL && GTK_IS_WIDGET (wid)) gtk_widget_destroy (wid); //== (GTK_WIDGET(dialog) // pthread_mutex_unlock (&?_mutex); //if the racy action-thread ends about now, signal to main //thread that it doesn't need to abort the monitor thread ID = data->mid; data->mid = 0; if (ID > 0) //race-management pthread_cancel (ID); break; default: // case GTK_RESPONSE_YES: //keep waiting // pthread_mutex_lock (&?_mutex); wid = data->dialog; //race-management data->dialog = NULL; if (wid != NULL) { gtk_widget_hide (wid); data->dialog = wid; // WAIT_FOR_EVENTS; //make the hide work } // pthread_mutex_unlock (&?_mutex); break; } } /** @brief thread function to manage timeout when reading a directory Assumes BGL is open, and @a data ->dialog is NULLED before 1st use @param data pointer to data struct with thread id's etc @return never happens - this must be cancelled by some other thread */ static gpointer _e2_fs_progress_monitor (E2_DRead *data) { struct timespec timeout; //data for timeout value for the wait function e2_utils_block_thread_signals (); //block all allowed signals to this thread //these are not used externally, but are needed for the wait pthread_mutex_t condition_mutex = PTHREAD_MUTEX_INITIALIZER; //no recurse pthread_cond_t wait_cond = PTHREAD_COND_INITIALIZER; gint secs = 10; //initial wait = 10 secs while (TRUE) { // sleep (secs); //CHECKME more effective to use cond wait with timeout ? pthread_mutex_lock (&condition_mutex); clock_gettime (CLOCK_REALTIME, &timeout); timeout.tv_sec += secs; pthread_cond_timedwait (&wait_cond, &condition_mutex, &timeout); pthread_mutex_unlock (&condition_mutex); printd (DEBUG,"dir-monitor-thread (ID=%lu) %d-sec sleep ended", (gulong) data->mid, secs); gboolean shown = (data->dialog != NULL && GTK_WIDGET_VISIBLE (data->dialog)); if (data->dialog == NULL) //once-only, create the dialog data->dialog = e2_dialog_slow (_("Reading directory data"), _("directory read"), _e2_fs_slowread_response_cb, data); if (data->dialog != NULL && !GTK_WIDGET_VISIBLE (data->dialog)) { gdk_threads_enter (); gtk_widget_show (data->dialog); gtk_window_present (GTK_WINDOW (data->dialog)); // WAIT_FOR_EVENTS; gdk_threads_leave (); WAIT_FOR_EVENTS_UNLOCKED_SLOWLY; printd (DEBUG,"dir-monitor-thread dialog presented"); } //initially, progressively lengthen interval between dialog popups if (!shown //dialog was not shown already when this loop was traversed && secs < 30) secs += 10; } //never get to here return NULL; } /** @brief create a list of some or almost all items in the directory @a localpath Items in the directory can be passed to @a filterfunc to determine whether the respective item should be listed. Otherwise, all items other than "." are listed. CWD is temporarily changed to the directory, so threads cannot assume that CWD always matches curr_view->dir or whatever for non-local dirs. If the read is lengthy, a downstream dialog will be shown. That locks gdk threads mutex ? This assumes BGL is open. Any @a filterfunc is called with BGL open @param localpath absolute path of dir to process, localised string @param monitor enumerator signalling how to handle threaded dir reading @param filterfunc function to call for each processed item, or NULL to append each item's name to returned list @param cb_data pointer to data to send to @a filterfunc @param free_data_func function to call at end of scan, to clean @a cb_data, or NULL @return list of FileInfo's for entries (maybe NULL), or error code if a problem occurred */ gpointer e2_fs_dir_foreach (VPATH *localpath, E2_FsReadWatch monitor, gpointer filterfunc, gpointer cb_data, GDestroyNotify free_data_func E2_ERR_ARG()) { gpointer entries; #ifdef E2_VFS if (e2_fs_item_is_mounted (localpath)) { #endif gboolean slowdir; switch (monitor) { case E2_DIRWATCH_NO: slowdir = FALSE; break; case E2_DIRWATCH_YES: slowdir = TRUE; break; default: // case E2_DIRWATCH_CHECK: slowdir = e2_fs_mount_is_fusepoint (localpath); break; } E2_DRFuncArgs args = { VPCSTR (localpath), slowdir, filterfunc, cb_data, free_data_func }; if (!slowdir) //no timeout-monitoring needed return (_e2_fs_read_mounted_dir (&args)); //slow (FUSE) dirs use same func as native dirs, but with timeout checking //create joinable read thread, not with glib thread funcs, as we need to //kill thread(s) pthread_t ID; if (!pthread_create (&ID, NULL, (gpointer)_e2_fs_read_mounted_dir, &args)) printd (DEBUG,"read-dir-thread (ID=%lu) started", ID); else { //FIXME message to user printd (WARN,"read-dir-thread create error!"); printd (DEBUG,"exiting function e2_fs_dir_foreach () with error result"); return GINT_TO_POINTER (E2DREAD_ENOTH); } //FIXME use a E2_DRead already produced elsewhere //or since this thread joins the others, it can be stacked ... // E2_DRead *data = ALLOCATE0 (E2_DRead); // CHECKALLOCATEDWARN (data, return (GINT_TO_POINTER (E2DREAD_ENOMEM));) // data->aid = ID; e2_window_show_status_message (_("Reading directory data")); //make status message show, at session start at least WAIT_FOR_EVENTS_UNLOCKED //_SLOWLY //create monitor thread //redundant if read-thread has finished already, but then it gets killed //immediately, anyhow E2_DRead data = { ID, 0, NULL }; pthread_attr_t attr; pthread_attr_init (&attr); pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); if (pthread_create (&data.mid, &attr, (gpointer) _e2_fs_progress_monitor, &data) == 0) printd (DEBUG,"dir-monitor-thread (ID=%lu) started", data.mid); else { //FIXME message to user with BGL management printd (WARN,"read-dir-thread-create error!"); } //block until read is finished or cancelled printd (DEBUG,"main thread blocking until read ends or times out"); pthread_join (ID, &entries); e2_window_clear_status_message (); //no BGL management neeeded WAIT_FOR_EVENTS_UNLOCKED; //some race-minimisation here GtkWidget *wid = data.dialog; data.dialog = NULL; if (wid != NULL && GTK_IS_WIDGET (wid)) { gdk_threads_enter (); gtk_widget_destroy (wid); gdk_threads_leave (); } ID = data.mid; data.mid = 0; if (ID > 0) pthread_cancel (ID); printd (DEBUG,"exiting function e2_fs_dir_foreach () with read result"); // DEALLOCATE (E2_DRead, data); printd (DEBUG,"dir read data cleaned up in main thread"); if (entries == PTHREAD_CANCELED) entries = GINT_TO_POINTER (E2DREAD_DNF); #ifdef E2_VFS } else //read vdir { entries = GINT_TO_POINTER (E2DREAD_DNR); if (e2_fs_vfsfunc_ready ((gpointer *)&vfs.start_operation)) { E2_VFSMonitor opdata; //E2_VFSTMP if progress is shown, set opdata message and title and flag opdata.flags = E2VFS_TIMER | E2VFS_CANCEL; if (vfs.start_operation (localpath->spacedata, IFACE_OFFSET (readdir), &opdata)) { opdata.error = E2_ERR_NAME; //no need fof local error here entries = opdata.iface->readdir (localpath, filterfunc, cb_data, free_data_func, &opdata); vfs.finish_operation (&opdata); } } } #endif return entries; } /** @brief get real path for @a local_path if it's a link @param local_path store for pointer to freeable, absolute, localised, path string which may be a link name @return TRUE if a newly-allocated replacement path is provided in @a local_path */ gboolean e2_fs_walk_link (gchar **local_path E2_ERR_ARG()) { struct stat sb; #ifdef E2_VFSTMP FIXME get proper spacedata #endif #ifdef E2_VFS VPATH data = { *local_path, NULL }; if (!e2_fs_lstat (&data, &sb E2_ERR_SAMEARG()) && S_ISLNK (sb.st_mode)) #else if (!e2_fs_lstat (*local_path, &sb E2_ERR_SAMEARG()) && S_ISLNK (sb.st_mode)) #endif { #ifdef E2_VFS if (e2_fs_item_is_mounted ((&data))) { #endif #ifdef __USE_GNU gchar *resolved_path = canonicalize_file_name (*local_path); if (resolved_path != NULL) { g_free (*local_path); *local_path = resolved_path; return TRUE; } #else gchar resolved_path [PATH_MAX+1]; if (realpath (*local_path, resolved_path) != NULL) { *(resolved_path + PATH_MAX) = '\0'; //ensure an end g_free (*local_path); *local_path = g_strdup (resolved_path); return TRUE; } #endif #ifdef E2_VFS } else //item is virtual { /* if (E2_ERR_NAME != NULL && *E2_ERR_NAME != NULL) { g_error_free (*E2_ERR_NAME); //just in case? *E2_ERR_NAME = NULL; } */ # ifdef E2_VFSTMP //FIXME vfs if relevant, get information about virtual item, how ?? # endif return FALSE; } #endif } return FALSE; } /** @brief stat item @a localpath, put results into @a buf This looks through links, but does not fail for hanging links (not sure about failure for virtual items) @param localpath relative or absolute path of item to stat, localised string @param buf pointer to buffer to hold the results @return 0 (FALSE) if the stat succeeded, else -1 (TRUE) */ gint e2_fs_stat (VPATH *localpath, struct stat *buf E2_ERR_ARG()) { #ifdef E2_VFS # ifdef E2_VFSTMP //what about incomplete mount of fuse-dir # endif if (e2_fs_item_is_mounted (localpath)) { #endif if (stat (VPCSTR(localpath), buf)) { //stat failed, but it could be just due to a hanging symlink struct stat statbuf; //don't disturb the caller's buffer if (!lstat (VPCSTR(localpath), &statbuf) //stat link succeeded //CHECKME more checks for hanging link? && (S_ISLNK (statbuf.st_mode) && errno == ENOENT)) { *buf = statbuf; return 0; } return -1; } return 0; #ifdef E2_VFS } else //item is virtual { gint result = -1; if (e2_fs_vfsfunc_ready ((gpointer *)&vfs.start_operation)) { E2_VFSMonitor opdata; //E2_VFSTMP if progress is shown, set opdata message and title and flag opdata.flags = E2VFS_TIMER | E2VFS_CANCEL; if (vfs.start_operation (localpath->spacedata, IFACE_OFFSET (stat), &opdata)) { opdata.error = E2_ERR_NAME; //no need for local error here result = opdata.iface->stat (localpath, buf, &opdata); vfs.finish_operation (&opdata); //also handles cleanups when iface func is N/A } } return result; } #endif } #ifdef E2_VFS /******** REPLACEMENTS FOR STANDARD FS FUNCTIONS *********/ gint e2_fs_lstat (VPATH *localpath, struct stat *buf, GError **E2_ERR_NAME) { gint result; if (e2_fs_item_is_mounted (localpath)) { result = lstat (VPCSTR(localpath), buf); if (result != 0) e2_fs_set_error_from_errno (E2_ERR_NAME); } else //need to do virtual { result = -1; if (e2_fs_vfsfunc_ready ((gpointer *)&vfs.start_operation)) { E2_VFSMonitor opdata; //E2_VFSTMP if progress is shown, set opdata message and title and flag opdata.flags = E2VFS_TIMER | E2VFS_CANCEL; if (vfs.start_operation (localpath->spacedata, IFACE_OFFSET (lstat), &opdata)) { opdata.error = E2_ERR_NAME; //no need for local error here result = opdata.iface->lstat (localpath, buf, &opdata); vfs.finish_operation (&opdata); } } } return result; } gint e2_fs_access (VPATH *localpath, gint mode, GError **E2_ERR_NAME) { gint result; if (e2_fs_item_is_mounted (localpath)) { result = access (VPCSTR(localpath), mode); if (result != 0) e2_fs_set_error_from_errno (E2_ERR_NAME); } else //need to do virtual { result = -1; if (e2_fs_vfsfunc_ready ((gpointer *)&vfs.start_operation)) { E2_VFSMonitor opdata; //E2_VFSTMP if progress is shown, set opdata message and title and flag opdata.flags = E2VFS_TIMER | E2VFS_CANCEL; if (vfs.start_operation (localpath->spacedata, IFACE_OFFSET (access), &opdata)) { opdata.error = E2_ERR_NAME; //no need for local error here result = opdata.iface->access (localpath, mode, &opdata); vfs.finish_operation (&opdata); } } } return result; } //CHECKME any need for this sort of stream for non-local files ? E2_FILE *e2_fs_file_open (VPATH *localpath, const gchar *how, GError **E2_ERR_NAME) { E2_FILE *result; if (e2_fs_item_is_mounted (localpath)) { result = fopen (VPCSTR(localpath), how); if (result != NULL) e2_fs_set_error_from_errno (E2_ERR_NAME); return result; } else //need to do virtual { #ifdef E2_VFSTMP //FIXME get virtual stream #endif return NULL; } } gint e2_fs_symlink (VPATH *target, VPATH *name, GError **E2_ERR_NAME) { gint result; //links never possible between namespaces if (target->spacedata != name->spacedata) { e2_fs_set_error_custom (E2_ERR_NAME, 126, "%s and %s are not in the same namespace", VPSTR(target), VPSTR(name)); return -1; } if (e2_fs_item_is_mounted (target)) //&& e2_fs_item_is_mounted (name)) { result = symlink (VPCSTR(target), VPCSTR(name)); //CHECKME relative links ok ? if (result != 0) e2_fs_set_error_from_errno (E2_ERR_NAME); } else //need to do virtual { result = -1; if (e2_fs_vfsfunc_ready ((gpointer *)&vfs.start_operation)) { E2_VFSMonitor opdata; //E2_VFSTMP if progress is shown, set opdata message and title and flag opdata.flags = E2VFS_TIMER | E2VFS_CANCEL; if (vfs.start_operation (target->spacedata, IFACE_OFFSET (symlink), &opdata)) { opdata.error = E2_ERR_NAME; //no need for local error here result = opdata.iface->symlink (target, name, &opdata); vfs.finish_operation (&opdata); } } } return result; } gint e2_fs_readlink (VPATH *localpath, gchar *targetbuf, size_t bufsize, GError **E2_ERR_NAME) { gint result; if (e2_fs_item_is_mounted (localpath)) { result = readlink (VPCSTR(localpath), targetbuf, bufsize); if (result != 0) e2_fs_set_error_from_errno (E2_ERR_NAME); return result; } else //need to do virtual { #ifdef E2_VFSTMP //FIXME do virtual change #endif return -1; } } gint e2_fs_unlink (VPATH *localpath, GError **E2_ERR_NAME) { gint result; if (e2_fs_item_is_mounted (localpath)) { result = unlink (VPCSTR(localpath)); if (result != 0) e2_fs_set_error_from_errno (E2_ERR_NAME); } else //need to do virtual { result = -1; if (e2_fs_vfsfunc_ready ((gpointer *)&vfs.start_operation)) { E2_VFSMonitor opdata; //E2_VFSTMP if progress is shown, set opdata message and title and flag opdata.flags = E2VFS_TIMER | E2VFS_CANCEL; if (vfs.start_operation (localpath->spacedata, IFACE_OFFSET (delete), &opdata)) { opdata.error = E2_ERR_NAME; //no need for local error here result = opdata.iface->delete (localpath, &opdata); vfs.finish_operation (&opdata); } } } return result; } gint e2_fs_remove (VPATH *localpath, GError **E2_ERR_NAME) { gint result; if (e2_fs_item_is_mounted (localpath)) { result = remove (VPCSTR(localpath)); if (result != 0) e2_fs_set_error_from_errno (E2_ERR_NAME); } else //virtual { result = -1; if (e2_fs_vfsfunc_ready ((gpointer *)&vfs.start_operation)) { E2_VFSMonitor opdata; //E2_VFSTMP if progress is shown, set opdata message and title and flag opdata.flags = E2VFS_TIMER | E2VFS_CANCEL; if (vfs.start_operation (localpath->spacedata, IFACE_OFFSET (delete), &opdata)) { opdata.error = E2_ERR_NAME; //no need for local error here result = opdata.iface->delete (localpath, &opdata); vfs.finish_operation (&opdata); } } } return result; } gint e2_fs_rmdir (VPATH *localpath, GError **E2_ERR_NAME) { gint result; if (e2_fs_item_is_mounted (localpath)) { result = rmdir (VPCSTR(localpath)); if (result != 0) e2_fs_set_error_from_errno (E2_ERR_NAME); } else //need to do virtual { result = -1; if (e2_fs_vfsfunc_ready ((gpointer *)&vfs.start_operation)) { E2_VFSMonitor opdata; //E2_VFSTMP if progress is shown, set opdata message and title and flag opdata.flags = E2VFS_TIMER | E2VFS_CANCEL; if (vfs.start_operation (localpath->spacedata, IFACE_OFFSET (delete), &opdata)) { opdata.error = E2_ERR_NAME; //no need for local error here result = opdata.iface->delete (localpath, &opdata); vfs.finish_operation (&opdata); } } } return result; } gint e2_fs_mkdir (VPATH *localpath, mode_t mode, GError **E2_ERR_NAME) { gint result; if (e2_fs_item_is_mounted (localpath)) { result = mkdir (VPCSTR(localpath), mode); if (result != 0) e2_fs_set_error_from_errno (E2_ERR_NAME); } else //need to do virtual { result = -1; if (e2_fs_vfsfunc_ready ((gpointer *)&vfs.start_operation)) { E2_VFSMonitor opdata; //E2_VFSTMP if progress is shown, set opdata message and title and flag opdata.flags = E2VFS_TIMER | E2VFS_CANCEL; if (vfs.start_operation (localpath->spacedata, IFACE_OFFSET (mkdir), &opdata)) { opdata.error = E2_ERR_NAME; //no need for local error here result = opdata.iface->mkdir (localpath, mode, &opdata); vfs.finish_operation (&opdata); } } } return result; } gint e2_fs_chdir_local (VPATH *localpath, GError **E2_ERR_NAME) { gint result; if (e2_fs_item_is_mounted (localpath)) { result = chdir (VPCSTR(localpath)); if (result != 0) e2_fs_set_error_from_errno (E2_ERR_NAME); return result; } else //need to do virtual { #ifdef E2_VFSTMP //CHECKME never relevant to do virtual change ?? #endif return -1; } } gint e2_fs_rename (VPATH *oldpath, VPATH *newpath, GError **E2_ERR_NAME) { gint result; if (e2_fs_item_is_mounted (oldpath) && e2_fs_item_is_mounted (newpath)) { result = rename (VPCSTR(oldpath), VPCSTR(newpath)); if (result != 0) e2_fs_set_error_from_errno (E2_ERR_NAME); } else //need to do virtual { result = -1; if (e2_fs_vfsfunc_ready ((gpointer *)&vfs.start_operation)) { E2_VFSMonitor opdata; //E2_VFSTMP if progress is shown, set opdata message and title and flag opdata.flags = E2VFS_TIMER | E2VFS_CANCEL; if (vfs.start_operation (oldpath->spacedata, IFACE_OFFSET (rename), &opdata)) //CHECKME newpath too ?? { opdata.error = E2_ERR_NAME; //no need for local error here result = opdata.iface->rename (oldpath, newpath, &opdata); vfs.finish_operation (&opdata); } } } return result; } gint e2_fs_chmod (VPATH *localpath, mode_t mode, GError **E2_ERR_NAME) { gint result; if (e2_fs_item_is_mounted (localpath)) { result = chmod (VPCSTR(localpath), mode); if (result != 0) e2_fs_set_error_from_errno (E2_ERR_NAME); } else //virtual { result = -1; if (e2_fs_vfsfunc_ready ((gpointer *)&vfs.start_operation)) { E2_VFSMonitor opdata; //E2_VFSTMP if progress is shown, set opdata message and title and flag opdata.flags = E2VFS_TIMER | E2VFS_CANCEL; if (vfs.start_operation (localpath->spacedata, IFACE_OFFSET (chmod), &opdata)) { opdata.error = E2_ERR_NAME; //no need for local error here result = opdata.iface->chmod (localpath, mode, &opdata); vfs.finish_operation (&opdata); } } } return result; } gint e2_fs_chown (VPATH *localpath, uid_t owner, gid_t group, GError **E2_ERR_NAME) { gint result; if (e2_fs_item_is_mounted (localpath)) { result = chown (VPCSTR(localpath), owner, group); if (result != 0) e2_fs_set_error_from_errno (E2_ERR_NAME); } else //need to do virtual { result = -1; if (e2_fs_vfsfunc_ready ((gpointer *)&vfs.start_operation)) { E2_VFSMonitor opdata; //E2_VFSTMP if progress is shown, set opdata message and title and flag opdata.flags = E2VFS_TIMER | E2VFS_CANCEL; if (vfs.start_operation (localpath->spacedata, IFACE_OFFSET (chown), &opdata)) { opdata.error = E2_ERR_NAME; //no need for local error here result = opdata.iface->chown (localpath, owner, group, &opdata); vfs.finish_operation (&opdata); } } } return result; } gint e2_fs_lchown (VPATH *localpath, uid_t owner, gid_t group, GError **E2_ERR_NAME) { gint result; if (e2_fs_item_is_mounted (localpath)) { result = lchown (VPCSTR(localpath), owner, group); if (result != 0) e2_fs_set_error_from_errno (E2_ERR_NAME); } else //need to do virtual { result = -1; if (e2_fs_vfsfunc_ready ((gpointer *)&vfs.start_operation)) { E2_VFSMonitor opdata; //E2_VFSTMP if progress is shown, set opdata message and title and flag opdata.flags = E2VFS_TIMER | E2VFS_CANCEL; if (vfs.start_operation (localpath->spacedata, IFACE_OFFSET (lchown), &opdata)) { opdata.error = E2_ERR_NAME; //no need for local error here result = opdata.iface->lchown (localpath, owner, group, &opdata); vfs.finish_operation (&opdata); } } } return result; } gint e2_fs_utime (VPATH *localpath, const struct utimbuf *buf, GError **E2_ERR_NAME) { gint result; if (e2_fs_item_is_mounted (localpath)) { result = utime (VPCSTR(localpath), buf); if (result != 0) e2_fs_set_error_from_errno (E2_ERR_NAME); } else { result = -1; if (e2_fs_vfsfunc_ready ((gpointer *)&vfs.start_operation)) { //E2_VFSTMP hanle NULL buf E2_VFSMonitor opdata; //E2_VFSTMP if progress is shown, set opdata message and title and flag opdata.flags = E2VFS_TIMER | E2VFS_CANCEL; if (vfs.start_operation (localpath->spacedata, IFACE_OFFSET (touch), &opdata)) { opdata.error = E2_ERR_NAME; //no need for local error here result = opdata.iface->touch (localpath, buf, &opdata); vfs.finish_operation (&opdata); } } } return result; } #endif //def E2_VFS /** @brief change time(s) of item @a localpath to current system time @param localpath localised string with absolute path of item to touch @return TRUE if the operation succeeded */ gboolean e2_fs_touchnow (VPATH *localpath E2_ERR_ARG()) { return (e2_fs_utime (localpath, NULL E2_ERR_SAMEARG()) == 0); } /** @brief update m/a/c times of config dir This should be called after writing the config/cache files to let any other instance know that it should re-read those files (if that option is in force) Error message expects BGL to be closed @return */ void e2_fs_touch_config_dir (void) { #ifdef E2_FAM if (app.monitor_type != E2_MONITOR_DEFAULT) return; #endif struct stat stat_buf; E2_ERR_DECLARE gchar *local = F_FILENAME_TO_LOCALE (e2_cl_options.config_dir); #ifdef E2_VFS VPATH data = { local, NULL }; //only local config data if (!e2_fs_utime (&data, NULL E2_ERR_PTR())) #else if (!e2_fs_utime (local, NULL E2_ERR_PTR())) #endif { #ifdef E2_VFS if (!e2_fs_stat (&data, &stat_buf E2_ERR_PTR())) #else if (!e2_fs_stat (local, &stat_buf E2_ERR_PTR())) #endif app.config_mtime = stat_buf.st_mtime; else { printd (WARN, "couldn't stat the config dir"); #ifdef E2_VFS e2_output_print_error (E2_ERR_NAME->message, FALSE); #else e2_output_print_strerrno (); #endif E2_ERR_CLEAR } } else { printd (WARN, "couldn't touch the config dir"); #ifdef E2_VFS e2_output_print_error (E2_ERR_NAME->message, FALSE); #else e2_output_print_strerrno (); #endif E2_ERR_CLEAR } F_FREE (local); } /** @brief tolerantly open file @a localpath and return its descriptor File descriptors are only relevant for local files, so no vfs treatment is needed @param localpath absolute path of file to open, localised string @param openflags bitflags to provide to open() @param mode mode used by open() when creating a file, -1 to get and use default @return descriptor of opened file, -1 on error */ gint e2_fs_safeopen (const gchar *localpath, gint openflags, mode_t mode) { gint res; if (mode == (mode_t)-1) { mode = umask (0); umask (mode); mode = ~mode & ALLPERMS; } do res = open (localpath, openflags, mode); while (res == -1 && errno == EINTR); return res; } /** @brief open file @a filepath for writing Error message expects BGL to be closed @param utfpath path of file to open, utf8 string @return pointer to data struct for opened file, or NULL if the open failed */ E2_FILE *e2_fs_open_writestream (const gchar *localpath E2_ERR_ARG()) { E2_FILE *f; #ifdef E2_VFSTMP FIXME get proper spacedata if this is to be used for non-local files #endif #ifdef E2_VFS VPATH ddata = { localpath, NULL }; if (e2_fs_item_is_mounted ((&ddata))) { #endif f = e2_fs_open_stream (localpath, "w"); if (f == NULL) { gchar *utf = F_FILENAME_FROM_LOCALE (localpath); gchar *msg = g_strdup_printf (_("Cannot open '%s' for writing - %s"), utf, g_strerror (errno)); e2_output_print_error (msg, TRUE); F_FREE (utf); } #ifdef E2_VFS } else //non-native open { # ifdef E2_VFSTMP //FIXME f = NULL; # endif } #endif return f; } /** @brief write @a string to local file @a localpath Error message expects BGL to be closed @param stream handle for file @param string NULL-terminated string to be written @param localpath file to be written, localised string, for error reporting @return EOF if a write error occurs, otherwise a non-negative value */ gint e2_fs_put_stream (E2_FILE *stream, const gchar *string, gchar *localpath E2_ERR_ARG()) { gint retval; #ifdef E2_VFSTMP FIXME get proper spacedata if virtual streaming is supported localpath is sometimes nominal, not a usable path VPATH data = { localpath, NULL }; //local space if (e2_fs_item_is_mounted ((&data))) { #endif retval = fputs (string, stream); //local only if (retval == EOF) { #ifdef E2_VFS E2_ERR_BACKUP (localerr); e2_fs_set_error_from_errno (E2_ERR_NAME); //apply relevant error data #endif gchar *utf = F_FILENAME_FROM_LOCALE (localpath); gchar *msg = g_strdup_printf (_("Error writing file '%s' - %s"), utf #ifdef E2_VFS E2_ERR_MSGC() #else , g_strerror (errno) #endif ); e2_output_print_error (msg, TRUE); F_FREE (utf); E2_ERR_CLEARBACKUP (localerr); } #ifdef E2_VFSTMP } else //non-native write { //FIXME retval = EOF; } #endif return retval; } /** @brief read from file into buffer @param descriptor file descriptor @param buffer start of memory to hold the read data @param bufsize size of @a buffer @return no. of bytes read, < 0 on error */ ssize_t e2_fs_read (gint descriptor, gpointer buffer, /*size_t*/ gulong bufsize E2_ERR_ARG()) { #ifdef E2_VFSTMP no version of this available yet #endif /* ssize_t nread; do nread = read (descriptor, buffer, bufsize); while (nread < 0 && E2_ERR_IS (EINTR)); return nread; */ if (bufsize == 0) return 0; ssize_t nread; size_t space = (size_t) bufsize; size_t total = 0; gpointer target = buffer; while (space > 0) { nread = read (descriptor, target, space); if (nread > 0) { space -= nread; total += nread; target += nread; //irrelevant if we're finished now } else if (nread == 0 || errno != EINTR) //nread < 0 break; } return (ssize_t)total; } /** @brief read file @a localpath into memory Note the file needs to be size-limited, for local files at least, to what can be represented by a gulong, i.e. to ULONG_MAX. This is to allow allocation by g_..., to simplify later cleanups (not supposed to mix glib and glibc mamaory-management) Memory is allocated by g_malloc, not malloc, and needs to be cleared by g_free, not free Error messages expect BGL open/off @param localpath path of file to be read, localised string @param contents store for ptr start of the allocated contents @param contlength store for no. of bytes written, or NULL if not interested @param string TRUE to add a trailing 0 if that's missing @return TRUE if @a localpath was read successfully */ gboolean e2_fs_get_file_contents (VPATH *localpath, gpointer *contents, /*ssize_t*/ gulong *contlength, gboolean string E2_ERR_ARG()) { gboolean retval; #ifdef E2_VFS if (e2_fs_item_is_mounted (localpath)) { #endif struct stat sb; retval = TRUE; const gchar *fmt = NULL; gint fdesc = e2_fs_safeopen (VPCSTR(localpath), O_RDONLY, 0); if (fdesc >= 0) { if (!e2_fs_fstat (fdesc, &sb)) { size_t len = sb.st_size; if (string) len += sizeof (gchar); //in case we need to append a 0 // *contents = malloc (len); //not size-limited, but imposes cleanup hassles *contents = g_try_malloc (len); //allows up to ULONG_MAX if (*contents == NULL) { e2_utils_show_memory_message (); #ifdef E2_VFSTMP // *E2_ERR_NAME = g_error_new_literal (G_FILE_ERROR, errno, g_strerror (errno)); // E2_ERR_CLEARBACKUP (localerr); #endif e2_fs_safeclose (fdesc); //bypass the normal cleanup process return FALSE; } /*ssize_t*/gulong nread = e2_fs_read (fdesc, *contents, sb.st_size E2_ERR_SAMEARG()); if (nread < 0 //|| nread < (gulong)sb.st_size ) { g_free (*contents); //or free() if needed *contents = NULL; nread = 0; retval = FALSE; fmt = _("Error reading file %s"); } else if (string) { gchar *s; if (nread >= sizeof (gchar)) { s = *contents + nread - sizeof (gchar); if (*s == '\0') nread -= sizeof (gchar); //OK ? else { s += sizeof (gchar); *s = '\0'; } } else //nothing read { s = *contents; *s = '\0'; } } if (contlength != NULL) *contlength = nread; } else //stat failed { retval = FALSE; fmt = _("Cannot get information about %s"); } e2_fs_safeclose (fdesc); } else //open failed { retval = FALSE; fmt = _("Cannot open file %s"); } #ifdef E2_VFS } else //item is virtual { # ifdef E2_VFSTMP //get information from virtual item # endif retval = FALSE; fmt = _("Error reading file %s"); //FIXME } #endif if (!retval && app.output.opt_show_on_new != NULL) //at session-start, when reading config //etc, we can't yet show any error { E2_ERR_BACKUP (localerr); #ifdef E2_VFS e2_fs_set_error_from_errno (E2_ERR_NAME); #endif e2_fs_error_local (fmt, localpath E2_ERR_MSGC()); E2_ERR_CLEARBACKUP (localerr); } return retval; } /** @brief read from file into buffer @param descriptor file descriptor @param buffer start of memory to hold the write data @param bufsize size of @a buffer @return no. of bytes written, < 0 on error */ ssize_t e2_fs_write (gint descriptor, gpointer buffer, /*size_t*/ gulong bufsize E2_ERR_ARG()) { #ifdef E2_VFSTMP //no version of this available yet #endif ssize_t written; if (bufsize == 0) return 0; do written = write (descriptor, buffer, bufsize); while ((written < bufsize && written >= 0) || errno == EINTR); return written; } /** @brief write whole file @a localpath, from memory starting at @a contents No overwrite checking is done @param localpath path of file to be write, localised string @param contents ptr to the start of the data to save @param contlength byte-length of data to save @param mode permissions to apply, or -1 for default @return TRUE if @a locapath was written successfully */ gboolean e2_fs_set_file_contents (VPATH *localpath, gpointer contents, size_t contlength, mode_t mode E2_ERR_ARG()) { gboolean retval; #ifdef E2_VFS if (e2_fs_item_is_mounted (localpath)) { #endif retval = TRUE; const gchar *fmt = NULL; gint fdesc = e2_fs_safeopen (VPCSTR(localpath), O_WRONLY | O_CREAT, mode); if (fdesc < 0) { retval = FALSE; fmt = _("Cannot create file %s"); } if (retval) { ssize_t written = 0; while (written < contlength) { written = TEMP_FAILURE_RETRY (write (fdesc, contents, contlength)); if (written < 0) { retval = FALSE; fmt = _("Error writing file %s"); break; } } if (retval) ftruncate (fdesc, written); e2_fs_safeclose (fdesc); } if (!retval) { // unlink (VPCSTR(localpath)); E2_ERR_BACKUP (localerr); #ifdef E2_VFS e2_fs_set_error_from_errno (E2_ERR_NAME); #endif e2_fs_error_local (fmt, localpath E2_ERR_MSGC()); E2_ERR_CLEARBACKUP (localerr); unlink (VPCSTR(localpath)); } #ifdef E2_VFS } else //item is virtual { # ifdef E2_VFSTMP //send information to virtual item # endif retval = FALSE; } #endif return retval; } /** @brief tolerantly close file descriptor File descriptors are only relevant for local files, so no vfs treatment is needed @param file_desc file descriptor @return 0 on success, -1 on error */ gint e2_fs_safeclose (gint file_desc) { gint res; do res = (gint) close (file_desc); while (res == -1 && errno == EINTR); return res; } /** @brief perform a native, blockwise, file copy Error messages assume BGL is open @param vsrc localised string, absolute path of item to copy @param src_sb pointer to valid struct stat for @a src @param vdest localised string, absolute path of copy destination @return TRUE if the copy was completed successfully */ gboolean e2_fs_copy_file (VPATH *src, const struct stat *src_sb, VPATH *dest E2_ERR_ARG()) { gboolean retval; #ifdef E2_VFS if (e2_fs_item_is_mounted (src) && e2_fs_item_is_mounted (dest)) { #endif // struct stat src_sb; struct stat dest_sb; E2_ERR_BACKUP (localerr); //CHECKME vfs e2_fs_open|close ever needed for closing while native copying ? //O_NOATIME is GNU-specific, != 'cp', and requires write-permission on item //O_NOFOLLOW causes spurious error gint src_desc = e2_fs_safeopen (VPCSTR(src), O_RDONLY, 0); if (src_desc < 0) { #ifdef E2_VFS e2_fs_set_error_from_errno (E2_ERR_NAME); #endif e2_fs_error_local (_("Cannot open '%s' for reading"), src E2_ERR_MSGC()); E2_ERR_CLEARBACKUP (localerr); return FALSE; } mode_t dest_mode = (src_sb->st_mode | S_IWUSR) & ALLPERMS; gint dest_desc = e2_fs_safeopen (VPCSTR(dest), O_WRONLY | O_CREAT | O_EXCL, dest_mode); if (dest_desc < 0) { #ifdef E2_VFS e2_fs_set_error_from_errno (E2_ERR_NAME); #endif e2_fs_error_local (_("Cannot create file %s"), dest E2_ERR_MSGC()); E2_ERR_CLEARBACKUP (localerr); TEMP_FAILURE_RETRY (close (src_desc)); return FALSE; } if (e2_fs_fstat (dest_desc, &dest_sb)) { #ifdef E2_VFS e2_fs_set_error_from_errno (E2_ERR_NAME); #endif e2_fs_error_local (_("Cannot get information about %s"), dest E2_ERR_MSGC()); E2_ERR_CLEARBACKUP (localerr); TEMP_FAILURE_RETRY (close (src_desc)); TEMP_FAILURE_RETRY (close (dest_desc)); return FALSE; } // use blocks the same size as dest block (like cp) // blksize_t buf_size = dest_sb.st_blksize; //or src ?? // gchar *buf = g_alloca (buf_size); //find a buffer size up to 1 MB (!= the 'cp' approach) // blksize_t buf_size = 1048576; //find a buffer up to 16 times dest block //(compromise between accesses and multi-tasking latency) blksize_t buf_size = dest_sb.st_blksize * 16; size_t src_size = src_sb->st_size * 2; while (buf_size > src_size && buf_size > dest_sb.st_blksize) buf_size /= 2; gpointer buf; #ifdef USE_GLIB2_10 while ((buf = g_slice_alloc (buf_size)) == NULL) #else while ((buf = g_try_malloc (buf_size)) == NULL) #endif { if (buf_size < dest_sb.st_blksize) { gdk_threads_enter (); e2_utils_show_memory_message (); gdk_threads_leave (); TEMP_FAILURE_RETRY (close (src_desc)); TEMP_FAILURE_RETRY (close (dest_desc)); return FALSE; } buf_size /= 2; } //CHECKME suspend access-monitoring here ... retval = TRUE; while (1) { ssize_t n_read = TEMP_FAILURE_RETRY (read (src_desc, buf, buf_size)); //(potential cancellation point, LEAKS) if (n_read == 0) break; if (n_read < 0) { #ifdef E2_VFS e2_fs_set_error_from_errno (E2_ERR_NAME); #endif e2_fs_error_local (_("Error reading file %s"), src E2_ERR_MSGC()); retval = FALSE; break; } ssize_t n_write = 0; while (n_write < n_read) { n_write = TEMP_FAILURE_RETRY (write (dest_desc, buf, n_read)); //(potential cancellation point, LEAKS) if (n_write < 0) { #ifdef E2_VFS e2_fs_set_error_from_errno (E2_ERR_NAME); #endif e2_fs_error_local (_("Error writing file %s"), dest E2_ERR_MSGC()); retval = FALSE; break; } } if (!retval) break; // pthread_testcancel (); //swap threads, cancel if instructed (leaks if so) } TEMP_FAILURE_RETRY (close (src_desc)); TEMP_FAILURE_RETRY (close (dest_desc)); if (!retval) { E2_ERR_CLEARBACKUP (localerr); unlink (VPCSTR(dest)); } #ifdef USE_GLIB2_10 g_slice_free1 (buf_size, buf); #else g_free (buf); #endif #ifdef E2_VFS } else //one or both of src/dest is non-native { retval = FALSE; if (e2_fs_vfsfunc_ready ((gpointer *)&vfs.start_operation)) { E2_VFSMonitor opdata; gboolean with_progress = FALSE; //FIXME API opdata.flags = E2VFS_TIMER | E2VFS_CANCEL; //E2_VFSTMP if progress is shown, set opdata message and title and flag if (vfs.start_operation (src->spacedata, IFACE_OFFSET(copy), &opdata)) //CHECKME dest may be different { opdata.error = E2_ERR_NAME; //no need for local error here retval = opdata.iface->copy (src, dest, with_progress, &opdata); vfs.finish_operation (&opdata); } } } #endif return retval; } /** @brief open pipe to read output from @a command Error message expects BGL to be closed @param command localised command string @return pointer to opened pipe, or NULL if open failed */ E2_FILE *e2_fs_open_pipe (gchar *command) { E2_FILE *pipe; if ((pipe = popen (command, "r")) == NULL) //no vfs command-execution for piping { gchar *s, *utf; if ((s = e2_utils_find_whitespace (command)) != NULL) *s = '\0'; utf = e2_utf8_from_locale (command); s = g_strdup_printf (_("Cannot open pipe for command '%s'"), utf); e2_output_print_error (s, TRUE); g_free (utf); } return pipe; } /** @brief read piped command output into memory A terminating 0 is always added Error message expects BGL to be closed @param command utf command string to be executed @param output store for ptr to command output string @return TRUE if @a command result was read successfully */ gboolean e2_fs_get_command_output (gchar *command, gpointer *output) { gchar *local = F_FILENAME_TO_LOCALE (command); E2_FILE *pipe = e2_fs_open_pipe (local); F_FREE (local); if (pipe == NULL) return FALSE; gchar *msg = g_strdup_printf ( _("Command %s failed: not enough memory"), command); size_t total = 0; size_t bsize = 4096; //block size gpointer buf = g_try_malloc ((gulong) bsize); if (buf == NULL) { e2_output_print_error (msg, TRUE); e2_fs_pipe_close (pipe); return FALSE; } gpointer store = buf; ssize_t bytes_read; while ((bytes_read = fread (store, 1, bsize, pipe)) > 0) { if (bytes_read == bsize) { total += bsize; buf = g_try_realloc (buf, total+bsize); if (buf == NULL) { e2_output_print_error (msg, TRUE); e2_fs_pipe_close (pipe); return FALSE; } store = buf + total; } else { *((gchar *)store+bytes_read) = '\0'; bytes_read++; total += bytes_read; buf = g_try_realloc (buf, total); if (buf == NULL) { e2_output_print_error (msg, TRUE); e2_fs_pipe_close (pipe); return FALSE; } break; } } g_free (msg); e2_fs_pipe_close (pipe); if (total == 0) g_free (buf); else *output = buf; return (total > 0); } /** @brief read up to @a count -1 bytes of piped command output into memory A terminating 0 is always added @param command utf command string to be executed @param contents ptr to where the contents are to be stored @param count size of block to read + 1 (for added 0) @return TRUE if @a command result was read successfully */ gboolean e2_fs_sniff_command_output (gchar *command, gpointer *contents, gulong count) { gchar *local = F_FILENAME_TO_LOCALE (command); E2_FILE *pipe = e2_fs_open_pipe (local); F_FREE (local); if (pipe == NULL) return FALSE; *contents = g_try_malloc (count); if (*contents == NULL) { //FIXME warn user e2_fs_pipe_close (pipe); return FALSE; } ssize_t bytes_read = fread (*contents, 1, (size_t)count-1, pipe); if (bytes_read < 0) return FALSE; *(*((gchar**)contents) + bytes_read) = '\0'; e2_fs_pipe_close (pipe); return TRUE; } /* * @brief blockwize read from stdin into memory A terminating 0 is always added Error message expects BGL to be closed @param contents ptr to set to where the input is stored @return TRUE if stdin was read successfully */ /*UNUSED gboolean e2_fs_read_stdin (gchar **contents) { fd_set fds; FD_ZERO (&fds); FD_SET (STDIN_FILENO, &fds); struct timeval wait; wait.tv_sec = 0; wait.tv_usec = 100000; if (select (STDIN_FILENO+1, &fds, NULL, NULL, &wait) != 1) return FALSE; size_t total = 0; size_t bsize = 1024; //block size gchar *buf = (gchar *) g_try_malloc ((gulong) bsize); if (buf == NULL) return FALSE; gchar *store = buf; ssize_t bytes_read; while (feof (stdin) == 0) { bytes_read = fread (buf, 1, bsize, stdin); if (ferror (stdin) != 0) { g_free (buf); return FALSE; //error msg ?? } if (bytes_read == bsize) { total += bsize; buf = g_try_realloc (buf, total+bsize); if (buf == NULL) { e2_output_print_error (_("Failed stdin read: not enough memory"), FALSE); e2_fs_close_stream (stdin); return FALSE; } store = buf + total; } else { *(store+bytes_read) = '\0'; bytes_read++; buf = g_try_realloc (buf, total + bytes_read); if (buf == NULL) { e2_fs_close_stream (stdin); return FALSE; } break; } } e2_fs_close_stream (stdin); *contents = buf; return TRUE; } */ /* * @brief write a sequence of bytes from memory to stdout @param contents ptr to 0-terminated sequence of bytes to be written @return TRUE if @a contents was sent successfully */ /*UNUSED gboolean e2_fs_write_stdout (gchar *contents) { fd_set fds; FD_ZERO (&fds); FD_SET (STDOUT_FILENO, &fds); struct timeval wait; wait.tv_sec = 0; wait.tv_usec = 100000; if (select (STDOUT_FILENO+1, NULL, &fds, NULL, &wait) != 1) return FALSE; return (e2_fs_put_stream (stdout, contents, "stdout" E2_ERR_NONE()) != EOF); } */ static gunichar realperms[10]; static gunichar ftypes[6]; static gunichar fbits[4]; /** @brief convert file mode_t to a readable permissions string Get string representing the permissions in @a mode. The order of displayed permissions is the same as the corresponding bitflags in @a mode, typically rwx for each of owner, group, other, in turn @param mode mode_t property to be parsed @return newly-allocated translated utf8 permissions string */ gchar *e2_fs_get_perm_string (mode_t mode) { //inspiration here from David Jensen gunichar thisperms[11]; //once-only, get a local copy of processed strings, to eliminate repeated [de]allocations if (realperms[0] == 0) { //NOTE for translators - this string has single-letters indicating // read write execute permissions. Their order is significant gunichar *temp = g_utf8_to_ucs4_fast (_("-rwxrwxrwx"), -1, NULL); memcpy (realperms, temp, sizeof (realperms)); g_free (temp); //NOTE for translators - this string has single-letters indicating // link, dir, block, char, fifo, socket filetypes. Their order is significant temp = g_utf8_to_ucs4_fast (_("ldbcfs"), -1, NULL); memcpy (ftypes, temp, sizeof (ftypes)); g_free (temp); //NOTE for translators - this string has single-letters indicating // sticky, suid, sgid permissions. Their order is significant temp = g_utf8_to_ucs4_fast (_("TtSs"), -1, NULL); memcpy (fbits, temp, sizeof (fbits)); g_free (temp); } memcpy (thisperms, realperms, sizeof (realperms)); thisperms [10] = 0; gint mask = 1; gint i; //assuming that mode flags are in the same order as the perms string ... for (i = 9; i > 0; --i) { if (!(mode & mask)) thisperms[i] = thisperms[0]; mask = mask << 1; } if (mode & S_ISVTX) thisperms[9] = (thisperms[9] == thisperms[0]) ? fbits[0] : fbits[1]; if (mode & S_ISGID) thisperms[6] = (thisperms[6] == thisperms[0]) ? fbits[2] : fbits[3]; if (mode & S_ISUID) thisperms[3] = (thisperms[3] == thisperms[0]) ? fbits[2] : fbits[3]; switch (mode & S_IFMT) { #ifdef S_IFLNK case S_IFLNK: i = 0; break; #else # warning "SYMLINK type not supported" #endif case S_IFDIR: i = 1; break; case S_IFBLK: i = 2; break; case S_IFCHR: i = 3; break; #ifdef S_IFIFO case S_IFIFO: i = 4; break; #else # warning "FIFO type not supported" #endif #ifdef S_IFSOCK case S_IFSOCK: i = 5; break; #else # warning "SOCKET type not supported" #endif default: i = -1; break; } if (i >= 0) thisperms[0] = ftypes[i]; gchar *retval = g_ucs4_to_utf8 (thisperms, -1, NULL, NULL, NULL); return retval; } /** @brief display error message @a msg, with current system error description Assumes BGL is open @param msg utf-8 string message to display @return FALSE (==error signal) always */ gboolean e2_fs_error (gchar *msg #ifdef E2_VFS , gchar *reason #endif ) { gchar *long_msg = g_strconcat (msg, " - ", #ifdef E2_VFS reason #else (gchar *) g_strerror (errno) #endif , NULL); gdk_threads_enter (); e2_output_print_error (long_msg, TRUE); gdk_threads_leave (); return FALSE; } /** @brief construct and display error message, with current system error description Assumes BGL is open @param format utf-8 format string (with a "%s") for the message to display @param local localised itemname or path string to be incorporated into the message @return FALSE (==error signal) always */ gboolean e2_fs_error_local (const gchar *format, VPATH *local #ifdef E2_VFS , gchar *reason #endif ) { gchar *utf = F_DISPLAYNAME_FROM_LOCALE (VPSTR(local)); //for output pane printing, no need to escape any pango-annoying component //of the itemname gchar *msg = g_strdup_printf (format, utf); e2_fs_error (msg #ifdef E2_VFS , reason #endif ); F_FREE (utf); g_free (msg); return FALSE; } /** @brief construct and display error message using @a format and @a local Assumes BGL is open @param format utf-8 string, format (with a "%s") for the message to display @param local localised itemname or path string to be incorporated into the message @return FALSE (==error signal) always */ gboolean e2_fs_error_simple (const gchar *format, VPATH *local) { gchar *utf = F_DISPLAYNAME_FROM_LOCALE (VPSTR(local)); //no need to escape any pango-annoying component of the itemname gchar *msg = g_strdup_printf (format, utf); gdk_threads_enter (); e2_output_print_error (msg, TRUE); gdk_threads_leave (); F_FREE (utf); return FALSE; } #ifdef E2_VFS /** @brief create filesystem error based on errno @param error pointer to error data struct, or NULL @return */ void e2_fs_set_error_from_errno (GError **error) { if (error != NULL) { g_clear_error (error); // GFileError err_no = g_file_error_from_errno (errno); // *error = g_error_new_literal (G_FILE_ERROR, err_no, g_str (errno)); *error = g_error_new_literal (G_FILE_ERROR, errno, g_strerror (errno)); } } /** @brief create custom error @param error pointer to error data struct, or NULL @param code error code @param format printf()-style format string for the error message @return */ void e2_fs_set_error_custom (GError **error, gint code, const gchar *format, ...) { if (error != NULL) { gchar *built; g_clear_error (error); if (format != NULL) { const gchar *name = format; gchar *freeme; built = g_strdup (format); va_list args; va_start (args, format); while (name != NULL) { freeme = built; built = g_strconcat (freeme, ", ", name, NULL); g_free (freeme); name = va_arg (args, const gchar*); } va_end (args); } else built = NULL; g_set_error (error, G_FILE_ERROR, code, built); if (built != NULL) g_free (built); } } /** @brief check whether @a func, a member of the vfs functions interface, is available Availability is indicated by non-NULL pointer. The vfs plugin will be loaded if possible @param func store for function-ptr to check @return TRUE if the function was, or now is, available */ gboolean e2_fs_vfsfunc_ready (gpointer *func) { if (!vfs.loaded) { //e2_window_set_cursor (GDK_WATCH); may freeze, probably unnecessary if (!e2_plugins_load_plugin ("vfs"VERSION)) //this will set the function ptr if it succeeds return FALSE; //e2_window_set_cursor (GDK_LEFT_PTR); } return (*func != NULL); } #endif emelfm2-0.4.1/src/filesystem/e2_fs_FAM_inotify.h0000600000175000017500000001024511010340377020353 0ustar cairocairo/* $Id: e2_fs_FAM_inotify.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2005-2008 tooar Portions copyright (C) 2005 John McCutchan This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __E2_FS_FAMINOTIFY_H__ #define __E2_FS_FAMINOTIFY_H__ #include #ifdef IN_DELETE_SUBDIR //we have a superseded inotify header, corrections needed #define IN_CREATE 0x00000100 #define IN_DELETE 0x00000200 #undef IN_DELETE_SELF #define IN_DELETE_SELF 0x00000400 #define IN_MOVE_SELF 0x00000800 #define IN_ISDIR 0x40000000 #endif //def IN_DELETE_SUBDIR //this is a workaround for a buggy linux/inotify.h #ifndef IN_MOVE_SELF #define IN_MOVE_SELF 0x00000800 #endif /* when monitoring a dir we ignore these inotify events: IN_CLOSE_WRITE this should be covered by IN_MODIFY IN_CLOSE_NOWRITE IN_OPEN we're monitoring dirs and config file and don't care about opening and we always get these, so don't need to ask IN_UNMOUNT IN_Q_OVERFLOW IN_IGNORED (item no longer watched, likely due to its deletion (or replacement)) */ #define INDIR_FLAGS \ IN_ACCESS | IN_MODIFY | IN_ATTRIB | IN_MOVED_FROM | IN_MOVED_TO | \ IN_CREATE | IN_DELETE | IN_DELETE_SELF | IN_MOVE_SELF /*for the config file we're interested only in events that would trigger a reload of that file, ie modification or creation. Not deletion, as a replacement may not yet be available. However, in normal course of events, the file is deleted and then replaced, and deletion causes a IN_IGNORED report to be generated, and monitoring ceases to be effective - so no IN_CREATE report is generated, normally. */ #define INCFG_FLAGS \ IN_MODIFY | IN_CREATE //the following are derived from local_inotify_syscalls.h used by gamin 0.1.5 #undef __NR_inotify_init #if defined(__i386__) # define __NR_inotify_init 291 # define __NR_inotify_add_watch 292 # define __NR_inotify_rm_watch 293 #elif defined(__x86_64__) # define __NR_inotify_init 253 # define __NR_inotify_add_watch 254 # define __NR_inotify_rm_watch 255 #elif defined(__alpha__) # define __NR_inotify_init 444 # define __NR_inotify_add_watch 445 # define __NR_inotify_rm_watch 446 #elif defined(__ppc__) || defined(__powerpc__) || defined(__powerpc64__) # define __NR_inotify_init 275 # define __NR_inotify_add_watch 276 # define __NR_inotify_rm_watch 277 #elif defined(__sparc__) || defined (__sparc64__) # define __NR_inotify_init 151 # define __NR_inotify_add_watch 152 # define __NR_inotify_rm_watch 156 #elif defined (__ia64__) # define __NR_inotify_init 1277 # define __NR_inotify_add_watch 1278 # define __NR_inotify_rm_watch 1279 #elif defined (__s390__) || defined (__s390x__) # define __NR_inotify_init 284 # define __NR_inotify_add_watch 285 # define __NR_inotify_rm_watch 286 #elif defined (__arm__) # define __NR_inotify_init 316 # define __NR_inotify_add_watch 317 # define __NR_inotify_rm_watch 318 #elif defined (__SH4__) # define __NR_inotify_init 290 # define __NR_inotify_add_watch 291 # define __NR_inotify_rm_watch 292 #elif defined (__SH5__) # define __NR_inotify_init 318 # define __NR_inotify_add_watch 319 # define __NR_inotify_rm_watch 320 #endif #ifdef __NR_inotify_init #include #define inotify_init(a) syscall (__NR_inotify_init) #define inotify_add_watch(a,b,c) syscall (__NR_inotify_add_watch,a,b,c) #define inotify_rm_watch(a,b) syscall (__NR_inotify_rm_watch,a,b) #else #warning "Unsupported architecture" #define inotify_init(a) -1 #define inotify_add_watch(a,b,c) -1 #define inotify_rm_watch(a,b) -1 #endif //supported arch //end of gamin-derived stuff #endif //ndef __E2_FS_FAMINOTIFY_H__ emelfm2-0.4.1/src/filesystem/e2_fs.h0000600000175000017500000002737711010340377016145 0ustar cairocairo/* $Id: e2_fs.h 874 2008-05-07 14:47:27Z tpgww $ Copyright (C) 2005-2008 tooar This file is part of emelFM2. emelFM2 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 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file src/filesystem/e2_fs.h @brief header for filesystem io functions */ #ifndef __E2_FS_H__ #define __E2_FS_H__ #include "emelfm2.h" #include #include #include #include #define E2_FILE FILE typedef enum { E2_DIRWATCH_NO, E2_DIRWATCH_YES, E2_DIRWATCH_CHECK } E2_FsReadWatch; #ifdef E2_VFS #include "e2_vfs.h" gint e2_fs_stat (VPATH *localpath, struct stat *buf, GError **error); gint e2_fs_access2 (VPATH *localpath, GError **error); gint e2_fs_access3 (VPATH *localpath, gint how, GError **error); gboolean e2_fs_check_write_permission (VPATH *localpath, GError **error); gboolean e2_fs_is_text (VPATH *localpath, GError **error); //gboolean e2_fs_is_link (gchar *localpath, GError **error); gboolean e2_fs_walk_link (gchar **local_path, GError **error); gboolean e2_fs_is_dir3 (VPATH *localpath, GError **error); gboolean e2_fs_recurse_mkdir (VPATH *localpath, gint mode, GError **error); gboolean e2_fs_chdir (gchar *path, GError **error); gboolean e2_fs_get_valid_path (gchar **utfpath, gboolean accessible, GError **error); gboolean e2_fs_cd_isok (gchar *path, GError **error); //FIXME make this work for vfs ssize_t e2_fs_read (gint descriptor, gpointer buffer, /*size_t*/ gulong bufsize, GError **error); gboolean e2_fs_get_file_contents (VPATH *localpath, gpointer *contents, /*ssize_t*/ gulong *contlength, gboolean string, GError **error); ssize_t e2_fs_write (gint descriptor, gpointer buffer, /*size_t*/ gulong bufsize, GError **error); gboolean e2_fs_set_file_contents (VPATH *localpath, gpointer contents, size_t contlength, mode_t mode, GError **error); gpointer e2_fs_dir_foreach (VPATH *localpath, E2_FsReadWatch monitor, gpointer filterfunc, gpointer cb_data, GDestroyNotify free_data_func , GError **error); gboolean e2_fs_copy_file (VPATH *src, const struct stat *src_sb, VPATH *dest, GError **error); gboolean e2_fs_vfsfunc_ready (gpointer *func); #else //ndef E2_VFS //access() looks through links, use e2_fs_access3 to prevent that #define e2_fs_access access #define e2_fs_lstat lstat #define e2_fs_unlink unlink #define e2_fs_remove remove #define e2_fs_rmdir rmdir #define e2_fs_mkdir mkdir //no error check or report with this chdir #define e2_fs_chdir_local chdir //#define e2_fs_dir_open opendir //#define e2_fs_dir_close closedir //#define e2_fs_dir_read readdir_r #define e2_fs_rename rename #define e2_fs_chmod chmod #define e2_fs_chown chown #define e2_fs_lchown lchown #define e2_fs_utime utime #define e2_fs_symlink symlink #define e2_fs_readlink readlink gint e2_fs_stat (VPATH *localpath, struct stat *buf); gint e2_fs_access2 (VPATH *localpath); gint e2_fs_access3 (VPATH *localpath, gint how); gboolean e2_fs_check_write_permission (VPATH *localpath); gboolean e2_fs_is_text (VPATH *localpath); //gboolean e2_fs_is_link (VPATH *localpath); gboolean e2_fs_walk_link (gchar **local_path); gboolean e2_fs_is_dir3 (VPATH *localpath); gboolean e2_fs_recurse_mkdir (VPATH *localpath, gint mode); gboolean e2_fs_chdir (gchar *path); gboolean e2_fs_get_valid_path (gchar **utfpath, gboolean accessible); gboolean e2_fs_cd_isok (gchar *path); ssize_t e2_fs_read (gint descriptor, gpointer buffer, /*size_t*/ gulong bufsize); gboolean e2_fs_get_file_contents (VPATH *localpath, gpointer *contents, /*ssize_t*/ gulong *contlength, gboolean string); ssize_t e2_fs_write (gint descriptor, gpointer buffer, /*size_t*/ gulong bufsize); gboolean e2_fs_set_file_contents (VPATH *localpath, gpointer contents, size_t contlength, mode_t mode); gpointer e2_fs_dir_foreach (VPATH *localpath, E2_FsReadWatch monitor, gpointer filterfunc, gpointer cb_data, GDestroyNotify free_data_func); gboolean e2_fs_copy_file (const gchar *src, const struct stat *src_sb, const gchar *dest); #endif //def E2_VFS //functions that only make sense for local operations #define e2_fs_fstat fstat #define e2_fs_open_stream fopen #define e2_fs_close_stream fclose #define e2_fs_dir_open opendir #define e2_fs_dir_read readdir_r #define e2_fs_dir_close closedir gboolean e2_fs_touchnow (VPATH *localpath #ifdef E2_VFS , GError **error #endif ); E2_FILE *e2_fs_open_writestream (const gchar *localpath #ifdef E2_VFS , GError **error #endif ); gint e2_fs_put_stream (E2_FILE *stream, const gchar *string, gchar *localpath #ifdef E2_VFS , GError **error #endif ); gboolean e2_fs_error (gchar *msg #ifdef E2_VFS , gchar *reason #endif ); gboolean e2_fs_error_local (const gchar *format, VPATH *local #ifdef E2_VFS , gchar *reason #endif ); gboolean e2_fs_error_simple (const gchar *format, VPATH *local); void e2_fs_check_coding (void); gchar *e2_fs_get_localdir (ViewInfo *view); gboolean e2_fs_ingroup (gid_t gid); //formatted file write, used locally for config and cache writing #define e2_fs_file_write g_fprintf //pipes can only be used locally #define e2_fs_pipe_close pclose E2_FILE *e2_fs_open_pipe (gchar *command); //always native gchar *e2_fs_get_perm_string (mode_t mode); //string utility only gboolean e2_fs_dir_is_native (gchar *utfpath); //native comparisons only gboolean e2_fs_complete_dir (GtkWidget *entry, guint keyval, guint pane); gboolean e2_fs_is_executable (FileInfo *info, ViewInfo *view); gboolean e2_fs_is_dir (FileInfo *info, ViewInfo *view); //inline gboolean e2_fs_is_dir_fast (FileInfo *info, struct stat *statbuf); //make this always inline ... //here we use stat() because we want to traverse the link //NOTE no path provided for the stat command, so CWD needs to be local and valid!! /*#define e2_fs_is_dir_fast(a,b) \ S_ISDIR (((FileInfo *)a)->statbuf.st_mode) \ || (S_ISLNK (((FileInfo *)a)->statbuf.st_mode) \ && !stat (((FileInfo *)a)->filename, (struct stat *)b) \ && S_ISDIR (((struct stat *)b)->st_mode)) there's no convenient and quick way to mimic the full path test used when populating the treestore, but the results of that test are a trailing "/" for the items of interest ... */ //#define e2_fs_is_dir_fast(a) g_str_has_suffix(a,G_DIR_SEPARATOR_S) #define e2_fs_is_dir_fast(a) *(a+strlen(a)-sizeof(gchar))==G_DIR_SEPARATOR gboolean e2_fs_get_command_output (gchar *command, gpointer *contents); //always native gboolean e2_fs_sniff_command_output (gchar *command, gpointer *contents, gulong count); //always native gint e2_fs_safeopen (const gchar *localpath, gint openflags, mode_t mode); gint e2_fs_safeclose (gint file_desc); void e2_fs_touch_config_dir (void); //always native //away-from-normal declaration here, to avoid build problems !! void e2_option_tree_write_to_file (E2_FILE *f, E2_OptionSet *set, GtkTreeIter *iter, gint level); #ifdef E2_FAM //these apply whether or not kernel-based fam is being used void e2_fs_FAM_connect (void); void e2_fs_FAM_disconnect (void); void e2_fs_FAM_change (gchar *olddir, E2_PaneRuntime *rt); gboolean e2_fs_FAM_monitor_config (void); gboolean e2_fs_FAM_cancel_monitor_config (void); void e2_fs_FAM_check_dirty (gboolean *p1dirty, gboolean *p2dirty, gboolean *cfgdirty); #else void e2_fs_FAM_check_dirty (gboolean *p1dirty, gboolean *p2dirty); void e2_fs_FAM_config_stamp (void); #endif #ifdef E2_FAM_KERNEL //void e2_fs_FAM_cancel_monitor_dir (gchar *path); //void e2_fs_FAM_monitor_dir (gchar *path); void e2_fs_FAM_clean_reports (gchar *path); #endif //flags for instructing e2_fs_tw how to perform its function typedef enum { E2TW_DEFAULT = 0, E2TW_PHYS = 1, //perform physical walk, don't look though symlinks E2TW_MOUNT = 1 << 1, //report only items on same file system as the argument // E2TW_CHDIR = 1 << 2, //BAD chdir to each subdir after it is opened (probably for native dirs only) // E2TW_DEPTH = 1 << 3, //DISABLED open and process any subdir as soon as it's found, //instead of after all non-subdirs have been processed E2TW_ONLYDIR = 1 << 4, //report only directories and stat() fails E2TW_NODIR = 1 << 5, //no E2TW_D or E2TW_DRR reports - only non-directories //and stat() fails and dirs not-opened (E2TW_DL, E2TW_DM, E2TW_DNR) E2TW_FIXDIR = 1 << 6, //if possible, repair DNR situations and then report DRR instead of DNR E2TW_DC = 1 << 7, //to allow immediate cleanup, issue a fake DP report //after some sorts of in-walker errors and after a //callback func initiates a STOP or a nested dir returns //STOP (not relevant with E2TW_NODIR) E2TW_QT = 1 << 8, //suppress some in-walker error messages e.g. after intentional DL E2TW_XQT = 1 << 9, //suppress ALL in-walker error messages #ifdef E2_VFS E2TW_XERR = 1 << 10 //tells the walker func that the _FIRST_ item in user_data //is a GError** for returning specific error data #endif } E2_TwFlags; //codes for status reports to tw callback functions //NOTE a hanging link to a dir is not identifed by glibc as a dir, //but it is stattable, so it's treated as a non-dir, not E2TW_NS = BUG ?? typedef enum { E2TW_F, //not-directory or link E2TW_SL, //symbolic link to a non-directory E2TW_SLN, //symbolic link to a non-existent non-directory E2TW_D, //opened a directory or symlink to a directory E2TW_DL, //directory, not opened due to tree-depth limit E2TW_DM, //directory, not opened due to different file system E2TW_DP, //directory, all subdirs have been visited E2TW_DNR, //unreadable directory that could not be remediated E2TW_NS, //un-statable item (can be a link to unstattable dir) E2TW_DRR //opened a formerly unreadable directory, now remediated } E2_TwStatus; //codes for feedback from tw callback functions //some of them are not mutually-exclusive typedef enum { E2TW_CONTINUE = 0, //continue processing the items in the dir E2TW_STOP = 1, //abort the walk, return FALSE from e2_fs_tw(); E2TW_SKIPSUB = 1 << 1, //don't open the subdir (only meaningful after E2TW_D or E2TW_DRR) E2TW_CLEAN = 1 << 2, //instruct the walker to issue a DP report, then stop (cleanup for breadth-first walks) E2TW_DRKEEP = 1 << 3, //dir mode-changes prior to a DRR report don't need to be reverted E2TW_FIXME = 1 << 8 //interim value for use inside callback, changed before returning } E2_TwResult; mode_t e2_fs_tw_adjust_dirmode (VPATH *localpath, const struct stat *statptr, gint howflags); gboolean e2_fs_tw (VPATH *start_item, E2_TwResult (*callback) (), gpointer user_data, gint max_depth, E2_TwFlags exec_flags E2_ERR_ARG()); //a limit on level of nested dirs that will be processed in a treewalk #define E2_DIRNEST_LIMIT 32 //gchar *e2_fs_mount_get_mountpoint_for_device (const gchar *device); gboolean e2_fs_mount_is_mountpoint (VPATH *localpath); gboolean e2_fs_mount_is_removable (const gchar *utfpath); gboolean e2_fs_mount_is_ejectable (const gchar *utfpath); gboolean e2_fs_mount_is_cased (ViewInfo *view); GList *e2_fs_mount_get_mounts_list (void); //gboolean permcheck) GList *e2_fs_mount_get_mountable_list (void); //gboolean permcheck) GList *e2_fs_mount_get_fusemounts_list (void); gboolean e2_fs_mount_is_fusepoint (VPATH *localpath); #ifdef E2_FS_MOUNTABLE void e2_fs_mount_actions_register (void); #endif #ifdef E2_HAL gboolean e2_hal_device_is_removable (const gchar *devpath); gboolean e2_hal_device_is_ejectable (const gchar *devpath); void e2_hal_init (void); void e2_hal_disconnect (void); #endif #endif //ndef __E2_FS_H__ emelfm2-0.4.1/debian-unofficial/0000700000175000017500000000000011015120161015330 5ustar cairocairoemelfm2-0.4.1/debian-unofficial/emelfm2.substvars0000600000175000017500000000024710646610322020656 0ustar cairocairoshlibs:Depends=libatk1.0-0 (>= 1.6.0), libc6 (>= 2.3.2.ds1-21), libglib2.0-0 (>= 2.6.0), libgtk2.0-0 (>= 2.6.0), libpango1.0-0 (>= 1.8.0), libx11-6 | xlibs (>> 4.1.0) emelfm2-0.4.1/debian-unofficial/menu.ex0000600000175000017500000000030410521644703016647 0ustar cairocairo?package(emelfm2): needs=X11 section="Apps/Tools"\ title="emelFM2" command="/usr/X11R6/bin/emelfm2"\ longtitle="2-pane Gtk+2 file manager" \ icon="/usr/share/pixmaps/emelfm2/emelfm2_48.png" emelfm2-0.4.1/debian-unofficial/copyright0000600000175000017500000000053310646610322017302 0ustar cairocairoemelFM2 Copyright (C) 2003-2007 tooar This package was first debianized by Florian Zaehringer on Sat 30 Aug 2003 20:34:01 +0200 It was downloaded from http://emelfm2.net It may be redistributed under the terms of the GNU GPL, Version 3 found on Debian systems in the file /usr/share/common-licenses/GPL . emelfm2-0.4.1/debian-unofficial/compat0000600000175000017500000000000210521644703016546 0ustar cairocairo4 emelfm2-0.4.1/debian-unofficial/rules0000700000175000017500000000401110521644703016422 0ustar cairocairo#!/usr/bin/make -f # -*- makefile -*- # Sample debian/rules that uses debhelper. # This file was originally written by Joey Hess and Craig Small. # As a special exception, when this file is copied by dh-make into a # dh-make output file, you may use that output file without restriction. # This special exception was added by Craig Small in version 0.37 of dh-make. # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 CFLAGS = -Wall -g ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) CFLAGS += -O0 else CFLAGS += -O2 endif configure: configure-stamp configure-stamp: dh_testdir # Add here commands to configure the package. touch configure-stamp build: build-stamp build-stamp: configure-stamp dh_testdir # Add here commands to compile the package. $(MAKE) PREFIX=/usr #docbook-to-man debian/emelfm2.sgml > emelfm2.1 touch build-stamp clean: dh_testdir dh_testroot rm -f build-stamp configure-stamp # Add here commands to clean up after the build process. -$(MAKE) clean dh_clean install: build dh_testdir dh_testroot dh_clean -k dh_installdirs # Add here commands to install the package into debian/emelfm2. $(MAKE) install PREFIX=$(CURDIR)/debian/emelfm2/usr/ $(MAKE) install_i18n PREFIX=$(CURDIR)/debian/emelfm2/usr/ cp `pwd`/icons/* `pwd`/debian/emelfm2/usr/share/pixmaps/emelfm2/ # Build architecture-independent files here. binary-indep: build install # We have nothing to do by default. # Build architecture-dependent files here. binary-arch: build install dh_testdir dh_testroot dh_installchangelogs dh_installdocs dh_installexamples # dh_install # dh_installmenu # dh_installdebconf # dh_installlogrotate # dh_installemacsen # dh_installpam # dh_installmime # dh_installinit # dh_installcron # dh_installinfo dh_installman dh_link dh_strip dh_compress dh_fixperms # dh_perl # dh_python # dh_makeshlibs dh_installdeb dh_shlibdeps dh_gencontrol dh_md5sums dh_builddeb binary: binary-indep binary-arch .PHONY: build clean binary-indep binary-arch binary install configure emelfm2-0.4.1/debian-unofficial/changelog0000600000175000017500000000726211011704631017221 0ustar cairocairoemelfm2 (0.4.1-1) unstable; urgency=low * new upstream -- Florian Zaehringer Mon, 19 May 2008 20:03:20 -0400 emelfm2 (0.4.0-1) unstable; urgency=low * new upstream -- Florian Zaehringer Tue, 1 Apr 2008 20:03:20 -0400 emelfm2 (0.3.6-1) unstable; urgency=low * new upstream -- Florian Zaehringer Thu, 29 Nov 2007 20:03:20 -0400 emelfm2 (0.3.5-1) unstable; urgency=low * new upstream -- Florian Zaehringer Mon, 30 July 2007 20:03:20 -0400 emelfm2 (0.3.4-1) unstable; urgency=low * new upstream -- Florian Zaehringer Mon, 21 May 2007 20:03:20 -0400 emelfm2 (0.3.3-1) unstable; urgency=low * new upstream -- Florian Zaehringer Tue, 20 Mar 2007 20:03:20 -0400 emelfm2 (0.3.2-1) unstable; urgency=low * new upstream -- Florian Zaehringer Thu, 18 Jan 2007 20:03:20 -0400 emelfm2 (0.3.1-1) unstable; urgency=low * new upstream -- Florian Zaehringer Wed, 22 Nov 2006 20:03:20 -0400 emelfm2 (0.3.0-1) unstable; urgency=low * new upstream -- Florian Zaehringer Tue, 31 Oct 2006 20:03:20 -0400 emelfm2 (0.2.0-1) unstable; urgency=low * new upstream -- Florian Zaehringer Thu, 26 Aug 2006 20:03:20 -0400 emelfm2 (0.1.8-1) unstable; urgency=low * new upstream -- Florian Zaehringer Thu, 27 Jul 2006 20:03:20 -0400 emelfm2 (0.1.7-1) unstable; urgency=low * new upstream -- Florian Zaehringer Thu, 27 Apr 2006 20:03:20 -0400 emelfm2 (0.1.6-1) unstable; urgency=low * new upstream -- Florian Zaehringer Tue, 14 Mar 2006 20:03:20 -0400 emelfm2 (0.1.5-1) unstable; urgency=low * new upstream -- Florian Zaehringer Tue, 24 Jan 2006 20:03:20 -0400 emelfm2 (0.1.4-1) unstable; urgency=low * new upstream -- Florian Zaehringer Thu, 29 Dec 2005 20:03:20 -0400 emelfm2 (0.1.3-1) unstable; urgency=low * new upstream -- Florian Zaehringer Fri, 18 Nov 2005 20:03:20 -0400 emelfm2 (0.1.2-2) unstable; urgency=low * included locales into debian package -- Florian Zaehringer Sat, 17 Sep 2005 10:34:36 -0400 emelfm2 (0.1.2-1) unstable; urgency=low * new upstream -- Florian Zaehringer Thu, 15 Sep 2005 20:03:20 -0400 emelfm2 (0.1.0-1) unstable; urgency=low * new upstream -- Florian Zaehringer Tue, 12 Jun 2005 01:19:52 +0100 emelfm2 (0.0.9-1) unstable; urgency=low * new upstream -- tooar Sat, 21 Aug 2004 01:19:52 +0100 emelfm2 (0.0.8-1) unstable; urgency=low * new upstream -- tooar Fri, 30 Apr 2004 01:19:52 +0100 emelfm2 (0.0.7-1) unstable; urgency=low * new upstream -- tooar Fri, 19 Mar 2004 01:19:52 +0100 emelfm2 (0.0.6-1) unstable; urgency=low * new upstream -- tooar Fri, 27 Feb 2004 11:19:52 +0100 emelfm2 (0.0.5-1) unstable; urgency=low * new upstream -- tooar Tue, 05 Jan 2004 01:04:47 +0200 emelfm2 (0.0.4-1) unstable; urgency=low * new upstream -- tooar Tue, 05 Dec 2003 01:04:47 +0200 emelfm2 (0.0.3-1) unstable; urgency=low * new upstream -- tooar Tue, 08 Nov 2003 01:04:47 +0200 emelfm2 (0.0.2-1) unstable; urgency=low * new upstream -- tooar Tue, 09 Sep 2003 01:04:47 +0200 emelfm2 (0.0.1-1) unstable; urgency=low * initial release -- tooar Sat, 30 Aug 2003 20:34:01 +0200 emelfm2-0.4.1/debian-unofficial/menu0000600000175000017500000000030410521644703016234 0ustar cairocairo?package(emelfm2): needs=X11 section="Apps/Tools"\ title="emelFM2" command="/usr/X11R6/bin/emelfm2"\ longtitle="2-pane Gtk+2 file manager" \ icon="/usr/share/pixmaps/emelfm2/emelfm2_48.png" emelfm2-0.4.1/debian-unofficial/files0000600000175000017500000000005010722677024016375 0ustar cairocairoemelfm2_0.3.6-1_i386.deb utils optional emelfm2-0.4.1/debian-unofficial/emelfm2.1x0000600000175000017500000001134310521644703017153 0ustar cairocairo.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins .\" .nf disable filling .\" .fi enable filling .\" .br insert line break .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .TH "EMELFM2" "1" "August 2006" "Liviu" "" .SH "NAME" emelFM2 .SH "SYNOPSIS" .B a GTK+2 file manager .SH "DESCRIPTION" emelFM2 is a file manager for UNIX\-like operating systems. It uses a simple and efficient interface pioneered by Norton Commander in the 1980's. The main window is divided into three "panes" or "panels". Two of them show information about the contents of selected filesystem directories. That information is presented as text. The third pane shows the output of commands executed within the program. A built\-in command\-line, toolbar buttons or assigned keys can be used to initiate commands. A flexible filetyping mechanism provides user\-determined actions for desired filetypes. .SH "COMMAND LINE OPTIONS" .TP \fB\-1\fR,\fB\-\-one\fR=\fI DIR\fR At session\-start, show DIR in the first filelist pane. .TP \fB\-2\fR,\fB\-\-two\fR=\fI DIR\fR At session\-start, show DIR in the second filelist pane. .TP \fB\-c\fR,\fB\-\-config\fR=\fI DIR\fR Set the configuration\-data directory to DIR (default: ~/.config/emelfm2). .TP \fB\-e\fR,\fB\-\-encoding\fR=\fI TYPE\fR Set the filesystem character encoding to TYPE. .TP \fB\-f\fR,\fB\-\-fallback\-encoding=\fI TYPE\fR Set the fallback encoding to TYPE (default: ISO\-8859\-1). .TP \fB\-i\fR,\fB\-\-ignore\-problems\fR Ignore the encoding/locale problems (use at your own risk!). .TP \fB\-l\fR,\fB\-\-leave\-alone\fR Do not suppress GTK+2 output on the console. .TP \fB\-n\fR,\fB\-\-no\-detach\fR Do not detach from terminal. Only works when emelFM2 is compiled without debug support. .TP \fB\-r\fR,\fB\-\-run\-at\fR\-start\fR\fR=\fI COMMAND\fR Run COMMAND immediately after session start. .TP \fB\-s\fR,\fB\-\-set\-option OPTION_NAME=string\-value\fR Set one\-line config option. "OPTION_NAME=string\-value" is a string in the format used in the config file. .TP \fB\-t\fR,\fB\-\-trash\fR=\fI DIR\fR Set the trash directory to DIR (default: ~/.local/share/Trash/files). .PP Help options: .TP \fB\-h\fR,\fB\-\-help\fR Display a brief help message. .TP \fB\-u\fR,\fB\-\-usage\fR Display a brief usage message. .TP \fB\-v\fR,\fB\-\-version\fR Display the version and build information. .PP These only work when emelFM2 is compiled with debug support: .TP \fB\-d\fR,\fB\-\-debug\fR=\fI N\fR Set the debug level. N = digit from 1 (lowest) to 5 (highest). Default is 5. .TP \fB\-x\fR,\fB\-\-verbose\fR Display time and location information in debug messages. Only works when emelFM2 is compiled with debug support. .SH "FILES AND DIRECTORIES" .TP .I [PREFIX]/bin Location for the emelFM2 binary. Default PREFIX is /usr/local, which may be modified at compilation time. .TP .I [PREFIX]/lib/emelfm2/plugins Default location for emelFM2 plugin files, named like e2p*.so. .TP .I [PREFIX]/share/doc/emelfm2 Default documentation files' location. .TP .I [PREFIX]/share/pixmaps/emelfm2 Default location of application\-specific icon files. .TP .I ~/.config/emelfm2 Default location for configuration files, named "config" and "cache". .TP .I ~/.local/share/Trash/files Default trash directory. .SH "ENVIRONMENT VARIABLES" These are directly used: .TP .I G_BROKEN_FILENAMES, G_FILENAME_ENCODING, HOSTNAME, LC_ALL, LC_MESSAGES, LANGUAGE or LANG, PATH .PP .SH "SUGGESTIONS AND BUG REPORTS" Please send bug reports, comments or feature requests to the mailing list . You do not need to be a subscriber. .br For known bugs refer to http://emelfm2.net/TODO. .SH "CREDITS" The original emelFM was essentially a compilation of some of the features that Michael Clark found most useful in other file managers. The current maintainer of emelFM2 is tooar. For a complete list of contributors please refer to [PREFIX]/share/doc/emelfm2/CREDITS. .SH "COPYRIGHT" Copyright (C) 2003\-2006, tooar . .br This program is licensed under the terms of the FSF General Public License and comes with ABSOLUTELY NO WARRANTY. .SH "SEE ALSO" emelFM2 http://emelfm2.net .br User Guide http://emelfm2.net/UserGuide .br updated emelFM http://www.havens.de/elm/emelfm.html .br old emelFM http://www.pitt.edu/~macst92/emelFM/ or .br http://emelFM.sourceforge.net/ .PP emelfm2-0.4.1/debian-unofficial/docs0000600000175000017500000000011610521644703016221 0ustar cairocairodocs/NEWS docs/README docs/CONFIGURATION docs/CREDITS docs/USAGE docs/ACTIONS emelfm2-0.4.1/debian-unofficial/dirs0000600000175000017500000000013510521644703016233 0ustar cairocairousr/bin usr/lib/menu usr/lib/emelfm2/plugins usr/share/doc/emelfm2 usr/share/pixmaps/emelfm2 emelfm2-0.4.1/debian-unofficial/control0000600000175000017500000000104210521644703016750 0ustar cairocairoSource: emelfm2 Section: utils Priority: optional Build-Depends: libglib2.0-dev, libgtk2.0-dev, debhelper Maintainer: Florian Zaehringer Standards-Version: 3.5.2 Package: emelfm2 Architecture: any Depends: ${shlibs:Depends} Description: file manager for X/gtk This is a non-official debian-package. emelFM2 is a file manager that implements the popular two-pane design. It features a simple GTK+2 interface, a flexible file typing scheme, and a built-in command line for executing commands without opening an xterm. emelfm2-0.4.1/WARNING0000777000175000017500000000000011015120161015010 2docs/WARNINGustar cairocairoemelfm2-0.4.1/Makefile.config0000600000175000017500000001473010751440767014733 0ustar cairocairo# $Id: Makefile.config 784 2008-02-03 22:36:39Z tpgww $ # See also - file INSTALL has further notes about some of the parameters # in this file ############################ ### installation (where) ### ############################ PREFIX ?= /usr/local #BIN_DIR, PLUGINS_DIR, DOC_DIR, ICON_DIR, LOCALE_DIR, MAN_DIR #are set in Makefile, based on PREFIX ##################### ### documentation ### ##################### # setting this to 1 makes the default docs dir include the current version no. DOCS_VERSION ?= 0 #the name of the default main-user-guide document HELPDOC ?= USAGE #the name of the default configuration help document CONFIGDOC ?= CONFIGURATION #################### ### misc options ### #################### #install desktop environment integration files XDG_INTEGRATION ?= 1 #where to install emelfm2.desktop and emelfm2.applications #for desktop environment integration XDG_DESKTOP_DIR ?= $(PREFIX)/share/applications XDG_APPLICATION_DIR ?= $(PREFIX)/share/application-registry # gettext/internationalisation support # (is automatically turned off if you don't have gettext) I18N ?= 1 # setting this to 1 makes the default file-pane layout "horizontal" PANES_HORIZONTAL ?= 0 # setting this to 1 enables setting window-transparency for e2 # (i.e. independent of window-manager settings) # ignored unless built for gtk >= 2.12, so also need USE_LATEST=1 WITH_TRANSPARENCY ?= 0 # GLib and GTK+ by default assume that filenames are # encoded in utf-8 (or ascii which is compatible with utf-8). # The default value 0 supports on-demand conversion of # path/filename strings not in utf-8/ascii, but if you don't # need that, then setting it to 1 omits the conversions, # resulting in smaller code and faster operation # NOTE if you use names with non-utf-8 / non-ascii characters # (e.g. with german umlauts), you should also have set the # environment variable G_FILENAME_ENCODING and/or # G_BROKEN_FILENAMES. The INSTALL document has more # information on this. FILES_UTF8ONLY ?= 0 # setting this to 1 provides support for assistive-technologies, to facilitate # operation by users with impaired sense(s). Not yet fully implemented WITH_ASSIST ?= 0 # See comments in file INSTALL about the merits of various file-alteration # monitors which may be enabled by the following options. # setting this to 1 provides support for file-change monitoring using kernel # infrastructure if that's available, or normal polling if not available # NOTE only linux is supported at this time # kqueue-usage (on bsd) has been coded but not tested # (volunteer needed to test and finalise) # portevent-usage (on solaris) is a TODO # (volunteer needed to work on that) WITH_KERNELFAM ?= 0 # Enable NO MORE THAN ONE of these monitors. # setting this to 1 provides support (on linux) for file-change monitoring # using inotify if that's available, or normal polling if not available # this setting is ignored for operating systems other than linux ifeq ($(WITH_KERNELFAM), 1) USE_INOTIFY ?= 1 else USE_INOTIFY ?= 0 endif # setting this to 1 provides support (on linux) for file-change monitoring # using dnotify if that's available, or normal polling if not available # this setting is ignored for operating systems other than linux USE_DNOTIFY ?= 0 # setting this to 1 provides support for file-change monitoring # using FAM if that's available, or normal polling if not available USE_FAM ?= 0 # setting this to 1 provides support for file-change monitoring # using gamin if that's available, or normal polling if not available USE_GAMIN ?= 0 # Setting EDITOR_SPELLCHECK to 0/1 dis/enables spell-checking (using GtkSpell) # in the internal editor. # To build this you will need to have installed gtkspell and aspell development # files, and to run it, the speller library and the underlying aspell and its # dictionaries. # NOTE this capability currently relies on some gtkspell internals, and so must # use a patched version of gtkspell. The patch is available from emeFM2 website. EDITOR_SPELLCHECK ?= 0 # Setting EXTRA_BINDINGS to 0/1 dis/enables keybindings for "transient" elements # of the emelFM2 GUI, like dialogs or menus. In practice, to make such bindings # work, some enabling functionality for the elements in question must be coded. # As of now, such code has been added only for view and edit dialogs, for evaluation # and testing purposes EXTRA_BINDINGS ?= 0 # setting this to 1 includes code (not much) that is applicable only # to Gtk versions > 2.6, if the build-system has any such Gtk USE_LATEST ?= 0 # setting this to 1 utilises replacement code for command execution. It's known # to work reasonably for some (most ?) OS's, but needs further evaluation on linux 2.4 # (though if it's still a problem there, it's not fatal, just wasteful of CPU cycles) NEW_COMMAND ?= 0 # setting this to 1 enables a thumbnail viewer plugin. This uses libgimpthumb for # managing thumbnail-file cacheing in accord with freedesktop.org specification. # At least version 2.2 of that library is needed in order for the plugin to load # at runtime. Corresponding development files are needed for building the plugin WITH_THUMBS ?= 0 # setting this to 1 enables a tracker-UI plugin. This uses shell commands to # interrogate tracker data, and so no external library is needed at runtime or # development file at build time (these may change if tracker's API is updated) WITH_TRACKER ?= 0 # setting this to 1 enables an Access-Control-List-management plugin. This uses # libacl - with a reasonably full (sort-of POSIX) API. # Corresponding development files are needed for building the plugin WITH_ACL ?= 0 # setting this to 1 enables HAL and dbus code, for detecting and responding to # added and removed volumes. # Requires HAL, dbus and dbus-glib WITH_HAL ?= 0 # setting this to 1 enables a VFS plugin. This uses gvfs capabilities, and so needs # libgio etc and glib >= 2.14 # NOT YET FUNCTIONAL WITH_VFS ?= 0 # the vfs plugin can, in principle, work with various backend vfs libraries. # Sofar only gvfs is enabled, and for that, a separate gvfs plugin is used. # NOT YET FUNCTIONAL WITH_GVFS ?= 0 # setting DEBUG to 0/1 turns off/on debugging messages and in-file data for debuggers DEBUG ?= 0 ######################### ### compilation (how) ### ######################### CC ?= gcc ifeq ($(DEBUG), 1) #additional CFLAGS (-g -O0) will always be added when in debug mode #CFLAGS ?= -Wall -Wextra -Wno-unused -Winline CFLAGS ?= -Wall -Winline else CFLAGS ?= -O2 -Wall #CFLAGS ?= -O3 -Wall -march=athlon-tbird -mtune=athlon-tbird #CFLAGS ?= -O3 -Wall -march=pentium -mtune=pentium4 endif emelfm2-0.4.1/INSTALL0000777000175000017500000000000011015120161015012 2docs/INSTALLustar cairocairoemelfm2-0.4.1/docs/0000700000175000017500000000000011015120161012721 5ustar cairocairoemelfm2-0.4.1/docs/LGPL0000600000175000017500000001672710643544426013445 0ustar cairocairo GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. emelfm2-0.4.1/docs/emelfm2.10000600000175000017500000001134310573407612014357 0ustar cairocairo.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins .\" .nf disable filling .\" .fi enable filling .\" .br insert line break .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .TH "EMELFM2" "1" "0.3.3" "Liviu" "" .SH "NAME" emelFM2 .SH "SYNOPSIS" .B a GTK+2 file manager .SH "DESCRIPTION" emelFM2 is a file manager for UNIX\-like operating systems. It uses a simple and efficient interface pioneered by Norton Commander in the 1980's. The main window is divided into three "panes" or "panels". Two of them show information about the contents of selected filesystem directories. That information is presented as text. The third pane shows the output of commands executed within the program. A built\-in command\-line, toolbar buttons or assigned keys can be used to initiate commands. A flexible filetyping mechanism provides user\-determined actions for desired filetypes. .SH "COMMAND LINE OPTIONS" .TP \fB\-1\fR,\fB\-\-one\fR=\fI DIR\fR At session\-start, show DIR in the first filelist pane. .TP \fB\-2\fR,\fB\-\-two\fR=\fI DIR\fR At session\-start, show DIR in the second filelist pane. .TP \fB\-c\fR,\fB\-\-config\fR=\fI DIR\fR Set the configuration\-data directory to DIR (default: ~/.config/emelfm2). .TP \fB\-e\fR,\fB\-\-encoding\fR=\fI TYPE\fR Set the filesystem character encoding to TYPE. .TP \fB\-f\fR,\fB\-\-fallback\-encoding=\fI TYPE\fR Set the fallback encoding to TYPE (default: ISO\-8859\-1). .TP \fB\-i\fR,\fB\-\-ignore\-problems\fR Ignore the encoding/locale problems (use at your own risk!). .TP \fB\-l\fR,\fB\-\-log\-all\fR Maximise GTK+2 error logging. .TP \fB\-m\fR,\fB\-\-daemon\fR Detach the emelFM2 session from its controlling terminal. Will not work when emelFM2 is compiled with debug support. .TP \fB\-r\fR,\fB\-\-run\-at\fR\-start\fR\fR=\fI COMMAND\fR Run COMMAND immediately after session start. .TP \fB\-s\fR,\fB\-\-set\-option OPTION_NAME=string\-value\fR Set one\-line config option. "OPTION_NAME=string\-value" is a string in the format used in the config file. .TP \fB\-t\fR,\fB\-\-trash\fR=\fI DIR\fR Set the trash directory to DIR (default: ~/.local/share/Trash/files). .PP Help options: .TP \fB\-h\fR,\fB\-\-help\fR Display a brief help message. .TP \fB\-u\fR,\fB\-\-usage\fR Display a brief usage message. .TP \fB\-v\fR,\fB\-\-version\fR Display the version and build information. .PP These only work when emelFM2 is compiled with debug support: .TP \fB\-d\fR,\fB\-\-debug\fR=\fI N\fR Set the debug level. N = digit from 1 (lowest) to 5 (highest). Default is 5. .TP \fB\-x\fR,\fB\-\-verbose\fR Display time and location information in debug messages. Only works when emelFM2 is compiled with debug support. .SH "FILES AND DIRECTORIES" .TP .I [PREFIX]/bin Location for the emelFM2 binary. Default PREFIX is /usr/local, which may be modified at compilation time. .TP .I [PREFIX]/lib/emelfm2/plugins Default location for emelFM2 plugin files, named like e2p*.so. .TP .I [PREFIX]/share/doc/emelfm2 Default documentation files' location. .TP .I [PREFIX]/share/pixmaps/emelfm2 Default location of application\-specific icon files. .TP .I ~/.config/emelfm2 Default location for configuration files, named "config" and "cache". .TP .I ~/.local/share/Trash/files Default trash directory. .SH "ENVIRONMENT VARIABLES" These are directly used: .TP .I G_BROKEN_FILENAMES, G_FILENAME_ENCODING, HOSTNAME, LC_ALL, LC_MESSAGES, LANGUAGE or LANG, PATH .PP .SH "SUGGESTIONS AND BUG REPORTS" Please send bug reports, comments or feature requests to the mailing list . You do not need to be a subscriber. .br For known bugs refer to http://emelfm2.net/TODO. .SH "CREDITS" The original emelFM was essentially a compilation of some of the features that Michael Clark found most useful in other file managers. The current maintainer of emelFM2 is tooar. For a complete list of contributors please refer to [PREFIX]/share/doc/emelfm2/CREDITS. .SH "COPYRIGHT" Copyright (C) 2003\-2007, tooar . .br This program is licensed under the terms of the FSF General Public License and comes with ABSOLUTELY NO WARRANTY. .SH "SEE ALSO" emelFM2 http://emelfm2.net .br User Guide http://emelfm2.net/UserGuide .br updated emelFM http://www.havens.de/elm/emelfm.html .br old emelFM http://www.pitt.edu/~macst92/emelFM/ or .br http://emelFM.sourceforge.net/ .PP emelfm2-0.4.1/docs/ACTIONS0000600000175000017500000003112310776374107013776 0ustar cairocairo$Id: ACTIONS 849 2008-04-07 10:32:39Z tpgww $ emelFM2 actions are primarily intended to be bound to UI items - a key-binding, a toolbar button and/or a menu item. Any action may also be included in any customised command string. Actions named like file.* apply to active-pane selected items. In the following: 1. the Parameters column has either Y=parameter(s) are expected, O=parameter(s) optional, or N=no parameter 2. parameter(s) are named in their english form, but in general, they would be translated (ENGLISH) NAME PARAMETER(S) DESCRIPTION bookmark.add O add dir to bookmarks . Parameter- pane1 or pane2 = use the dir shown in that pane; top = add to top of list; child = add as child of clicked bookmark; none = TBA children. N create menu of running child processes. Selection from that menu adds that child's pid to the command line (always prepended if is pressed at the time of selection) clear.pending N clear all queued commands and actions command.clear_history N remove all items from a directory/command combobox's dropdown history-list command.clear N clear the command line command.complete O complete command line. Parameter: none OR all = do all types, dirs = list all valid dirnames; files = all valid filenames; mounts = (un)mountable partitions for (u)mount command command.focus N focus the command line command.focus_toggle N toggle focus between command line and active pane command.history N show in the output pane a list of completed commands and file-actions command.insert_selection N insert (at cursor position in command line) a string with names of all selected item(s) command.mkdir N open make-directory dialog command.send N send command string to the last-run child process command.quit N close the running e2 instance configure.application Y open the configuration dialog. Parameter is the name of the page to show when starting configure.bookmarks N open the bookmarks configuration dialog configure.default N revert all configuration options to their respective default values configure.filetypes N open the filetypes configuration dialog configure.plugins N open the plugins configuration dialog dialog.about N open help dialog (which includes a page with "about" info) dialog.view N open (text) file view dialog (usually use file.view instead of this) dirline.focus_toggle Y toggle focus between a directory input line, and the last-focused filepane. Parameter = 1 or 2, for the corresponding dirline file.acl N set or change access control list(s) of selected items (this is supported in ACL plugin) file.copy_acl N recursively copy ACL(s) only of selected items respectively to matching items in the inactive pane (this is supported in ACL plugin) file.copy_as N copy selected item(s) from active pane to inactive, each with new name file.copy_merge N same as file.copy but merges, not replaces, any directories file.copy_with_time N same as file.copy but preserves mtime and atime of copied files (not dirs, links, etc) file.copy N copy selected item(s) from active pane to inactive file.crypt N encrypt or decrypt selected items (this is supported in the crypt plugin) file.delete N delete selected item(s) from active pane file.edit Y open specified text file using the internal editor, or external editor if so configured file.edit_again Y open specified text file using the internal edotor, with initial focus where it was last edited file.find N open file-find dialog, to find named item in current pane file.info N open information dialog for selected item(s) in active pane file.move_as N move selected item(s) from active pane to inactive, each with new name file.move N move selected item(s) from active pane to inactive file.open_in_other N open selected directory in other pane file.open N open item using the 'default' command file.open_with N open item using a specified command file.owners N open owner/group dialog for selected item(s) in active pane file.permissions N open permissions dialog for selected item(s) in active pane file.rename N rename selected item(s) in active pane file.symlink_as N link selected item(s) from active pane to inactive, each with new name file.symlink N link selected item(s) from active pane to inactive file.trash N move selected item(s) from active pane to trash file.trashempty N delete all trashed items file.view_at Y open specified text file using the internal viewer, with initial focus on a heading prescribed like [this] file.view_again Y open specified text file using the internal viewer, with initial focus where it was last viewed file.view N open first-selected text file using the internal or external viewer, according to the relevant config option find.trash N display in the inactive file pane the trash dir for the active pane list.children N list in the output pane all the child processes of the running e2 instance list.history N list in the output pane all prior commands and actions in the session list.pending N list in the output pane all queued commands and actions option.set Y set config option from string Parameter = string like "option-name=option-value" output.adjust_ratio Y adjust height of output pane relative to the main window. Parameter = 0.0 (hidden) to 1.0 (full window) output.clear N clear all content from the output pane output.goto_bottom Y scroll to and display the latest contents of the output pane output.goto_top Y scroll to and display the initial contents of the output pane output.help Y list help text in the output pane. Parameter: TBA output.page_down Y scroll the output pane down by one pane-height output.page_up Y scroll the output pane up by one pane-height output.print Y display string in the output pane. Parameter = the string output.scroll_down Y scroll the output pane down. Parameter = no of lines to scroll output.scroll_up Y scroll the output pane up. Parameter = no of lines to scroll output.expand N toggle output pane between full-window and last position TOGGLE ACTION output.show N toggle output pane between hidden and last position TOGGLE ACTION pane1. N show menu of filters for pane 1 TOGGLE ACTION pane1.focus N make pane1 active if it's not already so pane1.go_back N display in pane 1 the dir that is before the current one in the corresponding history list pane1.go_forward N display in pane 1 the dir that is after the current one in the corresponding history list pane1.go_up N display in pane 1 the parent of the dir currently shown there pane1.mirror N display in pane 1 the dir in pane 2 pane1.open N display in pane 1 the dir chosen from a dialog pane1.expand N toggle size of pane1 relative to pane1+pane2, full or partial TOGGLE ACTION pane1.show_hidden N toggle display of hidden items in pane TOGGLE ACTION pane1.toggle_select_all N toggle selection of all items in pane1 pane2. N show menu of filters for pane 2 TOGGLE ACTION pane2.focus N make pane2 active if it's not already so pane2.go_back N display in pane 2 the dir that is before the current one in the corresponding history list pane2.go_forward N display in pane 2 the dir that is before the current one in the corresponding history list pane2.go_up N display in pane 2 the parent of the dir currently shown there pane2.mirror N display in pane 2 the dir in pane 1 pane2.open N display in pane 2 the dir chosen from a dialog pane2.expand N toggle size of pane2 relative to pane1+pane2, full or partial TOGGLE ACTION pane2.show_hidden N toggle display of hidden items in pane2 TOGGLE ACTION pane2.toggle_select_all N toggle selection of all items in pane2 pane_active. N show menu of bookmarks pane_active. N show menu of filters for the active pane pane_active.focus N give active filelist the focus (mainly for start of chained filelist keybings) pane_active.go_back N display in the active pane the dir that is before the current one in the corresponding history list pane_active.go_forward N display in the active pane the dir that is after the current one in the corresponding history list pane_active.go_up N display in the active pane the dir that is the parent of the current one in that pane pane_active.goto_top N display the top line of the the active file-pane pane_active.goto_bottom N display the bottom line of the the active file-pane pane_active.invert_selection N toggle selection-state of all items in the active pane pane_active.select_names N select items in the active pane by matching names against a specified pattern pane_active.show_menu Y show filepane context menu pane_active.sortaccesssed N sort active pane by item access time pane_active.sortchanged N sort active pane by item change time pane_active.sortgroup N sort active pane by item group pane_active.sortmodified N sort active pane by item modified time pane_active.sortname N sort active pane by item name pane_active.sortpermission N sort active pane by item pemissions pane_active.sortsize N sort active pane by item size pane_active.sortuser N sort active pane by item owner pane_active.switch N toggle the active file pane pane_active.toggle_hidden N toggle whether hidden items are displayed in the active filepane pane_active.toggle_select_all N toggle whether all items in active pane are selected pane_active.toggle_selected N toggle selection-state of currently-focused item in the active pane pane_active.tree N open filesystem-directories-tree dialog for active file pane panes.adjust_ratio Y adjust size of active filepane relative to pane1+pane2. Parameter = 0.0 (hidden) to 1.0 (full size) panes.open N determine which directory line was active, display in the corresponding pane the dir chosen from a dialog panes.refresh N refresh contents of file panes if need be panes.refreshresume N resume periodic checks for whether to update file panes panes.refreshsuspend N cancel periodic checks for whether to update file panes panes.sync N change inactive filepane to be the same as active one panes.toggle_direction N toggle between hoizontal and vertical layout for file panes toggle.fullscreen N toggle main window fullscreen on/off Y run a command (that is not one of the actions). Parameter is the command, with any command-parameters ACTIONS FOR PLUGINS file.clone N copy selected items to active pane, after rename panes.compare N select items in active pane which are matched in inactive pane file.cpbar N copy selected items with a progress bar and stop/pause potential file.cpbar_with_time N copy selected items with original atime, mtime and with a progress bar and stop/pause potential command.detfind N run extended find-item dialog command.track N run tracker-GUI dialog file.du N determine disk usage of selected items file.foreach N run an entered command on each selecte item pane_active.glob N select items using rules similar to filepane filters configure.manage N import/export config data file.mvbar N move selected items with a progress bar and stop/pause potential file.copy_name N copy names, or (with ) paths, of selected items to clipboard file.pack N create an archive including the selected items command.renext N run extended rename dialog pane_active.sort_by_ext N sort display by filename extension file.timeset N change atime, mtime or (if root user) ctime of selected items file.unpack N unpack an archive into a temporary directory file.view_with_plugin N open first selected item with internal viewer (same as internal action) UI-RELATED "PSEUDO ACTIONS" which don't actually do anything bookmark. Y taskbar bookmark items Parameter = TBA command. Y combobox for specifying commands to be executed Parameter = min,max or ,max or min,expand. Min and max are size limits (in pixels). If no min is provided, 100 is used. 'expand' in effect is no maximum file. N list of filetype-specific items in a filepane context menu mountpoints. Y menu of mountable and unmountable devices. Parameter = name of toolbar where the button resides, same as the corresponding page name in the config dialog pane1. Y the bookmarks list, if it is shown in pane1 toolbar Parameter = TBA pane1. N filters menu for items shown in pane 1 pane1. N combobox for specifying the dir shown in pane 1. See command. for parameters description pane2. Y the bookmarks list, if it is shown in pane2 toolbar Parameter = TBA pane2. N filters menu for items shown in pane 2 pane2. N combobox for specifying the dir shown in pane 2. See command. for parameters description plugin. N menu of plugins as may be displayed in a context menu N separator in a toolbar or context menu N submenu in a toolbar or context menu emelfm2-0.4.1/docs/TODO0000600000175000017500000000524010522022064013421 0ustar cairocairo$Id: TODO 3 2006-11-01 04:36:04Z tpgww $ This file is part of emelFM2. Here is a list of things left TODO so nobody will forget them. (see also http://emelfm2.net/TODO, which may sometimes be more recent than this file) == tasks that always apply == * update documentation and homepage/wiki * find solutions for FIXMEs in the code * find & fix memory leaks == probable changes == * make it possible to simply apply current-pane config options to the other pane too, in advanced config mode * wider support for theme-default properties * make the filetypes config easier to use * easier communication from command line to running child applications * complete documentation about action arguments * additional translations * add missing api documentation * code restucturing and clean up == low priority things that can be done when there is time == * new plugins e.g undelete * support non-english-ascii letters (replacing dDfF) in macros * add a mechanism to clear history lists * find a way to have those small lines in the treeview as kde/qt or windows provide them * add function to recursively count all children of a certain model and iter * links for the output pane * add option to remove unsuccessful commands from command line history after another successful one * fix command line recreation overhead/memory leaks ? * add e2_cache_tree for GtkTreeModels * add tooltips to context menus == changes that may not be worth the effort == * explore relevance of (undocumented?) GtkFileSystemModel, GtkFileFolder * file filtering using GtkFileFilter * convert to a single form of 'external' command, instead of sometimes using the pseudo-action * interrogate system mime data for filetype commands * support virtual file system (probably gnome-vfs, if we can't wait for D-VFS) * threaded/asynchronous filelist-refreshing * incremental update of filelists when dir content changes * threaded/asynchronous task performance * show filetype/dir icons in file lists * 2-line panebars when vertical panes are not at full width * command queue * undo, for tasks == known bugs/problems == * some default keybindings might not work, if other things' bindings prevail * auto-refreshing file panes (without FAM/gamin) does not notice file-size changes (consequence of the way the fs works) * auto-refreshing with gamin does not notice most changes involving symlinks (gamin bug) * auto-refreshing with gamin and gnome (at least) and dnofify backend does not notice changes to the trash dir (gamin bug) * sizing of any command-line combobox in a vertical toolbar is illogical (wishlist = gtk support for "drop-across") * hard links are processed as separate files emelfm2-0.4.1/docs/SPEC0000600000175000017500000000407410613541451013421 0ustar cairocairo#%define prefix %{_prefix} #this is to allow easy relocation eg to /usr/local %define prefix /usr %define _bindir %{prefix}/bin %define _libdir %{prefix}/lib Name: emelfm2 Version: 0.1.8 Release: 1 Summary: Gtk+2 file manager with two-panel format License: GPL URL: http://emelfm2.net Group: File tools Requires: gtk+2.0 >= 2.6 file findutils >= 4.2 grep sed Source: %{name}-%{version}.tar.bz2 BuildRoot: %{_tmppath}/%{name}-%{version}-root BuildRequires: gtk+2-devel BuildRequires: gcc >= 3.2 %description emelFM2 is a file manager with the efficient two-pane format, featuring: o Simple interface o Bookmarks and history lists o Flexible filetyping scheme o Multiple actions selectable for each filetype o Filename, size, and date Filters o Built-in command line o Configurable keyboard bindings o Configurable toolbars o Runtime loadable plugins It is the Gtk+2 port of emelFM. %package i18n Summary: Translation files for emelFM2 Group: File tools %description i18n This package contains translation files which may be installed to enable non-english names in emelFM2's user-interface. Supported languages are: japanese, french, german, russian. %prep %setup -q %build #adjust the make parameters to suit - see file INSTALL %make DOCS_VERSION=1 USE_LATEST=1 CFLAGS="-O2 -march=%{_target_cpu}" %install rm -rfd %{buildroot} %make install PREFIX=%{buildroot}%{prefix} %make install_i18n PREFIX=%{buildroot}%{prefix} # remove unnecessary docs rm -rfd %{buildroot}%{_datadir}/doc %find_lang %{name} mkdir -p %{buildroot}%{_bindir} install -m 755 %{name} %{buildroot}%{_bindir} %clean rm -rfd %{buildroot} rm -rfd %{_builddir}/%{name}-%{version} %files %defattr (-,root,root) %{_bindir}/* %{prefix}/share/pixmaps/* %{prefix}/share/applications/* %{prefix}/share/application-registry/* %dir %_libdir/%{name} %dir %_libdir/%{name}/plugins %{_libdir}/%{name}/plugins/e2p*.so %{_mandir}/man1/* %defattr(644,root,root,755) %doc docs/ACTIONS docs/CONFIGURATION docs/CREDITS docs/GPL docs/NEWS docs/README docs/USAGE %files i18n -f %name.lang %defattr(-, root, root) %changelog emelfm2-0.4.1/docs/README0000600000175000017500000000434510522022064013616 0ustar cairocairo$Id: README 3 2006-11-01 04:36:04Z tpgww $ This file is part of emelFM2. DESCRIPTION ~~~~~~~~~ emelFM2 is a file manager that implements the popular two-pane design. It features a simple GTK+2 interface, a flexible filetyping scheme, and a built-in command line for executing commands without opening an xterm. emelFM2 is licensed under the FSF General Public License. WEBSITES ~~~~~~~~ emelFM2 http://www.emelfm2.net updated emelFM1 http://www.havens.de/elm/emelfm.html old emelFM1 http://www.pitt.edu/~macst92/emelFM/ or http://emelFM.sourceforge.net/ MAILING LIST ~~~~~~~~~ There is an emelFM2 mailing list provided by freelists.org. You can subscribe to the emelFM2 mailing list by sending email to emelfm2-request@freelists.org with 'subscribe' in the subject and you can unsubscribe from the list by sending email to the same address with 'unsubscribe' in the subject. web interface: http://www.freelists.org/cgi-bin/list.fcgi?list_id=emelfm2 list archive: http://www.freelists.org/archives/emelfm2/ email address emelfm2@freelists.org HELP ~~~~ Your help is wanted! It is always nice to get bug reports, comments, feature requests or patches. There is an emelFM2 wiki where some contents needs to be added. If you have any programming skills, you might also want to take a look at the TODO list. Please send bug reports, comments or feature requests to the mailing list (see above). You do not need to be a subscriber to the list. INTERNATIONALISATION ~~~~~~~~~~~~~~~~ i18n support is almost complete. Within the codebase, virtually all strings to be translated have the necessary _() or N_() tags. There are a few for which a final decision on whether to translate has not yet been made. So if you don't mind possibly making some relatively minor changes later, feel free to do a translation. Start by asking on the mailing list if anybody else is already doing the same. The make targets are i18n and [un]install_i18n. The help documents USAGE and CONFIGURATION might well be translated at some future time, but they are not yet mature. Probably not worth translating yet. COMPILATION, INSTALLATION, UNINSTALLATION ~~~~~~~~~~~~~~~~~~~~~~~~~~~ See the INSTALL file. This version of emelFM2 requires Gtk+2.6 or later. emelfm2-0.4.1/docs/CREDITS0000600000175000017500000000353210522022064013753 0ustar cairocairo$Id: CREDITS 3 2006-11-01 04:36:04Z tpgww $ This document is part of emelFM2. Thanks go to the following, for their contributions to the development of emelFM2: * Liviu Andronic * TomPh * Tarot Osuji develops leafpad, from which we imported character-encoding checks * Ronny Steiner did the german translation * Hirosi Utumi did the japanese translation * Robert Ragosta * Reboant * David Jensen * David S. Yates * Denis Prost not least for his french translation * Tim-Philipp Müller * Arnout Lok * Florian Zähringer * Marcus von Appen * Michael Clark * Aurelien Gateau * Paul Evans * Vaclav Dvorak * Konstantin Volckov * freelists.org for their mailing list service * freshmeat.net for their announce service * imendio (www.imendio.com) for their list cell renderer * The people making the packages and ports for various operating systems and distributions. * All users that have sent suggestions, bug reports or comments and everybody else i've forgotten. If you think you should be listed here, mail the maintainer (tooar) about it. The original emelFM was essentially a compilation of some of the features that Michael Clark found most useful in other file managers. An abbreviated "thank you" list: * Henrik Harmsen for FileRunner * Pixel for sfm * Emil Brink for gentoo * GNU for Midnight Commander For an annotated history of file managers like emelFM2, you can't do better than www.softpanorama.org/OFM/index.shtml emelfm2-0.4.1/docs/USAGE0000600000175000017500000016217310646610433013543 0ustar cairocairo emelFM2 help +---------------+ Note for translators: headings in this document that are surrounded by [] are search targets for context-specific help. The name inside the [] needs to match the translated name of the corresponding help-dialog page. Other headings here are simply capitalised. INTRODUCTION This document records advice, tips, answers to questions etc that might assist emelFM2 (e2) users. The content is work-in-progress. If you identify anything to usefully add here, mail tooar about it. [usage] STARTUP Various command-line switches can be used, to determine aspects of the way an e2 session operates: Program options: -1,--one=DIR set 1st pane's start directory to DIR -2,--two=DIR set 2nd pane's start directory to DIR -c,--config=DIR set config directory to DIR (default: ~/.config/emelfm2) -e,--encoding set filesystem coding (include "ascii" or "ASCII" in this, to omit file path/name conversions) -f,--fallback-encoding set fallback encoding (default: ISO-8859-1) -i,--ignore-problems ignore encoding/locale problems (at your own risk!) -l,--log-all maximise scope of error logging (relevant only when built without debugging-support) If the program has not been built with debugging-support enabled (compiled without make-option DEBUG=1): (note that this option must be used if you wish emelFM2 to nicely respond to shutdown, save-yourself etc requests by your desktop session-manager) -m,--daemon detach emelfm2 session from its controlling terminal, and run as a daemon -r,--run-at-start command to run immediatly after start -s,--set-option set configuration parameter (one-line argument in config file format) -t,--trash=DIR set trash directory to DIR (default: ~/.local/share/Trash/files) Help options: -h,--help show this help message -u,--usage display brief usage messsage -v,--version display version and build info If the program has been built with debugging-support enabled (compiled with make-option DEBUG=1): -d,--debug=[1-5] set debug messages level from 1 (lowest) to 5 (highest) -x,--verbose display time/location info in debug messages USER INTERFACE The core elements of the UI are: lists showing information about the items in two directories (or one, if the pair show the same directory); a place where e2 messages, output from commands etc is shown; and four toolbars. The directory lists and message area generally referred to here as "panes", and in particular, "file panes" or "output pane". The file panes can be tiled side-by-side, or top-to-bottom. "Pane 1" is on the left, if the file panes are tiled side-by-side, or on the top, if the panes are tiled top-to-bottom. "Pane 2" is on the right, if the file panes are tiled side-by-side, or on the bottom, if the panes are tiled top-to-bottom. At any time, one of the two file panes will be 'active' (have focus). The active pane is normally indicated by changed color of the column headers in the pane. However re-coloring does not work with some Gtk themes. If that applies to you, change the relevant setting on the 'panes' page of the configuration dialog (see the CONFIGURATION section, below), to enable bold column titles, as an alternative indicator. The file panes can re-sized, or hidden, by dragging the separator or clicking a relevant toolbar button. The output pane can be resized, up to the full window size, or hidden, likewise by dragging or clicking. You can change the order of columns displayed in either file pane. Press the mouse left-button while the mouse cursor is on the header of the column you want to move. Then drag it to where you want. Such change will be remembered between sessions. You can change the width of any column by dragging the relevant column-separator. (There is a minimum size, related to the size of the column label.) You can hide any column by un-checking the corresponding column on the 'columns' page of the configuration dialog. Such changes will be remembered between sessions. You can sort a file list according to the data in any column, by left-clicking on the column's header. Repeated clicks toggle the sort-order. The actual order that results depends on the user's locale settings. The result might be different from the 'traditional' way. You can display only items which match specified name(s), date and/or permissions criteria, by setting filter(s) for the pane in question. Do that via dialogs initiated from a toolbar filters button. Directories can be included in the filtering. One or more filters can be used simultaneously. To filter items by name, you provide a string with one or more patterns, each with wildcard(s) "*" and/or "?", in general. (NOTE: there can be no '[...]' character ranges in a pattern, and "*" and "?" cannot be escaped to include them literally in a pattern). If more than one pattern is used, they must be separated by a ",". Any pattern(s) can have "!" prepended, which will cause a matched item to be excluded from the display. To include items whose name begins with "!", prepend "\" to that pattern. As a convenience, the effect of every pattern in the string can be toggled, by checking the "inverted" box. Right-clicking on a file pane or output pane will pop up a menu of things you may wish to do, there. For the file panes, a sub-menu can be opened by pressing a or button before right-clicking. The toolbars are referred to as "pane1 bar", "pane2 bar", "task bar" and "command bar". The pane1 bar is by default at the top of pane 1. The pane2 bar is by default shown at the top of pane 2. The task bar is by default shown between the file panes. The command bar is by default shown near the bottom of the window. Pane1 bar and pane2 bar typically have the same items in them, in mirrored order (more or less). Commands initiated by the items in those bars apply to the respective pane. Task bar items mostly relate to the active pane. (By default it includes a 'refresh' button which updates both panes.) Command bar items relate to the output pane, or other miscellaneous commands. Both pane1 bar and pane2 bar typically have a combo box where the user can enter or select a directory to be shown in the corresponding pane (referred to a a "directory line"). In addition to normal choice, clicking the middle mouse button in a directory line, or pressing Tab while a directory line is 'focused', will open a directory-selection dialog. A "navigation" window for the active pane can be displayed, and used to select the directory to show in the corresponding file pane. By default, F9 will open this window. Activating (by mouse double-click or keypress) any of the listed directories will cause that directory to be opened. Directories which cannot be accessed are shown in the 'negative' text color (typically, red). Note that the content of such windows is not refreshed dynamically. A context-menu includes capability to manually refresh, among other things. By default, toolbar buttons will show tooltips, for those of us less familiar with the icons. You can change the button style to include labels, if you wish. Right-click on a toolbar to see its context menu, which includes settings which can be changed. Or open the configuration dialog, and on the relevant bar's 'options' page, change the 'button-style' option. The output pane is more than a simple terminal window, it is also intended to be a tool. You may have as many different output text buffers (aka tabs) as desired. They can be added or removed using the output pane context menu. If more than one tab is in use, numerical tab-titles are shown, for selecting the tab of interest at any time. Selected text in a tab can be saved, using the relevant context-menu item. The text can be edited, again using the relevant menu item. Not that editing per se is so important, but in particular the editor has the ability to find text, and save the whole buffer. See comments below on EDITING. Text selected in a tab can be "activated" by a double-click or a -click, if that text names an item that belongs to a recognised filetype. The named item will be opened just as if it had been in a filelist. Any filepath in the selection is handled, or if there is no filepath, the directory displayed in the active file pane will be assumed. If selected text describes a recognised filetype, and the output pane context-menu is opened by a right-click on the selection, then menu will include a sub-menu of operations for that filetype, the same menu as for a selected item of that type in a file-list context-menu. A -click, or right-click to open a context-menu, will also operate on non-selected text in the output pane, as described above. As there is no selection, space characters define the text that is used. FINDING ITEMS Typing, while a filepane is focussed, will select the first item whose name begins with the typed characters (not case sensitive). That item might be before or after the current position. A small window pops open near the bottom of the pane, showing the characters typed sofar. By default the keys f & F3, and a button on the command bar, will allow you to find an item in the active pane by entering into a dialog any part of the item's name. Wildcards may be entered. When scanning, this action loops round from either end of the filelist to the other end. A find plugin allows heavy-duty searching, with that you can find item(s) anywhere, and by many attributes. ENTRY COMPLETION By default, the Tab key is bound to an action (see KEY BINDINGS section, below) which "completes" entries in directory lines or the command line, as follows. When entering a path into a directory line, pressing the Tab key will complete that entry, if there is only one valid completion. If there is more than one such completion, they will be listed in the output pane. (Note also that pressing Tab will open a directory-selection dialog.) In general, when entering text into the command line, pressing the Tab key will complete the "word" that is being entered. If the entered string starts with "./", the word will be completed using the matching item-name from the active pane. If the entered string does not start with "./", the word will be completed using the matching item-name from all directories specified in the $PATH environment variable. If there is more than 1 item which could validly complete the word, those items will be listed in the output pane. The exception to the previous paragraph is when the entered command is "mount" or "umount". A "mount" command will be completed with a valid mount point, a "umount" command will be completed with a valid unmountable partition. Again, if there is more than one possible completion, they will be listed in the output pane. Note that this form of completion is permission-depenant, so in many instances, there will be nothing valis to show. KEY BINDINGS Various keys are assigned specific tasks within e2. To get a listing of the current e2 key bindings, enter the command 'keys' on the command line. You can also type 'keys panes', 'keys command line' or 'keys directory lines' to see the subset of bindings relevant to those places. (Note 'keys' in this context is a default alias, and should be translated, and may be changed by the user.) Any key can be assigned to more than one thing, by including the key in a binding more than once. Pressing any 'unbound' key while the focus is on a file pane opens a small window in which you can continue typing a name, and an item whose name matches what you type will be selected. Note that that window will intercept any "Enter" keypress (and the window will then close) so that if you wish to 'activate' the matched item by pressing Enter, you must wait (about 2 seconds) until the window closes, or otherwise, press Enter twice. The "comboboxes" (entry fields with drop-down history list) used throughout the application have hard-coded key bindings which clears the contents of the entry from the cursor position to the end, which clears the current entry and any matching history entry(ies), and which clears the entire history. SELECTION In general, things may be selected in accordance with gtk's normal method for doing so. For example, selecting items in a file-pane is done by combinations of left-burron-clicks, ctrl-key+left-clicks (which toggle selection-state of the clicked item) and shift-key+left-click (which select the range from previously-selected item to the clicked item). As a special case, e2 (except 0.1.0 to 0.1.2) also supports selection of file-pane items by dragging. The protocol for this is: press left mouse button when the cursor is on an item, then press a control key, then drag the mouse cursor over item(s) to be selected. (A bit complex, so as to not interfere with gtk's normal selection and DnD processes.) DRAG & DROP Drag and drop can be performed in the standard gtk fashion, i.e. by selecting the item(s) you want to process (see SELECTION above), then dragging to a location in either of the file lists, or to any other compatible application. When dragging by left-button, the default operation is to copy the selected item(s) to the drop-target but the action will be different if 'modifer' key(s) are pressed at the time of the drop. As usual, if the Shift key is presssed while dropping, the selection will be moved; or if both Shift and Control are pressed, the selection will be linked; or if the Alt key is pressed, a menu will prompt you for the operation you want to perform (the menu allows copy/move/link/cancel). To prevent gtk from treating the process as something other than a drag, the modifier key(s) need to be pressed after the drag has started. You may instead perform a drag with the middle-button, which behaves the same as left-plus-Alt i.e. always pops up that action menu. Note that there is an option to make middle-button clicks open the parent directory of the one where the click occurred, and it that option is in force, it may interfere with dragging by middle-button. If you drop ONto a directory (other than one that's part of the selection being dropped), the selected item(s) will be copied/moved/linked INto that directory. This means you can copy/move/link any item into a subdirectory without opening that directory in the other pane. If you don't drop onto a directory, the item(s) will be copied/moved/linked to the directory whose contents are displayed in that pane (if it's not the same as the source directory, of course) It is also possible to drag and drop between different instances of emelFM2 and between emelFM2 and some other gtk-based applications like nautilus, Gnome Midnight Commander and GQView. In some cases it might work in only one direction. TRASH When e2 puts something into trash, it will first try to use a folder named '.Trash' in the directory the item(s) come from i.e. the one shown in the active pane. If such a folder doesn't exist, fallback choices are: '.Trash' in the user's home directory, then finally, '.emelfm2/Trash' in the user's home directory. The last of these will be created if it didn't exist already. There is no over-write checking for trashed items. Any item with the same name, already in the trash, will simply be erased. OPERATIONS ON FILES AND DIRECTORIES Operations (copy, move etc) on item(s) in the active file pane are performed on the selected items, as would be expected. If a selected item is a directory, the operation applies to the directory as a whole (with its contents), NOT just to the contents of the directory. This means that if, say, a directory is moved, and in so doing over-writes another directory of the same name (usually after the user's confirmation), then the destination directory and all its contents will be replaced by the source directory and all its contents. The effect of this is that all of the contents of the destination directory are lost. Just like all the contents of a file are lost if it is over-written. If you want to operate only on the contents of a directory, you must open the directory and select the contents explicitly. Most such operations are added to a queue, and performed when they reach the head of that queue. This allows such operations to be initiated and performed without blocking the user-interface, when the operation takes some time to complete. Some operations - info, view, view again, edit, edit again, open, open with - are performed independently of the queue. You can directly rename any allowed item by clicking on its name in a filelist, when the item's row is not selected. In this case, the rename action is not queued. Actions and keybindings are provided to respectively list the finished, active, and waiting members of that queue. BOOKMARKS As you would expect, these are a mechanism for opening a particular directory with minimal effort. By default, the pane1 bar and pane2 bar, and the file pane context menu, include a bookmarks sub-menu. Actually, they are a bit more complex than a standard sub-menu - each includes its own context menu (yes, we know, peculiar UI design ..) with items for adding the currently-displayed to, or removing it from, the recorded bookmarks. This is a quick alternative to opening the configuration dialog to re-arrange bookmarks. Though accessible by these several menus, there is only one bookmarks list, which applies to both panes. TEXT FILE VIEWING A simple text-file viewer is included in emelFM2. By default, the F3 key is bound to open that. Alernatively, a specified external viewer may be used, if the relevant config option is set. The internal text file viewer handles several different protocols for line-ends (UNIX, DOS etc), and tries to interpret and convert various character-encodings. Even so, encoding may get the better of it, in which case, the file can't be viewed, or at least, it may contain un-recognizable content. An external encoding-converter can be used instead, if the relevant config option is set, and a converter command is provided there. Such command must convert text to UTF-8 and send that converted text to stdout, and the text will be read by emelFM2 directly into memory. If the converter-command requires that the file encoding be specified, then obviously you will need to discover that before opening the file. Some european candidates are: belarussian: CP1251 IBM866 ISO-8859-5 KOI8-UNI maccyr IBM855 bulgarian: CP1251 ISO-8859-5 IBM855 maccyr ECMA-113 czech: ISO-8859-2 CP1250 IBM852 KEYBCS2 macce KOI-8_CS_2 CORK estonian: ISO-8859-4 CP1257 IBM775 ISO-8859-13 macce baltic croatian: CP1250 ISO-8859-2 IBM852 macce CORK hungarian: ISO-8859-2 CP1250 IBM852 macce CORK lithuanian: CP1257 ISO-8859-4 IBM775 ISO-8859-13 macce baltic latvian: CP1257 ISO-8859-4 IBM775 ISO-8859-13 macce baltic polish: ISO-8859-2 CP1250 IBM852 macce ISO-8859-13 ISO-8859-16 baltic CORK russian: KOI8-R CP1251 ISO-8859-5 IBM866 maccyr slovak: CP1250 ISO-8859-2 IBM852 KEYBCS2 macce KOI-8_CS_2 CORK slovene: ISO-8859-2 CP1250 IBM852 macce CORK ukrainian: CP1251 IBM855 ISO-8859-5 CP1125 KOI8-U maccyr When the 'find' function is initiated, a bar of search-related options will be shown near the bottom of the window (and it can be dragged away from there). Of course the bar includes the a place to enter the text sought. That works incrementally, trying to match whatever is entered sofar. There are also options for searching backwards instead of forwards, for ignoring the case of the text sought, and for looping around from either end to the other end, if the search proceeds that far. Finding can be initiated from the keyboard or a mouse button. The 'Enter' key has been given special privileges: by itself causes the next search, with it finds the first or (if searching backwards) the last match, and with changes the search direction temporarily. Other than that, keys the same as the default button mnemonics may be used, with or . As a special case, g or g will perform a 'find'. The viewer supports the key bindings of a gtk textview widget, other than the ones relating to editing: Right Move cursor one character towards end of file Left Move cursor one character towards start of file Right Move cursor one word towards end of file Left Move cursor one word towards start of file Up Move cursor up one line Down Move cursor down one line Up Move cursor one paragraph towards start of file Down Move cursor one paragrpah towards end of file Home Move cursor to start of line End Move cursor to end of line Home Move cursor to start of file End Move cursor to end of line Page_Up Move display up one "page" Page_Down Move display down one "page" Page_Up Move display left one "page" Page_Down Move display rigth one "page" BackSpace Delete character before cursor BackSpace Delete word before cursor a Select all / Select all \ Unselect all c Copy selected text to clipboard Insert Copy selected text to clipboard Tab Move focus forward Tab Move focus backard There is a minimal context menu for changing default configuration. When done with searching, the bar can be hidden again. Entered search parameters are remembered for the duration of the current session, but not between sessions. An action is bound to the key F3 to view a file again (re-view), starting at the last-viewed spot in that file. This appies only to the internal viewer. TEXT FILE EDITING A "tiny" text-editor is available. By default, the F4 key is bound to open that. Alernatively, a specified external editor may be used, if the relevant config option is set. The internal editor is quite basic, intended for those little jobs that are hardly worth loading another application. Functions are: find, replace, cut/copy/paste, undo, save, save as, save selection. Selected text can be dragged to another spot. No printing. Most of these functions can be initiated from the keyboard, and the rest of them from the context menu and/or by button-click. Keyboard bindings set by emelFM2 are the same as the respective mnemonics for buttons and/or context-menu items. As a special case, z will perform an 'undo'. The editor also supports the key bindings of a gtk textview widget: Those listed above under TEXT FILE VIEWING Delete Delete character at the cursor position BackSpace Delete character before the cursor position BackSpace Same as Backspace, to help with mis-typing Delete Delete characters from cursor position to end of word BackSpace Delete characters from before cursor position to start of word x Cut selected text to clipboard v Paste from clipboard to cursor position Delete Cut selected text to clipboard Insert Paste from clipboard to cursor position Insert Toggle insert-overwrite mode See comments above about searching. Replacement choices are made via a similar interface. When active, the bar is stacked below the search options bar. See comments above about character encoding. The same applies to the editor. An action is bound to the key F4 to re-edit a file, starting at the last-viewed spot in that file. This appies only to the internal editor. PLUGINS These are effectively chunks of code that provide optional, additional capability for e2. Any of them may be cofigured to load (i.e. be ready to run) or not, depending on the user's expected need for it. (With a big, fast computer, just load them all and get on with life ...) Descriptions of the current plugins are provided in the respecitive tooltips, which can be displayed in the plugins context-sub-menu, or the plugins page of the configuration dialog. The view plugin always runs the internal viewer for text files, and so it is useful only if your default file viewer is set to be some external viewer. Any plugin might only be needed for a key binding, or a toolbar button action, for example. So a loaded plugin may be configured to appear in the plugins context menu, or not. Plugin configuration settings are preserved until changed by the user. If all goes according to plan, more plugins are coming ! Now, some specific notes: [access control list plugin] ACL's enable application of a superset of most of the "normal" permissions, to an item in a filesystem. Fine-tuning of *NIX read (r), write (w) and/or traverse|execute (x) permission is possible for particular users and/or particular groups. As such, ACL's are mainly relevant in contexts where there are various users, such as corporate systems. Not all *NIX operating systems and file systems support ACL's. If support is present, not all items (or none at all) in the filesystem need actually have an ACL. Any item in a supporting filesystem may have an "access-ACL" that determines the effective r, w, and/or x permission(s) of the item. On a system where _POSIX_ACL_EXTENDED is defined, any directory may also or instead have a "default-ACL" that governs the initial access-ACL for items created within that directory. Two standard formats are used for human-readable ACL display. This is an example of one ACL in the long text form: user::rw- user:lisa:rw- #effective:r-- group::r-- group:444:rw- #effective:r-- mask::r-- other::r-- These are two examples of the short text form: u::rw-,u:lisa:rw-,g::r--,g:444:rw-,m::r--,o::r-- g:444:rw,u:lisa:rw,u::wr,g::r,o::r,m::r An ACL consists of a series of ACL-entries. In the above examples, each line (in the long-form) and each comma-separated block (in the short-form) represents an ACL-entry. An ACL-entry contains * an entry tag type, largely represented by the first part of the entry (user, g, mask, o etc) (see below for a description of these types), * for two types of entry tag, a "qualifier", represented by the second part of the entry (a UID or GID - lisa, 444), and * a set of permissions represented by the third part of the entry (r w x). There are 6 types of enties, as described below. If an ACL exists, 3 of the 6 will always be present - these correspond to the usual user/group/other permissions which apply generally. The other 3 types provide the extra flexibility which justifies using an ACL. There are rules about how these may be used, of course. The ACL_USER_OBJ entry denotes access rights for the item's owner. A valid ACL contains exactly one such entry. Typically, the "normal" rwx permissions defined for the item's owner would be used for the permissions of the ACL_USER_OBJ entry. The ACL_GROUP_OBJ entry denotes default access rights for members of the item's group. A valid ACL contains exactly one such entry. Typically, the "normal" rwx permissions defined for the item's group would be used for the permissions of the ACL_GROUP_OBJ entry. If the ACL has no ACL_MASK entry, then the permissions defined for the item's group correspond to the permissions of the ACL_GROUP_OBJ entry. If the ACL does have an ACL_MASK entry, then the permissions defined for the item's group correspond to the permissions of the [ACL_GROUP_OBJ entry masked by (and'ed with) that?] ACL_MASK entry. The ACL_OTHER entry denotes access rights for processes that do not match any other entry in the ACL. The "normal" "other" rwx permissions defined for the item would typically be used for the permissions of the ACL_OTHER entry. A valid ACL contains exactly one such entry. ACL_USER entries denote the maximum access rights for the user identified by the entry's qualifier. This type of entry may appear zero or more times in an ACL. This type of entry has a qualifier denoting the identifier of a user (UID as a name or number). The actual permissions defined for the user correspond to the permissions of the ACL_USER entry masked by (and'ed with) the ACL_MASK entry. ACL_GROUP entries denote access the maximum rights for groups identified by the entry's qualifier. This type of entry may appear zero or more times in an ACL. This type of entry has a qualifier denoting the identifier of a group (GID as a name or number). The actual permissions defined for the group correspond to the permissions of the ACL_GROUP entry masked by (and'ed with) the ACL_MASK entry. An ACL_MASK entry denotes the maximum access rights that can be granted by entries of type ACL_USER, ACL_GROUP_OBJ, or ACL_GROUP. An ACL that contains entry(ies) of ACL_USER or ACL_GROUP tag types must contain exactly one entry of the ACL_MASK tag type. If an ACL contains no entries of ACL_USER or ACL_GROUP tag types, then an ACL_MASK entry is optional. A qualifier denotes the identifier of a user or a group (UID or GID as a name or number), for entries with tag types of ACL_USER or ACL_GROUP, respectively. All user-ID qualifiers must be unique among all entries of ACL_USER tag type, and all group-ID qualifiers must be unique among all entries of ACL_GROUP tag type. Entries with tag types other than ACL_USER or ACL_GROUP have no defined qualifiers, and in human-readable form, their respective qualifiers are empty or blank. You might get some further useful insight from the likes of: www.vanemery.com/Linux/ACL/linux-acl.html rofi.roger-ferrer.org/eiciel/doc Whew !!! First, a caveat. Not all operating systems (or more particularly their libraries used to manage ACL's) support the same level of functionality. This plugin assumes reasonably-full functionality, more-or-less the full capability of the aborted POSIX standard for ACL's. Linux meets this requirement, others will just have to experiment. There is a lot of ways that changes can be made using this plugin. You can add ACL(s) to any item that has none, and change any existing ACL in many ways, but (because the file system doesn't allow it) you can't entirely remove an existing ACL from an item. You can't add and remove things at the same time - you'd need to run the process twice to achieve that effect. ACL entries for the current item are represented on separate lines in the dialog. Entries can be added or removed using the buttons at the bottom of the dialog, and can be edited in place. What is the whole-flag for? In some cases (ACL_USER, ACL_GROUP, ACL_MASK) you can add that entire entry to, or remove that entire entry from, the ACL, or (if the entry exists already for an item being processed) you can just add to or remove from that entry's permissions. So we need to distinguish what is intended. If the entry's WHOLE setting is in effect, then as you'd expect, the specified change(s) for that entry apply to it as a whole, not just to its permissions. For other types of entries, the whole-flag is ignored, when setting or adding at least. A global whole-flag (below the displayed ACL), if set, has the same effect as setting each relevant whole-flag for all the entries in the ACL (at implementation-time, and whether or not the individual entries' whole-flags are set). Among other things, this arrangement means you will want to set the whole-flag for entries being added to an ACL. A common error when first adding ACL_USER or ACL_GROUP entry(ies) - a ACL_MASK entry must also be added. If the item currently being processed is a directory, you may choose to apply changes recursively. In that case, you may choose to apply changes to directories or to non-directories or to everything. Any changes to a directory (whether or not recursion is in effect) may be to its access-ACL and/or to its default-ACL, or to neither of those ACL's, depending on what has been selected. Remember - to apply changes to directories, you need to activate _all_ the relevant options. This is how the change-process works in the plugin: Each entry in the proposed new ACL is processed in turn. A WHOLE setting is deternined by whether the global whole-flag is set, or othewise, whether a whole-flag is set for the specific entry. After all WHOLE settings are known, the ACL (if any) of the item being updated is interrogated. If the chosen action is SET: * if a WHOLE setting is in effect: - if a matching existing entry is found, replace its permissions - if no matching existing entry is found, and if valid to do so, add this entry and its permissions - if no matching existing entry found, and not valid to add it, continue * if a WHOLE setting is NOT in effect: - if a matching existing entryis found, replace its permissions - if no matching existing entry is found, continue If the chosen action is ADD: * if a WHOLE setting is in effect: - if matching existing entry is found, add specified permissions to it - if no matching existing entry is found, and if valid to do so, add this entry and its permissions - if no matching existing entry is found, and not valid to add it, continue * if a WHOLE setting is NOT in effect: - if matching existing entry is found, add permissions to it - if no matching existing entry is found, continue If the chosen action is REMOVE: * if a WHOLE setting is in effect: - if matching existing entry is found, and if valid to do so, remove it - if matching existing entry is found, and if not valid to remove it entirely, remove specified permissions - if no matching existing entry is found, continue * if a WHOLE setting is NOT in effect: - if matching existing entry is found, remove specified permissions from it - if no matching existing entry is found, continue [clone plugin] Copies selected item(s), each with new name as entered by the user, to the current directory. [copy plugin] This offers the possibiitiy of pausing the copy process. Any such pause begins at the completion of the item currently being copied. If that's a directory, all its decendants will be competed before the pause. [crypt plugin] Encrypt or decrypts selected files. Encryption is performed using an algorithm based on ARC4 (the same algorithm as used by tinycrypt). Recursion into directories is possible, if the corresponding option is selected. The files may optionally be compressed too, as part of the encryption process (compression of a small file might make it a bit larger). For compression, the plugin uses external libraries - first it tries to find an LZO library (which has less compression but is fastest) or if that's not available, then it tries to find and use zlib, and lastly, it will try for bzip2 (most compression, but slowest). When decryptiong, decompression will be performed automatically, provided the corresponding compression library is still available. The conversion process requires that the file being processed fits entirely into available memory, and [de]compression roughly doubles that memory-space requirement. So this plugin is not suitable for large files. The contents of password entry(ies) may be rendered visible (by pressing p in an entry) or re-hidden (by h) or (for gtk >= 2.10) partially and temporarily visible (by t). Several file naming options are provided, anything more complex will need to be done in the rename plugin afterwards. Existing properties of an encrypted file can optionally be stored in the file, and then optionally re-applied after de-cryption. Each processed file can be retained as is after processing, if so desired. This is probably wise, until you are comfortable with the way the plugin is operating. Any file not kept is thoroughly wiped, not just deleted. [disk usage plugin] Calculates the 'apparent' disk usage of selected item(s). 'Apparent' in the sense that any 'holes' in the file data which are artefacts of the filesystem are not counted in the size, even though they're not avaiable for other purposes. [directory compare plugin] Selects items which match items in the other pane. Where relevant, md5 sums are used for the comparison. After selection, items can of course be moved, copied etc, or their names copied, using that plugin. As always, selections can be inverted by pressing Ctrl-i, before processing. [find plugin] The choices made for the various buttons and text-entry strings shown in this dialog are remembered for at least the duration of the current emelFM2 session. If the plugin is still loaded at the end of a session, then the choices will be saved, and available to be re-loaded next time. History lists for text-entry strings can be cleared by pressing Delete when the entry in question is focused. [find by name] Here you can enter the name of the file you want to match. The e2 macros %f or %F can be used instead of explicit name(s). If the "is" option is selected, the find will match any file of that name, which may include wildcard characters: * will match any or no characters. For example, the pattern "*.txt" will match files named foo.txt and .txt, but not foo.text. ? will match any one character. For example, "wibble.?" will match files named wibble.c and wibble.s, but not wibble. or foo.c. The "is like" option will match any file whose name contains the string you enter. For example, the string ".txt" will match the files foo.txt and bar.txt, but not the file baz.text. Again, wildcard characters can be used. If the "regular expression" button is selected, all of the regex capabilities of the shell "find" command may be used. In this case, don't search for %f or %F if more than one item is selected. [find by content] You may enter a string of characters to search for in files to be matched. For example, to match all files containing "linux" you could either select the "this" option and enter "linux", or select the "like this" option and enter the pattern "*linux*". Regular expression searching is conducted by the shell \"grep\" command, for which there is extensive documentation elsewhere. [find by mime] You may enter a string of characters which are all or part of the string representing the mimetype of files to be matched. For example, "pdf" or "application/pdf" would match files in Adobe's PDF format. The string is case-sensitive. [find by mtime] These options allow you to find files according to their modification time. That is the last time that the file was saved to disk storage. You can choose whether you want to match files with times earlier than, equal to, and/or later than the time you have entered. [find by atime] These options allow you to find files according to their access time. That is the last time that the file was read or executed. You can choose whether you want to match files with times earlier than, equal to, and/or later than the time you have entered." [find by ctime] These options allow you to find files according to their inode time. That time is updated whenever a linux file is created, when its atime or mtime is changed, when its mode is changed, and other times too. You can choose whether you want to match files with times earlier than, equal to, and/or later than the time you have entered. [find by size] You may enter the size (in bytes, kilobytes or megabytes) of files to be matched. For kb or MB, the size may be like "1.23". You can choose whether you want to match files sized smaller than, equal to, and/or bigger than the size you have entered. [find by permission] These options allow you to find files according to their permissions (a.k.a "mode"). You can match against multiple permissions. The ones selected need not be the only ones that apply to matched files. [find by owner] These options allow matching files according to their owner and/or group. You can choose to match against the name/group of the logged-in user and/or another specified user or group, or to ignore the user/group. You can also match files without a known user or group by selecting the "Match unknown users" or "Match unknown groups" toggle buttons. [find by type] These options allow you to find files according to their type. You can match against multiple types. [foreach plugin] Executes an entered command on each selected item separately. NOTE the action-name for this plugin is (in english) file.foreach. If this action is run from the command line, it needs to have a "!" prepended e.g. !file.foreach echo %f. (This is to prevent any %f or %p macro in the command from being expanded by the command-interpreter. Such expansion needs to be performed by the plugin, instead.) [glob plugin] Selects items whose name matches specified pattern(s). The selection criteria are the same as filelist filtering criteria. [move plugin] This offers the possibiitiy of pausing the move process. Any such pause begins at the completion of the item currently being moved. If that's a directory, all its decendants will be competed before the pause. [names copy plugin] This normally copies the name(s) of selected item(s) to the clipboard. If a key is pressed when the plugin is activated, the full path of each item will be copied, instead of just its name. If a key is pressed when the plugin is activated, copied item paths or names will be separated by a "newline" character, instead of a space. [pack plugin] Archives of these sorts are supported: .tar.gz .tar.bz2 .tar .zip .7z .rar .arj .zoo [rename plugin] Items to be processed can be located in any specified filesystem directory, and optionally, in any descendant directory of the starting point. Items to be processed can be those selected (in either displayed filelist), or all items which match a specific name, or match a name-pattern with wildcard character(s) "?" and/or "*", or a match a name-pattern with regular-expression syntax. Note that internally, regular-expression matching is always used. Regular expressions are "greedy" and this can produce unexpected results. For example, using a wildcard pattern "*-*", when renaming an item "12-34-56", the first "*" will match "12-34", not "12". Replacement names can be made all lower-case or all upper-case, and/or a pattern which contains wildcard characters, or "back-reference(s)" to regular-expression-group(s) in the search pattern. Documentation on regular expressions can be found by running the shell command "man 7 regex" or "man grep" or from www.regular-expressions.info/reference.html, or many other places. In brief: these are the special characters: ^ match the beginning of the line $ match the end of line \ ignore the special meaning of the next character \\ a literal "\" - indicates a range when not in the first/last position when specifying ranges with "[" and "]" (see below about LISTs) | union of regular expressions [LIST] match a single character specified inside the brackets e.g. [a-z] [^LIST] do not match a single character specified in the brackets e.g. [^0-9] . match any single character (including non-printable ones) * repeat the previous regular expression 0 or more times + repeat the previous regular expression 1 or more times ? repeat the previous regular expression 0 or 1 times (EXP) groups the expression inside the brackets This means, for example, that ".*" is needed where a wildcard "*" might have been used, and "\." for a literal "." Your installed software may only support 'basic' regular expressions, in which case any ? + { | ( ) needs to be preceded by a "\". Groups like (EXP) may be referred to in a replacement name-pattern by \1, \2 ... in order of their occurrence. The whole of any matching item name may be referred to in a replacement name-pattern by \0 (This is an emelFM2 extension, not standard regular expresssion syntax.) The replacement name-pattern may include up to 4 counter macros (see macros section below, for details). In this context, initial value defaults to 1 if not explicitly provided. If more than one counter is included, the respective i and w parameters are independent of those in the other counters. A normal "%c" in the name-pattern must be escaped as "\%c". It is generally wise to apply the confirmation option, so that each replacement name can be checked before it is applied. [sort-by-extension plugin] This is now effectively redundant, as the same functionality is available by clicking the filename column header when a key is pressed. When extension sorting is active, the column-header arrow changes to point right (for ascending order) or left (for descending order). [thumbnails plugin] This opens a dialog window showing thumbnails of all recognised images in the active-pane directory. More than one such dialog may be opened, for either or both filepanes. Images whose maximum dimension is less than 32 pixels are scaled up. Images whose maximum dimension is more than 128 pixels are scaled down. No file operations (copy, delete etc) may be performed directly on items in this dialog. However, selections are normally echoed back to the associated filelist, and operations may be performed from there. Sorting of the images using the normal fields is possible via a menu opened when the Sort button is clicked. The dialog has a context-menu which enables manual refreshes of the view contents, un-selecting all items in the view, rotating or flipping the selected items, and changing whether to replicate all [de]selections in the dialog back to the associated filelist. Any rotation or flip is for display only, the original file is not altered. This plugin uses functions in libgimpthumb to manage thumbnail-cacheing in accordance with freedesktop.org specification. [timeset plugin] Replacement date(s) and/or time(s) may be separately set. If a new date or time is not provided, then the current value will be used. Formats of entered strings should be appropriate for the user's locale. By way of confirmation, the corresponding 'set' box must be checked, to implement the change. Ony the privileged user ('root') is able to vary the 'ctime' (inode change time) of any item(s). Changing ctime requires temporary changes to the system clock. That is normally unwise, as typically, other things rely on system time. Any replacement date/time can be applied recursively if the item is a directory. If multiple selected items are being processed, the replacement date/time can be applied to all remaining unprocessed items. All selected items can be conformed by simply checking a 'set' box and applying the change to all. [tracker plugin] At least until the tracker (a.k.a metatracker) information-indexer (see www.gnome.org/~jamiemcc/tracker) is further developed and better-documented, this plugin merely provides a simple listing of indexed items which match specified criteria. Tracker (currently) works with a small number of categories of item (in tracker terms, "services") e.g. documents, videos, music, or it works with mimetypes (sometimes fake mimetypes like 'system-file-manager'?). When searching by service, it's possible to also specify some content. (NOTE not yet sure how this content works - probably the specified words are AND'ed.) Tracker can also run pre-defined queries (called "rdf queries", expressed in small XML-ish files with ".rdf" extension). Rdf started as a standard on top of XML for encoding metadata and has evolved into a means for encoding information about, and relations between, things. See, for example, www.w3.org/RDF and www.xml.com/pub/a/2001/01/24/rdf.html. To find items by service, select the corresponding radio button, and the service type. Any desired content may be added in the entry field at the bottom. To find items by mimetype, select the corresponding radio button, and add one or more mimetypes into the entry field at the bottom. If more than one type, separate them with a space character. To use an rdf query, select the corresponding radio button, and enter or choose the path of the query file. The tracker package includes utilities "tracker-files", "tracker-search" and "tracker-query". The plugin expects those to be executable by name-only (i.e. located somewhere in the search-path). Tracker itself does not have any gnome dependency, but it may be built with a simple GUI that does rely on gnome. [unpack plugin] Archives of these sorts are supported: .tar.gz .tar.bz2 .tar .zip .7z .rar .arj .zoo Compressed single-items (i.e. no tar, and usually named like "somename.ext.bz2" or "othername.ext.gz") are not supported. They can be readily unpacked by a simple command like 'bzip2 -d %f' or 'unzip %f' [view plugin] This opens the first selected item with the internal text-file viewer. It is intended to be used when the default view action is configured to use an external viewer. CONFIGURATION Most things about the e2 user-interface can be changed, if you wish. Hopefully the default settings are such that changes, if any, will be rare. Settings can be viewed or changed in the configuration dialog. Open that by clicking the relevant button near the right-end of the command bar, or by pressing the F11 key (unless you've already changed the configuration of those things ..) A separate document titled CONFIGURATION gives more specific guidance about e2 settings and how to change them. e2 configuration data are saved, between sessions, in 2 files: one ('cache') stores data related to run-time detals the user did not specifically set e.g. window size, history lists for visited directories. The other ('config') stores the various parameters set in the configuration dialog. Both those files are by default saved in the directory ~/.emelfm2. If either or both is deleted, e2 will simply revert to default settings for the missing data. Note that emelfm2 can (and by default does) watch for and respond to changes to the file 'config'. Some file-alteration-monitors that may be in use do not (or at least assert that they do not) follow links, so it is bad practice to make 'config' a link to some other file. [commands] Commands used in the emelFM2 (e2) command-line, or associated with a key binding, filetype, alias, are normal shell commands except as set out here. Multiple commands may be joined by ';', as for normal shells. In the following, surrounding parentheses ' ' are not part of the actual command. INTERNAL COMMANDS Names of e2 internal commands (a.k.a. actions) are of the form 'string1.string2', e.g. 'file.copy'. Actions may be executed just like an external command, by entering the action name. You may add argument(s) for an action, with whitespace character(s) between the action name and the first argument, e.g.'file.view mydocument'. A detailed description of actions and their arguments is provided in the document ACTIONS. A list of the actions can be found in the configuration dialog, e.g. by clicking on the action column on the 'key bindings' page. Much like shell environment variables, internal variables may be defined and used e.g. for communication between actions or commands. As in the case of an environment variable, an internal variable can be stored by a command of the form 'NAME=VALUE', retrieved by '$NAME', and cleared by 'NAME='. In addition, a command '=VALUE' will list the name of any variable with that value, and just '=' will list all internal variables with their values. Whitespace surrounding the "=" in any variable-related command is ignored. COMMAND MODIFIERS There are several ways to influence how e2 runs a command (action or external command). One or more of the following may be applied: * adding '>' to the beginning of an external command will cause it to run in a separate shell, i.e. bash, zsh, korn, sh etc. This is necessary if you want extra functionality like pipes, re-direction, fancy variable processing. Commands which include any of '|<>' are automatically run in a separate shell. * adding '>>' to the beginning of a "joined" external command will cause it to run as a single command in a separate shell, i.e. bash, zsh, korn, sh etc. This is necessary if you do not want the individual components of the joined command to be indivually sent to the shell. * adding '|' to the beginning of a command will cause it to 'block'. This means nothing else will happen until the command is completed. (For commands applied to selected items, any '|' will be ignored, as a configuration setting takes precedence) * adding '!' to the beginning of a command will suppress macro expansion. (See below for macros description). This means things like '%f' will be treated literally. * adding '&' to the end of a command will run it in the background, and all output other than standard error is suppressed. (For commands applied to selected items, any '&' will be ignored, as a configuration setting takes precedence) MACROS %f = substitute (space-separated series of) quoted itemname(s), corresponding to (each) selected item in the active pane %F = substitute (space-separated series of) quoted pathname(s), corresponding to (each) selected item in the inactive pane %d = substitute the active directory path, quoted %D = substitute the inactive directory path, quoted %p = substitute (space-separated series of) quoted pathname(s), corresponding to (each) selected item in the active pane %P = same as %F, for convenience Prepending an extra '%' to the above produces the same result but without quotes %c[i][,w] = substitute decimal number string. i and w are optional numbers >= 0. If present, i will set the value to be shown in the counter, or if not present, the (last-used counter-macro-value + 1) will be used. If present, w must be preceded by a ',' and sets the minimum width of the counter field, which will be padded with leading 0's as required. No spaces are allowed surrounding the ','. Width defaults to 1 if not explicitly provided. Examples: %c %c0 %c10,2 %c,3 %t = substitute absolute path of a unique sub-directory in the default temp directory %{"Prompt:"} = open a dialog seeking user input, with prompt 'Prompt:', and substitute the entered string %{(cachename)@"Prompt:"} = open a dialog seeking user input, with prompt 'Prompt:' and history list that ws cached as "cachename", and substitute the entered string %{*@"Prompt:"} = open a dialog seeking a password, with prompt 'Prompt:', and substitute the entered string (If there is no "(cachename)@" or "*@" in a %{} macro, and the prompt is to include "@", any such character must be escaped as "\@" in the prompt string.) Any string enterted via the dialog may contain other macro(s). %$prefix$ = prepend the string between the $$'s to anything that follows e.g. %$file://$%%p will result in a series of absolute unquoted path strings, each preceded by "file://". The string between the $$'s may contain other macro(s). Any other '%' inside a command is simply treated as part of the command. ALIASES e2 aliases are a mechanism for replacing part or all of a command string entered into the main command line. The process works pretty much like aliases for command line interfaces generally. Before any command is executed by e2, the recorded aliases are scanned and if a match is found, it will be substituted. As many command aliases as you wish may be recorded in the e2 configuration data. By default 'x' will run the default terminal application, and 'keys' will list in the output pane the current key bindings. The full list can be reviewed on the 'aliases' page of the configuration dialog. An alias can only be matched at the start of the entered command. Alias strings may include posix extended regular expressions. A replacement string for an alias may contain \1 or \2. The alias itself will be substituted for \1. The part of the entered command after the alias will be substituted for \2. This means you can, for example, enter a command like 'do this' and have it actually run something like 'do-the-real-command more options that always apply this', or like 'do this and that'. OTHER SUBSITITUTION '$$' will be replaced by the path of the active directory, very much like %d, except that in this case there is no trailing "/". Sometimes needed for path comparisons. '$VAR' or '${VAR}' will be replaced by the value of the environment variable 'VAR', if that exists. However such replacement will NOT happen if the $VAR or ${VAR} is part or all of a string inside parentheses, e.g. 'string with $VAR' (here, the ' ' are actually part of the string). If the variable is part of a longer string, the ${} form should be used. '$[OPT]' will be replaced by the current value of the e2 configuration option 'OPT' if it exists. The various option names can be found in the 'config' file, in lines like 'list-font=Sans 10', where 'OPT' is 'list-font' Any '~' that is at the start of a command or command-argument, and is followed by a '/', or which is the only content of an argument, will be replaced with the current user's home directory. Any un-escaped '*' or '?' in a command argument will be assumed to be part of a wildcard filename, and the argument in question will be expanded into a series of matching filenames (if any) COMMUNICATION WITH CHILD PROCESSES A command of the form 'pid:message' will pipe 'message' to the process with process id 'pid' (the process has to be a child of e2). The piped message will be encoded as utf-8. You can set 'pid' to 0 to send 'message' to the last-started child. This provides a mechanism to pass input to an application which has been started from the command line, and is waiting for input from the user. There is no mechanism to send a message to a child of a command which is running in a separate shell ("grand-child"?). There is no path for getting messages back from child processes, other than for display in the output pane. EXAMPLES diff -c %f %F > %{Filename for patch:} This runs the diff command to create a patch between the selected files, prompting for the filename for the patch. tar xzvf %f -C %D & This unpacks a selected tarball from the active directory into the inactive directory. Because of the '&' at the end, e2 will not list the output. su rpm -Uvh %f This opens the default terminal application and su's to root, prompting for the root password. Then it executes the RPM update command. >rpm -qlip %f | less This executes an rpm query command and pipes the output to 'less'. emelfm2-0.4.1/docs/WARNING0000600000175000017500000000042210521644703013767 0ustar cairocairoWarning: When using pre-releases, be sure to backup your configuration directory regularly (usually ~/.emelfm2). Config upgrades will only work properly between full releases. This means your configuration might get lost/changed/modifyed if you try different pre-releases. emelfm2-0.4.1/docs/INSTALL0000600000175000017500000001644410667644103014010 0ustar cairocairo$Id: INSTALL 654 2007-09-06 00:22:27Z tpgww $ REQUIREMENTS ~~~~~~~~~~~~ Gtk+ >= 2.6 (http://www.gtk.org) gcc >= 3.2 (http://gcc.gnu.org) shell commands - file >= 4, grep, sed The crypt plugin can make use of [de]compressors liblzo, libz and/or libbz2 if they're available, but will otherwise work without any [de]compression. Development files are not needed. Build-time options emelFM2 can be built with with some relevant capabilities of later Gtk+ versions. emelFM2 can be built with improved file alteration monitoring, using kernel-based mechansim (sofar only dnotify or inotify on linux) or FAM or gamin >= 0.1.0 (http://www.gnome.org/~veillard/gamin/index.html). emelFM2 can be built with plugins that require some external libraries libgimpthumb >= 2.2 for the thumbnails plugin libacl with more-or-less the full capability of the POSIX standard for ACL's for the ACL plugin COMPILING ~~~~~~~~~ make or make OPTION=choice OPTION2=choice2 .... The various make options are set out in file Makefile.config, and some of them are further discussed here. For new makers, any paramter specified in Makefile.config in this form: OPTION ?= choice means that OPTION will be assiged the value choice unless OPTION is otherwise specified by the user, as an argument to the make command, as shown in the second example above. You can of course edit Makefile.config if you want. PREFIX=//emelfm2 NOTE: changing PREFIX affects the default location of the custom icon files. The configuration file (typically ~/.emelfm2/config) includes paths for custom icon files, so if you have previously installed emelFM2 with a different PREFIX, the pre-existing config file, even if it has the current version number, will include icon paths which don't match the current PREFIX. The easiest solution for "missing" icon files is to open the emelFM2 configuration dialog, and click the "Default" button. some relevant make options: DOCS_VERSION=1 causes the default docs dir name to include the current emelFM2 version no. Some distros prefer this style. HELPDOC=MY-HELP-DOC changes the name of the main user-guide document from the default (USAGE) to MY-HELP-DOC CONFIGDOC=MY-CFG-DOC changes the name of the configuration help document from the default (CONFIGURATION) to MY-CFG-DOC USE_LATEST=1 builds a few differences that are available in the build-time gtk version, if that is later than 2.6 At most ONE of the following file-alteration-monitor processes can be enabled. Each has advantages and constraints: USE_FAM=1 builds code for file-change monitoring using FAM instead of monitoring the m/ctimes of the parent dir. Requires FAM development files and/or library, of course Functionality a bit limited, but reasonably widely available USE_GAMIN=1 builds code for file-change monitoring using gamin instead of monitoring the m/ctimes of the parent dir. Requires gamin development files and/or library. Gamin is sort of FAM on steroids, with use of kernel mechanism if one is available. Gamin never reports access-time changes, seems never to report some directories (e.g. ~/.Trash), its dnotify backend is unreliable under heavy load, and its treatment of symlinks is ambivalent But gamin does support bsd's and ntfs partitions. Source is available at www.gnome.org/~veillard/gamin/sources NOTE don't use this if only FAM is available ! It will build successfully, but crash when run ! USE_INOTIFY=1 builds code for directly interrogating linux kernel's inotify FAM functionality. Implies a kernel with support for the inotify API used in 2.6.13 (some 2.6.12's may have it too, at your distro's pleasure. Existence of /proc/sys/fs/inotify/* is a good - but not foolproof - indicator). Should simply revert to polling if built with this, but the capability is not actually present USE_DNOTIFY=1 builds code for directly interrogating linux kernel's dnotify FAM functionality. Implies a kernel with support for the dnotify - which should be everything after 2.4.0 Should simply revert to polling if built with this, but the capability is not actually present Note that dnotify requires a monitored dir to be 'opened' and so it must be mounted before displaying the dir, and cannot be unmounted while displayed WARNING - this option fared badly with one tester's smp kernel. There were spurious processes generated, and spurious filelist refreshing occurred. On investigation, this seems to have been a problem with the particular kernel. USE_KQUEUE=1 NOTE this option does not yet work. Code has been written, but needs bsd'er(s) to perfect and test it. Volunteers welcome. builds code for directly interrogating bsd kernel's kqueue FAM functionality. Should simply revert to polling if built with this, but the capability is not actually present Note that kqueue requires a monitored dir to be 'opened' and so it must be mounted before displaying the dir, and cannot be unmounted while displayed FILES_UTF8ONLY=1 By default, emelFM2 will convert path/file name strings which are encoded in something other than utf-8 (which includes ascii) for internal use and interaction with glib/gtk. If you're sure that only utf-8 or ascii will be needed, then setting this variable to 1 will omit the conversions, resulting in slightly smaller and faster code. NOTE: If your filesysten encodes path/file names in something other than utf-8/ascii (e.g. with german umlauts), you should also have: 1. set the environment variable G_FILENAME_ENCODING=character set name (or a comma-separated list of such names, and glib uses the first name in that list). OR 2. set the environment variable G_BROKEN_FILENAMES=1. In that case, glib assumes that the locale encoding is used for names. To cancel the effect of G_BROKEN_FILENAMES you have to unset the environment variable, setting it to 0 is not enough! G_FILENAME_ENCODING takes priority over G_BROKEN_FILENAMES. PANES_HORIZONTAL=1 causes the default file-pane layout to be top-to-bottom instead of side-by-side WITH_THUMBS = 1 enables a thumbnail viewer plugin. This uses libgimpthumb for managing thumbnail-file cacheing in accord with freedesktop.org specification. At least version 2.2 of that library is needed in order for the plugin to load at runtime. Corresponding development files are needed for building the plugin WITH_TRACKER = 1 enables a tracker-UI plugin. This uses at least version 0.6 of that application. No external library is involved, as the data is interrogated via shell commands. Similarly, no development file is needed at build time (these may change if tracker's API is updated). WITH_ACL = 1 enables an Access-Control-List-management plugin. This uses libacl. Not all operating systems (or more particularly their libraries used to manage ACL's) support the same level of functionality. This plugin assumes reasonably-full functionality, more-or-less the full capability of the aborted POSIX standard for ACL's. Linux meets this requirement, others will just have to experiment. Corresponding development files are needed for building the plugin UPDATE TRANSLATION FILES ~~~~~~~~~~~~~~~~~~~~~~~~ make i18n INSTALLATION ~~~~~~~~~~~~ make install or make install PREFIX=//emelfm2 To process just the plugins make install_plugins or make install_plugins PREFIX=//emelfm2 UNINSTALLATION ~~~~~~~~~~~~ make uninstall or make uninstall PREFIX=//emelfm2 or make uninstall_plugins or make uninstall_plugins PREFIX=//emelfm2 emelfm2-0.4.1/docs/HACKING0000600000175000017500000001202010647377413013735 0ustar cairocairo$Id: HACKING 539 2007-07-18 11:52:43Z tpgww $ This file is part of emelFM2. It was ripped off from xchat2. Just some tips if you're going to help with emelFM2 code (patches etc).. == Source code style conventions == * Use "//" C++ style comments. * Use tabs, not spaces, to indent code. (Recommended is a tab size of 4.) * Stick to the same consistant coding style: void routine (void) { if (function (gpointer a, gint b, gchar *c)) { //do something gint x = b + 1; printf (_("Break lines after 80 characters, in general.\n)) rt->set = e2_option_tree_register (option_name, group_name, option_name, NULL, NULL, NULL, E2_OPTION_TREE_UP_DOWN | E2_OPTION_TREE_ADD_DEL); printf (_("or _perhaps_ NOT after 80, if the line is just a string that needs to be internationalised like this.\n")); } } (vertically aligned braces, a space after if, while, functions, etc.). == How to make a really nice and clean patch? == * Please provide unified format diffs (run diff -u). * Call your patch something more meaningfull than emelfm2.diff. Have two directories, unpacked from the original archive: emelfm2-0.0.9 emelfm2-0.0.9p1 Then edit/compile the emelfm2-0.0.9p1 directory. When you're done, make a patch with: cd emelfm2-0.0.9p1 make clean cd .. diff -urN emelfm2-0.0.0 emelfm2-0.0.0p1 > emelfm2-something.diff == Some UI design rules == * Dialogs should be quitable by pressing ESC and activatable pressing ENTER. * All windows and dialogs should be freely resizable, ie they must not have a minimum size. * Provide context menus where possible. * Have a flat dialog hierarchy, ie don't have a million dialogs on top of each other to achieve something. * Dialogs must not block the main window. * Be configurable and scaleable, ie don't boast of advanced functionality but provide it through context menus and expanders. NOTE: the emelfm2 plugin api will probably change. Thus the following information is subject to change. == Plugin Development == Here is an outline of the Plugin Writing process. Hopefully I will be able to write a more extensive document when I get time. Step 1) #include "emelfm2.h" <- You need this to get access to all the functions and data structures of emelFM Step 2) Write the function that does what you want your plugin to do when activated. For example, if you want a plugin that prints "Hello World", then you would write this function. void hello_world() { status_message("Hello World\n"); } * The restricions for this function are that it must return void and can take no parameters. Step 3) Write an init_plugin function something like this: gint init_plugin(Plugin *p) { p->name = "Hello World"; p->description = "Prints \"Hello World\" on the output window."; p->plugin_cb = hello_world; /* This is whatever you named the function */ /* in Step 2 */ return TRUE; } * This function *must* be called init_plugin, take a Plugin * argument, and return gint. Step 4) If your plugin needs any cleaning up when it is unloaded you can *optionally* implement an unload_plugin function as follows: void unload_plugin(Plugin *p) { status_message("I'm being unloaded\n"); } * This function *must* be called unload_plugin, take a Plugin * argument, and return void. Step 5) Add your plugin to the "OBJS = " line in the Makfile. For example, if the source file for your plugin is hello_world.c, then you would add hello_world.so to the "OBJS = " list in the Makefile. emelFM Internals ~~~~~~~~~~~~~~~~ There are four major data structures that you will probably need to understand. ViewInfo: The backbone of emelFM is the ViewInfo structure. These structures hold everything that makes up the two panels in the interface. FileInfo: This is a simple data structure that corresponds to a single row in the panel file list. They contain two fields, the filename and a stat struct that holds the information retrieved from calling lstat on the file. App: The App data structure encompasses the entire user interface of emelFM. It includes two ViewInfo objects, corresponding to the right and left panels, and numerous GtkWidget objects needed for the interface. Config: The Config data structure holds all the configuration information including filetypes, bookmarks, plugins, etc. When emelFM is started it creates a global App object called 'app' and a global Config object called 'cfg'. It then creates two pointers to ViewInfo objects called 'curr_view' and 'other_view'. At any given time 'curr_view' will point to the panel that currently has focus and 'other_view' will point to the other panel. Important functions: GList *get_selection(ViewInfo *view); This will return a GList of FileInfo objects coresponding to the files selected (or tagged) in the panel corresponding to view. For example if you called get_selection(curr_view) it would return the selected files in the active panel. There are numerous examples in the source of how to use the GList returned by this function. emelfm2-0.4.1/docs/CONFIGURATION0000600000175000017500000004572510522022064014677 0ustar cairocairo$Id: CONFIGURATION 3 2006-11-01 04:36:04Z tpgww $ emelFM2 configuration help +----------------------------+ Note for translators: headings in this document that are surrounded by [] are search targets for context-specific help. The name inside the [] needs to match the translated name of the corresponding configuration-dialog page. Other headings here are simply capitalised. INTRODUCTION This document is still under development. Feel free to mail tooar a note about things that might usefully be added here. Surrounding parentheses ' ' or " " are used in this document for clarity, they are not part of a string or character actually entered, unless so stated explicitly. DIALOG BUTTONS Configuration dialogs include 'ok', 'apply', and 'cancel' buttons (among others). Clicking 'ok' applies any changes made in the dialog to the relevant parts (if any) of the application window, saves the updated configuration data onto disk in the 'config' file, and closes the configuration dialog. Clicking 'apply' differs from 'ok' in that the dialog window is not closed, and any changes are applied to the session, but not immediately saved to disk (though then-current configuration settings are always saved at the end of each session). Clicking 'cancel' will revert any changes made since the last 'apply' (and of course those might not be all of the changes made) then close the dialog. 'TREE' OPTIONS - GENERAL Click a tree row to focus it. Alternatively, move focus by pressing the Tab key, and within the treeview, by pressing a relevant a relevant arrow key. Click a field (cell) in a focused row to edit the cell, or edit a focused cell by pressing the space-bar. Editing a cell may invoke another dialog, if relevant (eg for an icon). Where permitted, a selected tree line can be added, deleted, moved up or down by clicking the corresponding button at the foot of the page. Where permitted, tree rows can be dragged to another location in the tree. This takes some practice to get right ! Clicking the cancel button will close the dialog without saving any changes made. Pressing escape-key will do the same. In some instances (eg button labels, menu labels), text fields are for 'public' display. You are encouraged to include a mnemonic key (gtk expects a preceeding '_') and/or may include pango markup language, like label Tree-option configuration pages have a context menu, displayed by a mouse right-click or a menu-key press. The menu replicates most of the buttons at the bottom of the page, but also allows cut/copy/paste and view collapse/expand. Where an option involves doing/running something, the text of the function (meaning an e2 internal command a.k.a. action, or external command), may be typed directly into the relevant field, or action names can be selected from a drop-down (-up?) list. Any argument for the action is entered into the second field (to the right of the first). The entered command string can comprise: action(s) and/or external command(s), modifier(s), alias, argument(s), macro(s), variable(s). Multiple commands are joined with ';'. Refer to the separate USAGE document for more about such commands. Note that for convenience when creating toolbars or menus, the actions list includes things that do not actually run anything directly. Part or all of their name is enclosed in <> - examples are 'command.', '', ''. These pseudo-actions may have argument(s) if appropriate. [bookmarks] The 'label' field holds the string that appears in the bookmarks menu. You may use a mnemonic key and/or markup in that field. The 'tooltip' field hold the menu item tooltip. The 'path' is the actual path to to bookmark. It may be '~' or start with '~/'. The '~' will be expanded to your home directory. The 'tooltip' and/or 'path' field may include variable(s), like $VAR or ${VAR}. (or $[var], but why would you want an e2 option value, in this context?) Bookmarks which are children of the first item are shown on the taskbar. Do not change the name of that first item, or else the bookmarks will not be shown. [aliases] The 'match' field contains a string that will be replaced, if it is found at the start of a command string which is about to be run. Extended regular expressions are allowed in the 'match' field. '^' is prepended to any match which does not have '^' at its start. (This causes pattern matching from the start of the string.) If an alias has its associated 'stop' field set, alias-searching will end if that alias is matched. If the flag is not set for a matched item, the scan will continue down the aliases list, with the matched command now as the target. So the alias substitution is effectively chained. The 'replace' field contains a string that will be put into a command instead of the corresponding 'match' field value. The 'replace' field is one of the "flexible" command fields - refer to the GENERAL section, above. The 'replace' field may contain '\1' and/or '\2'. If so, the 'replace' field value will be substituted for '\1', The part of the entered command, after the 'match' field and the following space(s) (effectively, the arguments), will be substituted for '\2'. Refer also to the separate USAGE document. The obscure-looking initial alias ensures that commands with pipes and/or redirections run in a separate shell. Otherwise, such commands won't work properly. [filetypes] The basic segmentation for stored filetype information is by 'category'. Each category may have as many filetypes as you wish (distinguished by the extension that is part of their name), and as many commands as you wish to execute on any file within the category. So the tree layout for each category is as follows: category name (eg image files) |____ extensions [optional color name e.g. #AB12CA or orange] | extension in this category (eg jpg) [optional color name] | another extension (if desired) [optional color name] | and so on ... | |____ commands default command for this category (see below) another command (if desired) and so on ... Extensions are assumed to be the part of the filename after a ".", usually the first one in the name. Examples are 'tar', 'gz', 'tar.gz'. Pseudo-extensions '', '', '' are recognised, and would typically be included. As of version 0.1.6, there is experimental support for setting foreground text color, for any or all categories and/or extensions. The various pseudo-filetypes (enclosed in < >, e.g. ) are not affected by any custom color. Nor are the various item-types (directories, links, sockets etc) whose colors are set on other config dialog pages. Any extension color will override the corresponding category color, if any. If no color is provided, the theme-default text color will be used, as before. Color names are stored in the 3rd column of the view, ajacent to an "extensions" heading, and/or adjacent to any specific exension under that heading. Any specific extension color wlll override any color for all extensions in the category. Color names can be keyed in manually or pasted, but to select a new color it is best to select the row in question, then click on the "Color" button near the bottom of the window. To delete a color, select the cell in question, and delete the string. Each command item has 2 adjacent fields on a single line in the tree: The left-most field (under the 'commands' field) stores a label string that will appear in the file-pane context menu. That string may include a mnemonic key and/or pango markup language. Sometimes it is convenient to simply use the name of the command. The adjacent field to the right has the command string itself. This is one of the "flexible" command fields (see GENERAL section above). The first command in each category is the default for that category, executed when an item in the category is double-clicked. You may add categories, provided that they each conform to this structure. The easiest way to add a category is to copy/paste an existing category and then edit the pasted lines (except the heading lines with 'extensions' and 'commands', of course). [plugins] The 'loaded' field is TRUE if the plugin is to be loaded into memory (i.e. made usable). (Plugins are loaded at session-start or after a change to the plugin config data.) The 'menu' field is TRUE if the plugin is to appear in the plugins context menu. (A loaded plugin may be used only for keybindings, say, and so need not be on the plugins menu.) The 'label' field holds the label that appears in the plugins context menu. You may use a mnemonic key and/or markup in that field. The 'filename' field is for the actual file name, typically e2p.so. It does not include a path. The 'location' field is for a path to a directory containing the named file, or it may be left empty if the the default plugins directory is to be used. To add a new plugin it is easiest to select the line in question, then click on the "Select" button near the bottom of the window. [key bindings] There are key some bindings that apply in all contexts within e2, and there are other bindings that apply only in specific contexts. So the tree layout for this page is as follows: general |___ keybinding |___ another keybinding |___ and so on ... | |______ context (eg panes) | |___ keybinding | |___ another keybinding | |___ and so on ... | |______ and so on ... Key bindings in the 'general' category always apply. Key bindings in a context category apply only when the focus is on the the item described by the category name. Each key binding has 3 adjacent fields on a single line in the tree: The left-most field is added automatically by pressing the desired key(s) after the field has been selected. The middle field is a 'continue' flag. Any key can be assigned to more than one action, by including the key in more than one binding. In order that the keypress interpreter does not stop at the first match, all but the last instance must have their 'continue' flag set. The right-most field 'action' is selectable from the actions list. Use with argument, for a more-complex/external command. Refer also to the USAGE document. Sequences of keys can be implemented. Any bound key may have 'child' key(s), and they may have child(ren), and so on. If so, each 'parent' is treated much like a modifier (ie like Shift, Ctrl, Alt) for each child. You might think of the sequence as forming a compound key, activated by pressing the parent, then shortly after, the child, then the grandchild, and so on. (The allowed time interval for recognising a child is set at another option, by default it is 2 seconds.) Unlike standard modifier keys, each key in the sequence can be bound to its own command. Users are prevented from changing category names, or adding, removing categories. You can of course add remove or move actual keybindings. [bar] The pane1 bar is the toolbar which by default is shown at the top of pane 1. Pane 1 is on the left, if the file-panes are tiled side-by-side, or on the top, if the panes are tiled top-to-bottom. By default, this toolbar includes a pseudo-action 'pane1.'. That needs to have the argument ',expand' for it to work properly. You may add a minimum size, if you want that to be bigger than the default, by making the argument 'size,expand' where 'size' is an integer like '100'. The pane2 bar is the toolbar which by default is shown at the top of pane 2. Pane 2 is on the right, if the file-panes are tiled side-by-side, or on the bottom, if the panes are tiled top-to-bottom. By default, this toolbar includes a pseudo-action 'pane2.'. That needs to have the argument ',expand' for it to work properly. You may add a minimum size, if you want that to be bigger than the default, by making the argument 'size,expand' where 'size' is an integer like '100'. See the section ALL TOOLBARS, below. [task bar] This is the toolbar which by default is shown between the file panes. (Actually, it is stored at the end of pane 1). By default, this toolbar includes a pseudo-action 'bookmark.'. That needs to have the argument 'task bar', for it to work properly. See the section ALL TOOLBARS, below. [command bar] This is the toolbar which by default is shown near the bottom of the window. By default, this toolbar includes a pseudo-action 'command.'. That needs to have the argument ',expand' for it to work properly. You may add a minimum size, if you want that to be bigger than the default, by making the argument 'size,expand' where 'size' is an integer like '200'. See the section ALL TOOLBARS, below. ALL TOOLBARS The 'Label' field has the label shown for each bar item if the bar's 'style' option is set accordingly. You may use a mnemonic key and/or markup in that field. Some items (eg command line, separator, submenu) never display their label and so it doesn't matter what the field contains. Typically the label for those items will be empty. The 'tooltip' and/or 'argument' field may include $ variable(s). emelFM2 toolbars support a special kind of toggle button. The configuration data for such a button comprises a pair of standard button entries. Each pair has one each of the action types "toggle.on" and "toggle.off" (or translated forms of those names). The pair do need to be adjacent in the bar configuration data. The first one of each pair sets the toggle-state at session start (unless over-ridden by a saved value). The members of the pair can have different labels, icons, tooltips, and functions. Here, "function" refers to an (internal) action or (external) command, with any appropriate argument(s). The 'action' field value is one of the 'selectable' form - refer to the GENERAL section above. Use the pseudo-actions or as appropriate.. Refer to the ACTIONS document for information about pseudo-actions. For toolbars, submenu items must be defined in a custom menu, on the "custom menus" config page. The name of that custom menu must be provided in the action field of the toolbar's sub-menu item. Refer to the "custom menus" section below. [context menu] This page deals with the content of the file-pane context menu only (other context menus cannot be altered by the user). You may use a mnemonic key and/or markup in the 'Label' field, which is what appears in the context menu. Some items (eg separator, submenu) never display their label, and so it doesn't matter what the field contains. Toggles can be used, by adding adjacent pairs of items, with actions toggle.off and toggle.on, and argument(s) which are the actual things to run. A subset of the norrnal context menu may be shown, if a key or a key is pressed when the menu is activated. Only the menu items with the 'shift' field set will be used when a Shift key is pressed. Similarly for the 'ctrl' field. The 'action' field value is one of the 'selectable' form - refer to the GENERAL section above. Use the pseudo-actions , for those types of items. Refer to the ACTIONS document for information about pseudo-actions. Submenu items are created as children of the item, or a separate custom sub-menu may be specified. In the latter case, the name of that custom menu must be provided in the action field of the sub-menu item, and the menu's contents defined on the "custom menus" config page. Refer to the "custom menus" section below. [custom menus] The "Menu" column stores the name used to identify the desired menu, when a custom-menu is specified in toolbar or context-menu configuration data. That name does not appear in the user interface, other than in this page of the configuration dialog. It need not be translated, but must be unique for the lookup process to work properly. "Child" entries for each "Menu" hold data for the actual menu items in that menu. The columns - icon, label, tip and command - have the same meaning as for other menus. Entries in these columns are optional, but presumably you will want at least a label and command. The command may be any action or external command, or combination of those. There is no separate column for action/command parameters - any such must be put into the same column as the command. Menu child-entries may in turn have children, which will be shown as a sub-menu. 'NON-TREE' OPTIONS - GENERAL The respective tooltips give reasonable guidance about the application of those options. [interface] The 'columns' page allows the various columns in file-panes 1 or 2 to be (un)hidden. It always shows the columns in the default order. To change the displayed column-order, in the relevant file-pane, drag the relevant column header to the desired place. To change the width of a column, drag the relevant column header separator to the desired width. There is a minimum size, related to the width of the header label. [icons] Icons used in toolbar buttons, dialog buttons, menu items etc can be gtk "stock" icons or custom icons. A number of the latter are supplied with the application. By default, custom icons are expected to be in the default icons directory, usually something like /usr/share/pixmaps/emelfm2 (in this example, assuming the installation 'prefix' was /usr) The icons directory can be changed, by specifying another directory in the advanced configuration dialog. Go to the 'interface' page. This provides a mechanism to implement different sets of icons. It may be convenient, for example, to create various sub-directories in your config directory (~./emlefm2) and store there icons which are compatible with various gtk themes. Even finer tuning is also possible. In the configuration dialog, any icon can be specified to be a file with a full (absolute) path. In that case, the path will over-ride any directory (default or otherwise) that would otherwise apply. [text encoding] When opening text files for viewing or editing, the character-encoding of the file in question must be recognisable to the application doing the viewing or editing. Many, but not all, such applications can deal with various encodings. Sometimes it is necessary to convert the encoding before sending the text to the application. EmelFM2 has basic encoding conversion capability which is applied when appropriate to files opened with the internal viewer or editor. There is also a configuration option to specify an external command to perform encoding conversion. If enabled, that command will be applied in preference to the internal conversion, for files opened in the internal viewer or editor. Any such external command must send the converted text to stdout. The command may contain any of the macros %f, %%f, %p, %%p. Otherwise, the current filename, surrounded by '', will be appended to the command string. EmelFM2 has configuration options to use an external text-file viewer and/or editor, with corresponding commands. If those applications require the text encoding to be altered, custom command-string(s) can be crafted accordingly. If it's appropriate to use the default external encoder command, then prepending "$[command-encoder] |" to the viwer/editor command will achieve that (e.g. $[command-encoder] | leafpad). emelfm2-0.4.1/docs/desktop_environment/0000700000175000017500000000000011015120161017016 5ustar cairocairoemelfm2-0.4.1/docs/desktop_environment/emelfm2.desktop0000600000175000017500000000037610671335770021775 0ustar cairocairo[Desktop Entry] Version=1.0 Type=Application Name=emelFM2 GenericName=file manager Comment=2-pane Gtk+2 file manager TryExec=emelfm2 Exec=emelfm2 Icon=emelfm2/emelfm2_48.png Terminal=false StartupNotify=false Categories=System;FileTools;GTK;FileManager; emelfm2-0.4.1/docs/desktop_environment/emelfm2.applications0000600000175000017500000000027210521644703022776 0ustar cairocairoemelfm2 command=emelfm2 name=emelFM2 can_open_multiple_files=false expects_uris=false requires_terminal=false startup_notify=false mime_types=inode/directory uses_gnomevfs=false emelfm2-0.4.1/docs/GPL0000600000175000017500000010451310643544426013320 0ustar cairocairo GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . emelfm2-0.4.1/docs/help.txt0000777000175000017500000000000011015120161015301 2READMEustar cairocairoemelfm2-0.4.1/docs/NEWS0000600000175000017500000000146611011704631013437 0ustar cairocairo$Id: NEWS 879 2008-05-12 00:04:41Z tpgww $ == emelFM2 0.4.1 == 19/5/2008 Highlights of this release: * some small feature changes, including an action to toggle fullscreen display of the main window, and other things to ease operation on small screens * several bugfixes, including a fix for a regression on utf8 encoding detection Details are available at http://emelfm2.net/ChangeLog. A note about the crypt plugin - this has passed all tests to now, but since it can irrevocably compromise the contents of processed files, we take a conservative approach, and CAUTION users to use the plugin warily. Apply the option to keep the original file, and confirm somewhat that encrypt(s) can be reversed successfully. (After a long period of successful operation, we'll feel more comfortable about removing this caution.) emelfm2-0.4.1/Doxyfile0000600000175000017500000001731110611774220013520 0ustar cairocairo# Doxyfile 1.3.4 #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- PROJECT_NAME = emelFM2 PROJECT_NUMBER = OUTPUT_DIRECTORY = docs/api OUTPUT_LANGUAGE = English USE_WINDOWS_ENCODING = NO BRIEF_MEMBER_DESC = YES REPEAT_BRIEF = NO ALWAYS_DETAILED_SEC = NO INLINE_INHERITED_MEMB = NO FULL_PATH_NAMES = YES STRIP_FROM_PATH = ./src/ SHORT_NAMES = NO JAVADOC_AUTOBRIEF = NO MULTILINE_CPP_IS_BRIEF = NO DETAILS_AT_TOP = YES INHERIT_DOCS = YES DISTRIBUTE_GROUP_DOC = NO TAB_SIZE = 4 ALIASES = OPTIMIZE_OUTPUT_FOR_C = YES OPTIMIZE_OUTPUT_JAVA = NO SUBGROUPING = YES #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- EXTRACT_ALL = YES EXTRACT_PRIVATE = NO EXTRACT_STATIC = NO EXTRACT_LOCAL_CLASSES = YES HIDE_UNDOC_MEMBERS = NO HIDE_UNDOC_CLASSES = YES HIDE_FRIEND_COMPOUNDS = NO HIDE_IN_BODY_DOCS = NO INTERNAL_DOCS = NO CASE_SENSE_NAMES = YES HIDE_SCOPE_NAMES = NO SHOW_INCLUDE_FILES = YES INLINE_INFO = YES SORT_MEMBER_DOCS = YES GENERATE_TODOLIST = YES GENERATE_TESTLIST = YES GENERATE_BUGLIST = YES GENERATE_DEPRECATEDLIST= YES ENABLED_SECTIONS = MAX_INITIALIZER_LINES = 30 SHOW_USED_FILES = YES #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- QUIET = YES WARNINGS = YES WARN_IF_UNDOCUMENTED = YES WARN_IF_DOC_ERROR = YES WARN_FORMAT = "$file:$line: $text" WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- INPUT = src plugins FILE_PATTERNS = *.c *.h RECURSIVE = YES EXCLUDE = e2_marshal.c e2_marshal.h EXCLUDE_SYMLINKS = YES EXCLUDE_PATTERNS = EXAMPLE_PATH = EXAMPLE_PATTERNS = EXAMPLE_RECURSIVE = NO IMAGE_PATH = INPUT_FILTER = FILTER_SOURCE_FILES = NO #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- SOURCE_BROWSER = YES INLINE_SOURCES = NO STRIP_CODE_COMMENTS = YES REFERENCED_BY_RELATION = YES REFERENCES_RELATION = YES VERBATIM_HEADERS = NO #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- ALPHABETICAL_INDEX = YES COLS_IN_ALPHA_INDEX = 5 IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- GENERATE_HTML = YES HTML_OUTPUT = html HTML_FILE_EXTENSION = .html HTML_HEADER = HTML_FOOTER = HTML_STYLESHEET = HTML_ALIGN_MEMBERS = YES GENERATE_HTMLHELP = NO CHM_FILE = HHC_LOCATION = GENERATE_CHI = NO BINARY_TOC = NO TOC_EXPAND = NO DISABLE_INDEX = NO ENUM_VALUES_PER_LINE = 4 GENERATE_TREEVIEW = YES TREEVIEW_WIDTH = 270 #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- GENERATE_LATEX = NO LATEX_OUTPUT = latex LATEX_CMD_NAME = latex MAKEINDEX_CMD_NAME = makeindex COMPACT_LATEX = NO PAPER_TYPE = a4wide EXTRA_PACKAGES = LATEX_HEADER = PDF_HYPERLINKS = NO USE_PDFLATEX = NO LATEX_BATCHMODE = NO LATEX_HIDE_INDICES = NO #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- GENERATE_RTF = NO RTF_OUTPUT = rtf COMPACT_RTF = NO RTF_HYPERLINKS = NO RTF_STYLESHEET_FILE = RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- GENERATE_MAN = NO MAN_OUTPUT = man MAN_EXTENSION = .3 MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- GENERATE_XML = NO XML_OUTPUT = xml XML_SCHEMA = XML_DTD = #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- GENERATE_PERLMOD = NO PERLMOD_LATEX = NO PERLMOD_PRETTY = YES PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- ENABLE_PREPROCESSING = YES MACRO_EXPANSION = YES EXPAND_ONLY_PREDEF = NO SEARCH_INCLUDES = YES INCLUDE_PATH = plugins plugins/optional INCLUDE_FILE_PATTERNS = PREDEFINED = EXPAND_AS_DEFINED = SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::addtions related to external references #--------------------------------------------------------------------------- TAGFILES = GENERATE_TAGFILE = ALLEXTERNALS = NO EXTERNAL_GROUPS = YES PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- CLASS_DIAGRAMS = NO HIDE_UNDOC_RELATIONS = NO HAVE_DOT = NO CLASS_GRAPH = YES COLLABORATION_GRAPH = YES UML_LOOK = NO TEMPLATE_RELATIONS = NO INCLUDE_GRAPH = YES INCLUDED_BY_GRAPH = YES CALL_GRAPH = NO GRAPHICAL_HIERARCHY = YES DOT_IMAGE_FORMAT = png DOT_PATH = DOTFILE_DIRS = MAX_DOT_GRAPH_WIDTH = 1024 MAX_DOT_GRAPH_HEIGHT = 1024 MAX_DOT_GRAPH_DEPTH = 3 GENERATE_LEGEND = YES DOT_CLEANUP = YES #--------------------------------------------------------------------------- # Configuration::addtions related to the search engine #--------------------------------------------------------------------------- SEARCHENGINE = NO