123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026(**************************************************************************)(* *)(* This file is part of Frama-C. *)(* *)(* Copyright (C) 2007-2023 *)(* CEA (Commissariat à l'énergie atomique et aux énergies *)(* alternatives) *)(* *)(* 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, version 2.1. *)(* *)(* It 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. *)(* *)(* See the GNU Lesser General Public License version 2.1 *)(* for more details (enclosed in the file licenses/LGPLv2.1). *)(* *)(**************************************************************************)openCil_typesopenCil_datatypeopenGtk_helper(* To debug performance related to height of lines *)letfixed_height=falsetypefiletree_node=|FileofDatatype.Filepath.t*Cil_types.globallist|GlobalofCil_types.globalletsame_noden1n2=matchn1,n2with|File(f1,_),File(f2,_)->f1=f2|Globalg1,Globalg2->Cil_datatype.Global.equalg1g2|_->falselet_pretty_nodefmt=function|File(s,_)->Datatype.Filepath.prettyfmts|Global(GFun({svar=vi},_)|GVar(vi,_,_)|GFunDecl(_,vi,_)|GVarDecl(vi,_))->Format.fprintffmt"%s"vi.vname|_->()(* Fetches the internal (hidden) GtkButton of the column header.
Experimentally, to first force gtk to create a header button for the column,
you should:
- add first the column to the table;
- explicitely set the widget of the header (and this widget should not be a
button itself).
Otherwise, this function will return None. *)letget_column_header_button(col:GTree.view_column)=letrecget_button=function|None->None|Somew->ifw#misc#get_type="GtkButton"thenletbut_props=GtkButtonProps.Button.castw#as_widgetinSome(newGButton.buttonbut_props)elseget_buttonw#misc#parentinget_buttoncol#widgetclasstypet=objectmethodmodel:GTree.modelmethodflat_mode:boolmethodset_file_attribute:?strikethrough:bool->?text:string->Datatype.Filepath.t->unitmethodset_global_attribute:?strikethrough:bool->?text:string->varinfo->unitmethodadd_global_filter:text:string->key:string->(Cil_types.global->bool)->(unit->bool)*GMenu.check_menu_itemmethodget_file_globals:Datatype.Filepath.t->(string*bool)listmethodfind_visible_global:string->Cil_types.globaloptionmethodadd_select_function:(was_activated:bool->activating:bool->filetree_node->unit)->unitmethodappend_text_column:title:string->tooltip:string->visible:(unit->bool)->text:(global->string)->?sort:(global->global->int)->([`Visibility|`Contents]->unit)methodappend_pixbuf_column:title:string->(globallist->GTree.cell_properties_pixbuflist)->(unit->bool)->([`Visibility|`Contents]->unit)methodselect_global:Cil_types.global->boolmethodselected_globals:Cil_types.globallistmethodview:GTree.viewmethodreset:unit->unitmethodregister_reset_extension:(t->unit)->unitmethodrefresh_columns:unit->unitend(* crude way to to debug inefficiencies with the gtk interface *)(*let c = ref 0
let gtk s = incr c; Format.printf "[%d %s]@." !c s
*)moduleMAKE(TREE:sigtypetvalsons:t->tarrayend)=structtypecustom_tree={finfo:TREE.t;mutablesons:custom_treearray;parent:custom_treeoption;fidx:int(* invariant: parent.(fidx)==myself *)}letinboundia=i>=0&&i<Array.lengtha(** The custom model itself *)classcustom_tree_classcolumn_list=object(self)inherit[custom_tree,custom_tree,unit,unit]GTree.custom_tree_modelcolumn_listasparentmethodcustom_encode_itercr=cr,(),()methodcustom_decode_itercr()()=crmethod!custom_flags=[`ITERS_PERSIST]valmutablenum_roots:int=0valmutableroots:custom_treearray=[||]methodget_roots=rootsmethodcustom_get_iter(path:Gtk.tree_path):custom_treeoption=letindices:intarray=GTree.Path.get_indicespathinmatchindiceswith|[||]->None|_->ifinboundindices.(0)rootsthenletresult=ref(roots.(indices.(0)))intryfordepth=1toArray.lengthindices-1doletindex=indices.(depth)inifinboundindex!result.sonsthenresult:=!result.sons.(index)elseraiseNot_founddone;Some!resultwithNot_found->NoneelseNonemethodcustom_get_path(row:custom_tree):Gtk.tree_path=letcurrent_row=refrowinletpath=ref[]inwhile!current_row.parent<>Nonedopath:=!current_row.fidx::!path;current_row:=match!current_row.parentwithSomep->p|None->assertfalsedone;GTree.Path.create((!current_row.fidx)::!path)methodcustom_value(_t:Gobject.g_type)(_row:custom_tree)~column:_=assertfalsemethodcustom_iter_next(row:custom_tree):custom_treeoption=letnidx=succrow.fidxinmatchrow.parentwith|None->ifinboundnidxrootsthenSomeroots.(nidx)elseNone|Someparent->ifinboundnidxparent.sonsthenSomeparent.sons.(nidx)elseNonemethodcustom_iter_children(rowopt:custom_treeoption):custom_treeoption=matchrowoptwith|None->ifinbound0rootsthenSomeroots.(0)elseNone|Somerow->ifinbound0row.sonsthenSomerow.sons.(0)elseNonemethodcustom_iter_has_child(row:custom_tree):bool=Array.lengthrow.sons>0methodcustom_iter_n_children(rowopt:custom_treeoption):int=matchrowoptwith|None->Array.lengthroots|Somerow->Array.lengthrow.sonsmethodcustom_iter_nth_child(rowopt:custom_treeoption)(n:int):custom_treeoption=matchrowoptwith|Nonewheninboundnroots->Someroots.(n)|Somerowwheninboundnrow.sons->Some(row.sons.(n))|_->Nonemethodcustom_iter_parent(row:custom_tree):custom_treeoption=row.parentmethodcustom_foreachf=letfp_=fp(matchself#custom_get_iterpwith|Somev->v|None->assertfalse)inparent#foreachfmethodset_tree(fill_cache:intlist->custom_tree->unit)(t:TREE.tlist)=num_roots<-0;letrecmake_forestposrootsons=Array.mapi(funit->letresult={finfo=t;fidx=i;parent=Someroot;sons=[||]}infill_cache(i::pos)result;letsons=make_forest(i::pos)result(TREE.sonst)inresult.sons<-sons;result)sonsinletnew_roots=List.map(funt->letpos=num_rootsinnum_roots<-num_roots+1;letroot={finfo=t;sons=[||];parent=None;fidx=pos}infill_cache[pos]root;letsons=make_forest[pos]root(TREE.sonst)inroot.sons<-sons;root)tinroots<-Array.of_listnew_rootsmethodclear()=self#custom_foreach(funp_->self#custom_row_deletedp;false)endletcustom_tree()=newcustom_tree_class(newGTree.column_list)endmoduleMYTREE=structtypestorage={mutablename:string;globals:globalarray;mutablestrikethrough:bool}typet=MFileofstorage*tlist|MGlobalofstorage(* Sort order of the rows. *)typesort_order=|Ascending(* Ascending alphabetical order on names. *)|Descending(* Descending alphabetical order on names. *)|Customof(global->global->int)(* Custom order on globals. *)letinverse_sort=function|Ascending->Descending|Descending->Ascending|Customsort->Custom(fungh->sorthg)letstorage_type=function|MFile(s,_)->File(Datatype.Filepath.of_strings.name,Array.to_lists.globals)|MGlobal{globals=[|g|]}->Globalg|MGlobal_->assertfalseletsonst=matchtwith|MFile(_,s)->Array.of_lists|MGlobal_->[||]letsons_info=function|MFile(_,l)->List.map(function|MGlobal{name=n;strikethrough=st}->(n,st)|MFile_->assertfalse(* should not happen, a file is
never under a file in the tree *))l|MGlobal_->[]letget_storaget=matchtwith|MFile(s,_)->s|MGlobals->sletis_function_global=function|GFun_|GFunDecl_->true|_->falseletis_defined_global=function|GFun_|GVar_|GEnumTag_|GCompTag_->true|_->falseletis_undefined_global=function|GFunDecl_|GVarDecl_|GEnumTagDecl_|GCompTagDecl_->true|_->falseletis_builtin_globalg=Cil.hasAttribute"FC_BUILTIN"(Cil_datatype.Global.attrg)letcomes_from_sharefilename=letpath=Filepath.Normalized.of_stringfilenameinFilepath.is_relative~base_name:Fc_config.datadirpathletis_functiont=matchtwith|MFile_->false|MGlobal{globals=[|g|]}->is_function_globalg|MGlobal_->falseletdefault_storagesglobals={name=s;globals=globals;strikethrough=false;}letglobal_names=Pretty_utils.to_stringPrinter.pp_varnamesletextension_namee=Pretty_utils.to_stringPrinter.pp_short_extendedeletga_name=function|Dfun_or_pred(li,_)->Some(global_nameli.l_var_info.lv_name)|Dvolatile_->Some"volatile clause"|Daxiomatic(s,_,_,_)->Some(global_names)|Dtype(lti,_)->Some(global_namelti.lt_name)|Dlemma(s,_,_,_,_,_)->Some(global_names)|Dinvariant(li,_)->Some(global_nameli.l_var_info.lv_name)|Dtype_annot(li,_)->Some(global_nameli.l_var_info.lv_name)|Dmodel_annot(mf,_)->Some(global_namemf.mi_name)|Dextended(e,_,_)->Some("ACSL extension "^(extension_namee))letmake_list_globalshidesort_orderglobs=(* Association list binding names to globals. *)letl=List.fold_left(* Correct the function sons_info above if a [File] constructor can
appear in [sons] *)(funaccglob->matchglobwith|GFun({svar=vi},_)|GVar(vi,_,_)|GVarDecl(vi,_)|GFunDecl(_,vi,_)->(* Only display the last declaration/definition *)ifhideglob||(not(Ast.is_def_or_last_declglob))thenaccelse((global_namevi.vname),glob)::acc|GAnnot(ga,_)->ifhideglobthenaccelse(matchga_namegawith|None->acc|Somes->(s,glob)::acc)|_->acc)[]globsinletsort=matchsort_orderwith|Ascending->fun(s1,_)(s2,_)->Extlib.compare_ignore_cases1s2|Descending->fun(s1,_)(s2,_)->Extlib.compare_ignore_cases2s1|Customsort->fun(name1,g1)(name2,g2)->letc=sortg1g2inifc=0thenExtlib.compare_ignore_casename1name2elsecinletsorted=List.sortsortlinList.map(fun(name,g)->MGlobal(default_storagename[|g|]))sortedletmake_filehidesort_order(path,globs)=letstorage=default_storage(path:Filepath.Normalized.t:>string)(Array.of_listglobs)inletsons=make_list_globalshidesort_orderglobsinstorage,sonsendmoduleMODEL=MAKE(MYTREE)(* Primitives to handle the filetree menu (which allows to hide some
entries) *)moduleMenusHide=structlethidekey()=Configuration.find_bool~default:falsekeyletmenu_item(menu:GMenu.menu)~label~key=letmi=GMenu.check_menu_item~label()inmi#set_active(hidekey());menu#add(mi:>GMenu.menu_item);miletmi_set_callback(mi:GMenu.check_menu_item)~keyreset=mi#connect#toggled~callback:(fun()->letv=mi#activeinConfiguration.setkey(Configuration.ConfBoolv);reset())endletkey_flat_mode="filetree_flat_mode"letflat_mode=MenusHide.hidekey_flat_modeletkey_hide_stdlib="filetree_hide_stdlib"lethide_stdlib=MenusHide.hidekey_hide_stdlibmoduleState=struct(* Caching between what is selected in the filetree and the gtk to the
gtk node *)typecache={cache_files:(intlist*MODEL.custom_tree)Datatype.Filepath.Hashtbl.t;cache_vars:(intlist*MODEL.custom_tree)Varinfo.Hashtbl.t;cache_global_annot:(intlist*MODEL.custom_tree)Global_annotation.Hashtbl.t;}letdefault_cache()={cache_files=Datatype.Filepath.Hashtbl.create17;cache_vars=Varinfo.Hashtbl.create17;cache_global_annot=Global_annotation.Hashtbl.create17;}letpath_from_nodecache=function|File(s,_)->(trySome(Datatype.Filepath.Hashtbl.findcache.cache_filess)withNot_found->None)|Global(GFun({svar=vi},_)|GVar(vi,_,_)|GVarDecl(vi,_)|GFunDecl(_,vi,_))->(trySome(Varinfo.Hashtbl.findcache.cache_varsvi)withNot_found->None)|Global(GAnnot(ga,_))->(trySome(Global_annotation.Hashtbl.findcache.cache_global_annotga)withNot_found->None)|_->Noneletfill_cachecache(path:intlist)row=matchrow.MODEL.finfowith|MYTREE.MFile(storage,_)->Datatype.Filepath.Hashtbl.addcache.cache_files(Datatype.Filepath.of_stringstorage.MYTREE.name)(path,row)|MYTREE.MGlobalstorage->matchstorage.MYTREE.globalswith(* Only one element in this array by invariant: this is a leaf*)|[|GFun({svar=vi},_)|GVar(vi,_,_)|GVarDecl(vi,_)|GFunDecl(_,vi,_)|]->Varinfo.Hashtbl.addcache.cache_varsvi(path,row)|[|GAnnot(ga,_)|]->Global_annotation.Hashtbl.addcache.cache_global_annotga(path,row)|_->(* no cache for other globals yet *)()(* Extract Cil globals. We remove builtins that are not used in this project,
as well as files that do not contain anything afterwards *)letcil_files()=letfiles=Globals.FileIndex.get_files()inletglobals_of_filef=letall=List.rev(Globals.FileIndex.get_symbolsf)inletis_unused=function|GFun({svar=vi},_)|GFunDecl(_,vi,_)|GVar(vi,_,_)|GVarDecl(vi,_)->Cil_builtins.is_unused_builtinvi|_->falseinletgls=List.filter(fung->not@@is_unusedg)allinifgls=[]thenNoneelseSome(f,gls)inList.filter_mapglobals_of_filefiles(** Make and fill the custom model with default values. *)letcomputehide_filterssort_order=lethideg=List.exists(funfilter->filterg)hide_filtersinletmodel=MODEL.custom_tree()inletcache=default_cache()in(* Let's fill up the model with all files and functions. *)letfiles=cil_files()inbeginifflat_mode()thenletlist=List.concat(List.mapsndfiles)inletfiles=MYTREE.make_list_globalshidesort_orderlistinmodel#set_tree(fill_cachecache)fileselseletsorted_files=(List.sort(fun(p1,_)(p2,_)->(* invert comparison order due to inversion by fold_left below *)Filepath.Normalized.compare_prettyp2p1)files)inletfiles=List.fold_left(funaccv->letname,globals=MYTREE.make_filehidesort_ordervinifnot((hide_stdlib())&&(MYTREE.comes_from_sharename.MYTREE.name))then(MYTREE.MFile(name,globals))::accelseacc)[]sorted_filesinmodel#set_tree(fill_cachecache)files;end;model,cacheend(* Definitions related to 'Find text' using [visible_nodes] *)exceptionFound_globalofCil_types.globalexceptionGlobal_not_foundletmake(tree_view:GTree.view)=(* Menu for configuring the filetree *)letmenu=GMenu.menu()in(* Buttons to show/hide variables and/or functions *)letkey_hide_variables="filetree_hide_variables"inletkey_hide_functions="filetree_hide_functions"inletkey_hide_defined="filetree_hide_defined"inletkey_hide_undefined="filetree_hide_undefined"inletkey_hide_builtins="filetree_hide_builtins"inletkey_hide_annotations="filetree_hide_annotattions"inlethide_variables=MenusHide.hidekey_hide_variablesinlethide_functions=MenusHide.hidekey_hide_functionsinlethide_defined=MenusHide.hidekey_hide_definedinlethide_undefined=MenusHide.hidekey_hide_undefinedinlethide_builtins=MenusHide.hidekey_hide_builtinsinlethide_annotations=MenusHide.hidekey_hide_annotationsinletinitial_filterg=lethide_kind=function|GFun_|GFunDecl_->hide_functions()|GVar_|GVarDecl_->hide_variables()|GAnnot_->hide_annotations()|_->falseinhide_kindg||(MYTREE.is_builtin_globalg&&hide_builtins())||(Cil.global_is_in_libcg&&hide_stdlib())||(MYTREE.is_defined_globalg&&hide_defined())||(MYTREE.is_undefined_globalg&&hide_undefined())inletinitial_sort_order=MYTREE.Ascendinginletmhide_variables=MenusHide.menu_itemmenu~label:"Hide variables"~key:key_hide_variablesinletmhide_functions=MenusHide.menu_itemmenu~label:"Hide functions"~key:key_hide_functionsinletmhide_stdlib=MenusHide.menu_itemmenu~label:"Hide stdlib"~key:key_hide_stdlibinletmhide_defined=MenusHide.menu_itemmenu~label:"Hide defined symbols"~key:key_hide_definedinletmhide_undefined=MenusHide.menu_itemmenu~label:"Hide undefined symbols"~key:key_hide_undefinedinletmhide_builtins=MenusHide.menu_itemmenu~label:"Hide built-ins"~key:key_hide_builtinsinletmhide_annotations=MenusHide.menu_itemmenu~label:"Hide global annotations"~key:key_hide_annotationsinlet()=menu#add(GMenu.separator_item():>GMenu.menu_item)inletmflat_mode=MenusHide.menu_itemmenu~label:"Flat mode"~key:key_flat_modein(* Initial filetree nodes to display *)letinit_model,init_path_cache=State.compute[initial_filter]initial_sort_orderinletset_rowmodel?strikethrough?text(path,raw_row)=letrow=raw_row.MODEL.finfoinOption.iter(funb->(MYTREE.get_storagerow).MYTREE.strikethrough<-b)strikethrough;Option.iter(funb->(MYTREE.get_storagerow).MYTREE.name<-b)text;iffalsethenmodel#custom_row_changed(GTree.Path.create(List.revpath))raw_rowinletmyself=object(self)(* Invariant: the filetree is always completely rebuilt when the project
changes, because Design calls [reset] below. *)(* GTK model of the filetree *)valmutablemodel_custom=init_model(* caching from nodes to paths *)valmutablepath_cache=init_path_cache(* node currently selected *)valmutablecurrent_node=None(* Extendable. See method register_reset_extension. *)valmutablereset_extensions=[](* Extendable. See method add_select_function. *)valmutableselect_functions=[](* Extendable. See method add_global_filter *)valmutablehide_globals_filters=[initial_filter](* Extendable. See method append_pixbuf_column. *)valmutablecolumns_visibility=[](* Should be we call the actions registered to be applied on a node,
even if the node is already selected. Used after 'reset' has been
called. *)valmutableforce_selection=false(* Forward reference to the first column. Always set *)valmutablename_column=None(* Sort order for the rows in the filetree. Alphabetical order on names by
default, can be changed for custom order by text columns. *)valmutablesort_order=initial_sort_order(* The direction of the current sorting, and the column id according to
which the tree is sorted. Used to maintain consistent sort indicators. *)valmutablesort_kind=`ASCENDING,-1(* Properly sets the sort indicator of [column], according to the current
[sort_kind]. *)methodprivateset_sort_indicatorcolumn=letorder,id=sort_kindinifid=column#get_oidthen(column#set_sort_indicatortrue;column#set_sort_orderorder)elsecolumn#set_sort_indicatorfalse(* Changes the sort order to [sort] when left-clicking on the header of
[column]. *)methodprivatechange_sortcolumnsort=matchsort_kindwith|`ASCENDING,idwhenid=column#get_oid->sort_kind<-`DESCENDING,column#get_oid;sort_order<-MYTREE.inverse_sortsort|_->sort_kind<-`ASCENDING,column#get_oid;sort_order<-sortmethodrefresh_columns()=List.iter(funf->f`Visibility)columns_visibilitymethodappend_text_column~title~tooltip~visible~text?sort=letrenderer=GTree.cell_renderer_text[`XALIGN0.5]inletcolumn=GTree.view_column~renderer:(renderer,[])()inignore(tree_view#append_columncolumn);letlabel=GMisc.label~text:title()inGtk_helper.do_tooltip~tooltiplabel;column#set_widget(Somelabel#coerce);column#set_alignment0.5;column#set_reorderabletrue;column#set_min_width50;iffixed_heightthen(column#set_sizing`FIXED;column#set_resizablefalse;column#set_fixed_width100)elsecolumn#set_resizabletrue;lettextsglobals=List.fold_left(funaccglobal->`TEXT(textglobal)::acc)[]globalsinletfmodelrow=ifvisible()thenletpath=model#get_pathrowinself#set_sort_indicatorcolumn;matchmodel_custom#custom_get_iterpathwith|Some{MODEL.finfo=v}->letglobals=Array.to_listMYTREE.((get_storagev).globals)inrenderer#set_properties(textsglobals)|None->()incolumn#set_cell_data_funcrendererf;letsort=matchsortwith|None->fungh->String.compare(textg)(texth)|Somesort->sortinletcallback()=self#change_sortcolumn(MYTREE.Customsort);self#reset()incolumn#set_clickabletrue;ignore(column#connect#clicked~callback);letrefresh=function|`Contents->self#reset()|`Visibility->column#set_visible(visible())inrefresh`Visibility;columns_visibility<-refresh::columns_visibility;refreshmethodappend_pixbuf_column~title(f:(globallist->GTree.cell_properties_pixbuflist))visible=letcolumn=GTree.view_column~title()incolumn#set_reorderabletrue;iffixed_heightthen(column#set_sizing`FIXED;column#set_resizablefalse;column#set_fixed_width100)elsecolumn#set_resizabletrue;letrenderer=GTree.cell_renderer_pixbuf[]incolumn#packrenderer;column#set_cell_data_funcrenderer(funmodelrow->ifvisible()thenlet(path:Gtk.tree_path)=model#get_pathrowinmatchmodel_custom#custom_get_iterpathwith|Some{MODEL.finfo=v}->renderer#set_properties(f(Array.to_list((MYTREE.get_storagev).MYTREE.globals)))|None->());ignore(tree_view#append_columncolumn);letfilter_active,mi=self#filter_from_columnvisibletitlefin(* We return a function showing or masking the column*)letrefresh=letprev=reftrueinfunr->letvisible=visible()inif!prev!=visiblethen((* Column freshly appeared or disappeared. Update it *)prev:=visible;column#set_visiblevisible;mi#misc#set_sensitivevisible;(* A filter is active for the column. The visible nodes have
probably changed, destroy the filetree and rebuild it *)iffilter_active()thenself#reset();)(* Column state has not changed. If it is visible and its
contents have changed, the nodes to display may change *)elseifvisible&&r=`Contents&&filter_active()thenself#reset()inrefresh`Visibility;columns_visibility<-refresh::columns_visibility;refreshmethodprivatefilter_from_columncol_visibletitlef=letopt_active=ref(fun()->false)inlethide_globalg=col_visible()&&(!opt_active)()&&f[g]=[(`STOCK_ID"":GTree.cell_properties_pixbuf)]inlettext=Printf.sprintf"Selected by %s only"titleinletkey="filter_"^titleinletvisible,mi=self#add_global_filter~text~keyhide_globalinopt_active:=visible;(visible,mi)methodview=tree_viewmethodmodel=model_custommethodreset()=self#reset_internal();self#refresh_columns();methodregister_reset_extensionf=reset_extensions<-f::reset_extensionsmethodset_file_attribute?strikethrough?textfilename=tryset_rowmodel_custom?strikethrough?text(Datatype.Filepath.Hashtbl.findpath_cache.State.cache_filesfilename)withNot_found->()(* Some files might not be in the list because
of our filters. Ignore *)methodset_global_attribute?strikethrough?textv=tryset_rowmodel_custom?strikethrough?text(Varinfo.Hashtbl.findpath_cache.State.cache_varsv)withNot_found->()(* Some globals might not be in the list because of
our filters. Ignore *)methodflat_mode=flat_mode()methodget_file_globalsfile=trylet_,raw_row=Datatype.Filepath.Hashtbl.findpath_cache.State.cache_filesfileinMYTREE.sons_inforaw_row.MODEL.finfowithNot_found->[](* Some files may be hidden if they contain nothing
interesting *)methodfind_visible_globaltext=(* We perform up to two iterations in the list of globals, as follows:
1. First, we advance until the selected element (if any);
2. Then, we start searching for [text] until the end of the list;
3. If nothing was found, we start again, this time from the beginning
of the list until the selected global. *)letregex=Str.regexp_case_foldtextinletname_matchesname=tryignore(Str.search_forwardregexname0);truewithNot_found->falseinletfound_selection=ref(current_node=None)inletmodel=model_custominletget_global=functionGlobalg->g|_->assertfalseinletis_current_nodenode=matchcurrent_nodewith|None->false|Somenode'->same_nodenodenode'in(* Called when the currently selected node has been found. Either
the real search can start, or we abort because we have finished
wrapping around. *)letnode_found()=ifnot!found_selectionthenfound_selection:=trueelseraiseGlobal_not_found(* finished *);inletrecauxtextt=matcht.MODEL.finfowith|MYTREE.MFile({MYTREE.name},_)->(* search children *)(* note: we avoid calling [storage_type] here because
we do not need the child nodes *)letfake_node=File(Datatype.Filepath.of_stringname,[])inifis_current_nodefake_nodethennode_found();Array.iter(auxtext)t.MODEL.sons|MYTREE.MGlobal{MYTREE.name}asst->letnode=MYTREE.storage_typestinifis_current_nodenodethennode_found()else(* We never consider the current node as matching. This way, if
'foo' is selected, we can search for 'fo' and find it farther.*)if!found_selection&&name_matchesnamethenraise(Found_global(get_globalnode))intryArray.iter(auxtext)model#get_roots;(* First search did not succeed, will try second search if
user wants to wrap around. *)ifcurrent_node<>None&>oolbox.question_box~title:"Not found"(Printf.sprintf"No more occurrences for: %s\n\
Search from beginning?"text)~buttons:["Yes";"No"]=1(*yes*)thenbeginassert(!found_selection);(* try searching again *)Array.iter(auxtext)model#get_roots;end;Nonewith|Found_globalg->Someg|Global_not_found->Nonemethodprivateenable_select_functions()=letselectpathpath_currently_selected=letfaile=Gui_parameters.error"selector handler got an internal error, please report: %s@.%s@."(Printexc.to_stringe)(Printexc.get_backtrace())intrylet{MODEL.finfo=t}=Option.get(model_custom#custom_get_iterpath)inletselected_node=MYTREE.storage_typetinletwas_activated=matchcurrent_nodewith|None->false|Someold_node->same_nodeselected_nodeold_nodeinif(force_selection||notwas_activated)&¬path_currently_selectedthenbegin(*Format.printf "##Select %a: %b %b %b, %s@."
pretty_node selected_node force_selection was_activated
path_currently_selected (GTree.Path.to_string path) *)current_node<-Someselected_node;letold_force_selection=force_selectioninList.iter(funf->tryf~was_activated:(notold_force_selection&&was_activated)~activating:trueselected_nodewithe->faile)select_functions;end;force_selection<-false;truewithe->Gui_parameters.error"gui could not select row in filetree, please report: %s"(Printexc.to_stringe);trueintree_view#selection#set_select_functionselectmethodadd_select_functionf=select_functions<-select_functions@[f];methodprivatevarinfo_of_globalg=matchgwith|GVar(vi,_,_)|GVarDecl(vi,_)|GFun({svar=vi},_)|GFunDecl(_,vi,_)->Somevi|_->Nonemethodunselect=tree_view#selection#unselect_all();current_node<-None(* Display a path of the gtk filetree, by expanding and centering the
needed nodes *)methodprivateshow_path_in_treepath=expand_to_pathtree_viewpath;tree_view#selection#select_pathpath;(* set_cursor updates the keyboard cursor and scrolls to the element *)tree_view#set_cursorpath(Option.getname_column);tree_view#misc#grab_focus()(* TODO: keep the structure of the tree, ie. reexpand all the nodes that
are currently expanded (not only the currently selected) *)methodprivatereset_internal()=(* We force a full recomputation using our filters for globals *)letmc,cache=State.computehide_globals_filterssort_orderintree_view#set_model(Some(mc:>GTree.model));model_custom<-mc;path_cache<-cache;List.iter(funf->f(self:>t))reset_extensions;force_selection<-true;(* Here, current_node may come from another project. This is not
a problem, as we only use it to do a basic search. Otherwise,
the solution would be to projectify it outside of the class. *)(matchcurrent_nodewith|None->()|Somenode->matchState.path_from_nodepath_cachenodewith|None->()|Some(path,_)->self#show_path_in_tree(GTree.Path.create(List.revpath)))methodselect_globalg=matchState.path_from_nodepath_cache(Globalg)with|None->(* selection failed *)self#unselect;false|Some(path,_)->self#show_path_in_tree(GTree.Path.create(List.revpath));truemethodselected_globals=matchcurrent_nodewith|None->[]|Some(File(_,g))->g|Some(Globalg)->[g]methodadd_global_filter~text~keyf=hide_globals_filters<-f::hide_globals_filters;letmi=MenusHide.menu_itemmenu~label:text~keyinignore(MenusHide.mi_set_callbackmi~keyself#reset_internal);(MenusHide.hidekey,mi)initializer(* Name column *)letname_renderer=GTree.cell_renderer_text[`YALIGN0.0]inletcolumn=GTree.view_column~title:"Name"~renderer:((name_renderer:>GTree.cell_renderer),[])()inlet_=tree_view#append_columncolumninname_column<-Somecolumn;letm_name_rendererrenderer(lmodel:GTree.model)iter=self#set_sort_indicatorcolumn;let(path:Gtk.tree_path)=lmodel#get_pathiterinmatchself#model#custom_get_iterpathwith|Somep->letspecial,text,strike,underline=matchp.MODEL.finfowith|MYTREE.MFile({MYTREE.name=m;strikethrough=strike},_)->ifm=""(* Unknown location *)thentrue,"Unknown file",strike,falseelseletpath=Datatype.Filepath.of_stringminfalse,Filepath.Normalized.to_pretty_stringpath,strike,false|MYTREE.MGlobal({MYTREE.name=m;strikethrough=strike})ass->false,m,strike,MYTREE.is_functionsinrenderer#set_properties[`TEXTtext;`STRIKETHROUGHstrike;`WEIGHT(ifspecialthen`LIGHTelse`NORMAL);`UNDERLINE(ifunderlinethen`LOWelse`NONE)]|None->()incolumn#set_cell_data_funcname_renderer(m_name_renderername_renderer);iffixed_heightthencolumn#set_sizing`FIXED;iffixed_heightthen(column#set_resizablefalse;column#set_fixed_width100)elsecolumn#set_resizabletrue;column#set_clickabletrue;lettitle=GMisc.label~text:"Name"()incolumn#set_widget(Sometitle#coerce);(* Filter menu when right-clicking on the column header. *)letpop_menu()=menu#popup~button:3~time:(GtkMain.Main.get_current_event_time());inlet()=matchget_column_header_buttoncolumnwith|None->(* Should not happen, but who knowns? *)ignore(column#connect#clicked~callback:pop_menu)|Somebutton->(* Connect the menu to a right click. *)letcallbackevt=ifGdkEvent.Button.buttonevt=3then(pop_menu();true)elsefalseinignore(button#event#connect#button_release~callback)in(* Changes the sort order when left-clicking on the column header. *)letcallback()=self#change_sortcolumnMYTREE.Ascending;self#reset()inignore(column#connect#clicked~callback:callback);(* Sets the sort_kind to the initial sort. *)sort_kind<-`ASCENDING,column#get_oid;ignore(MenusHide.mi_set_callbackmhide_functions~key:key_hide_functionsself#reset_internal);ignore(MenusHide.mi_set_callbackmhide_variables~key:key_hide_variablesself#reset_internal);ignore(MenusHide.mi_set_callbackmhide_stdlib~key:key_hide_stdlibself#reset_internal);ignore(MenusHide.mi_set_callbackmhide_defined~key:key_hide_definedself#reset_internal);ignore(MenusHide.mi_set_callbackmhide_undefined~key:key_hide_undefinedself#reset_internal);ignore(MenusHide.mi_set_callbackmhide_builtins~key:key_hide_builtinsself#reset_internal);ignore(MenusHide.mi_set_callbackmhide_annotations~key:key_hide_annotationsself#reset_internal);ignore(MenusHide.mi_set_callbackmflat_mode~key:key_flat_modeself#reset_internal);menu#add(GMenu.separator_item():>GMenu.menu_item);tree_view#set_model(Some(init_model:>GTree.model));self#enable_select_functions();iffixed_heightthentree_view#set_fixed_height_modetrue;endin(myself:>t)(*
Local Variables:
compile-command: "make -C ../../.."
End:
*)