diff --git a/ivette/src/dome/renderer/dialogs.tsx b/ivette/src/dome/renderer/dialogs.tsx
index 36556d52bfd7a033d07e2def97524516bdff2779..bbdda3f02693deb736dca5a2a21142fb060662a7 100644
--- a/ivette/src/dome/renderer/dialogs.tsx
+++ b/ivette/src/dome/renderer/dialogs.tsx
@@ -157,7 +157,7 @@ export interface FileFilter {
 
 export interface FileDialogProps {
   /** Prompt message. */
-  message?: string;
+  title?: string;
   /** Open button label (default is « Open »). */
   label?: string;
   /** Initially selected path. */
@@ -196,11 +196,11 @@ export interface OpenDirProps extends FileDialogProps {
 export async function showOpenFile(
   props: OpenFileProps,
 ): Promise<string | undefined> {
-  const { message, label, path, hidden = false, filters } = props;
+  const { title, label, path, hidden = false, filters } = props;
   return remote.dialog.showOpenDialog(
     remote.getCurrentWindow(),
     {
-      message,
+      title,
       buttonLabel: label,
       defaultPath: path && defaultPath(path),
       properties: (hidden ? ['openFile', 'showHiddenFiles'] : ['openFile']),
@@ -219,12 +219,12 @@ export async function showOpenFile(
 export async function showOpenFiles(
   props: OpenFileProps,
 ): Promise<string[] | undefined> {
-  const { message, label, path, hidden, filters } = props;
+  const { title, label, path, hidden, filters } = props;
 
   return remote.dialog.showOpenDialog(
     remote.getCurrentWindow(),
     {
-      message,
+      title,
       buttonLabel: label,
       defaultPath: path && defaultPath(path),
       properties: (
@@ -257,11 +257,11 @@ export async function showOpenFiles(
 export async function showSaveFile(
   props: SaveFileProps,
 ): Promise<string | undefined> {
-  const { message, label, path, filters } = props;
+  const { title, label, path, filters } = props;
   return remote.dialog.showSaveDialog(
     remote.getCurrentWindow(),
     {
-      message,
+      title,
       buttonLabel: label,
       defaultPath: path,
       filters,
@@ -282,7 +282,7 @@ type openDirProperty =
 export async function showOpenDir(
   props: OpenDirProps,
 ): Promise<string | undefined> {
-  const { message, label, path, hidden } = props;
+  const { title, label, path, hidden } = props;
   const properties: openDirProperty[] = ['openDirectory'];
   if (hidden) properties.push('showHiddenFiles');
 
@@ -295,7 +295,7 @@ export async function showOpenDir(
   return remote.dialog.showOpenDialog(
     remote.getCurrentWindow(),
     {
-      message,
+      title,
       buttonLabel: label,
       defaultPath: path,
       properties,
diff --git a/ivette/src/frama-c/api/generated/kernel/ast/index.ts b/ivette/src/frama-c/api/generated/kernel/ast/index.ts
index 03afaed2e4db3b3f14b83ae484aea67859036425..fa299dac4c294998a38c3656a84797fefe1a8592 100644
--- a/ivette/src/frama-c/api/generated/kernel/ast/index.ts
+++ b/ivette/src/frama-c/api/generated/kernel/ast/index.ts
@@ -71,6 +71,11 @@ const compute_internal: Server.ExecRequest<null,null> = {
 /** Ensures that AST is computed */
 export const compute: Server.ExecRequest<null,null>= compute_internal;
 
+/** Emitted when the AST has been changed */
+export const changed: Server.Signal = {
+  name: 'kernel.ast.changed',
+};
+
 /** Marker kind */
 export enum markerKind {
   /** Expression */
@@ -284,6 +289,15 @@ export const byLocation: Compare.Order<location> =
     marker: byMarker,
   });
 
+const getMainFunction_internal: Server.GetRequest<null,Json.key<'#fct'>> = {
+  kind: Server.RqKind.GET,
+  name:   'kernel.ast.getMainFunction',
+  input:  Json.jNull,
+  output: Json.jKey<'#fct'>('#fct'),
+};
+/** Get the current 'main' function. */
+export const getMainFunction: Server.GetRequest<null,Json.key<'#fct'>>= getMainFunction_internal;
+
 const getFunctions_internal: Server.GetRequest<null,Json.key<'#fct'>[]> = {
   kind: Server.RqKind.GET,
   name:   'kernel.ast.getFunctions',
diff --git a/ivette/src/frama-c/index.tsx b/ivette/src/frama-c/index.tsx
index 2efb1200a091375e42a788cdf61d8427b1f71219..c134dfaf7dcd12e5d833711f59198a0f66a5326e 100644
--- a/ivette/src/frama-c/index.tsx
+++ b/ivette/src/frama-c/index.tsx
@@ -37,6 +37,10 @@ import Properties from 'frama-c/kernel/Properties';
 
 import 'frama-c/kernel/style.css';
 
+import * as Menu from 'frama-c/menu';
+
+Menu.init();
+
 /* --------------------------------------------------------------------------*/
 /* --- Frama-C Kernel Groups                                              ---*/
 /* --------------------------------------------------------------------------*/
diff --git a/ivette/src/frama-c/kernel/ASTview.tsx b/ivette/src/frama-c/kernel/ASTview.tsx
index 2f2d9bbae19f40609fe3f95f0526a67ca9ada098..76879dcfca9675d4aa1aeffa73711ae6ac734aac 100644
--- a/ivette/src/frama-c/kernel/ASTview.tsx
+++ b/ivette/src/frama-c/kernel/ASTview.tsx
@@ -201,14 +201,20 @@ export default function ASTview() {
     return () => { buffer.off('change', setBullets); };
   }, [buffer, setBullets]);
 
+  async function reload() {
+    printed.current = theFunction;
+    loadAST(buffer, theFunction, theMarker);
+  }
+
   // Hook: async loading
   React.useEffect(() => {
-    if (printed.current !== theFunction) {
-      printed.current = theFunction;
-      loadAST(buffer, theFunction, theMarker);
-    }
+    if (printed.current !== theFunction)
+      reload();
   });
 
+  // Also reload the buffer when the AST is recomputed.
+  Server.onSignal(Ast.changed, reload);
+
   React.useEffect(() => {
     const decorator = (marker: string) => {
       if (multipleSelections?.some((location) => location?.marker === marker))
diff --git a/ivette/src/frama-c/menu.ts b/ivette/src/frama-c/menu.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bb9a411c8b8608852f228b25dacf02f33af8f835
--- /dev/null
+++ b/ivette/src/frama-c/menu.ts
@@ -0,0 +1,65 @@
+/* ************************************************************************ */
+/*                                                                          */
+/*   This file is part of Frama-C.                                          */
+/*                                                                          */
+/*   Copyright (C) 2007-2021                                                */
+/*     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).             */
+/*                                                                          */
+/* ************************************************************************ */
+
+/* --------------------------------------------------------------------------*/
+/* --- Frama-C MENU                                                       ---*/
+/* --------------------------------------------------------------------------*/
+
+import * as Dome from 'dome';
+import * as Dialogs from 'dome/dialogs';
+import * as Server from 'frama-c/server';
+import * as Ast from 'frama-c/api/kernel/ast';
+import * as States from 'frama-c/states';
+
+const cFilter = {
+  name: 'C source files',
+  extensions: ['c', 'i', 'h'],
+};
+const allFilter = {
+  name: 'all',
+  extensions: ['*'],
+};
+
+async function setFiles(): Promise<void> {
+  const files = await Dialogs.showOpenFiles({
+    title: 'Select C source files',
+    filters: [cFilter, allFilter],
+  });
+  if (files) {
+    await Server.send(Ast.setFiles, files);
+    await Server.send(Ast.compute, { });
+    States.resetSelection();
+  }
+  return;
+}
+
+export function init() {
+  Dome.addMenuItem({
+    menu: 'File',
+    label: 'Set source files',
+    id: 'file_set',
+    onClick: setFiles,
+    type: 'normal',
+  });
+}
+
+/* --------------------------------------------------------------------------*/
diff --git a/ivette/src/frama-c/states.ts b/ivette/src/frama-c/states.ts
index 876761db7b47e3b4381140df8f8739838bd794b0..7900638fce9c60e9ef144f3f82a39985d6b2f962 100644
--- a/ivette/src/frama-c/states.ts
+++ b/ivette/src/frama-c/states.ts
@@ -772,4 +772,23 @@ export function useSelection(): [Selection, (a: SelectionActions) => void] {
   return [current, (action) => setCurrent(reducer(current, action))];
 }
 
+/** Resets the selected locations. */
+export async function resetSelection() {
+  GlobalSelection.setValue(emptySelection);
+  const main = await Server.send(Ast.getMainFunction, { });
+  const selection = {
+    ...emptySelection,
+    current: { fct: main },
+  };
+  // If the selection has already been modified, do not change it.
+  if (GlobalSelection.getValue() === emptySelection)
+    GlobalSelection.setValue(selection);
+}
+/* Select the main function when the current project changes and the selection
+   is still empty (which happens at the start of the GUI). */
+PROJECT.on(async () => {
+  if (GlobalSelection.getValue() === emptySelection)
+    resetSelection();
+});
+
 // --------------------------------------------------------------------------
diff --git a/src/plugins/server/kernel_ast.ml b/src/plugins/server/kernel_ast.ml
index 463499a6e5af2ab46a9cc9f20b3a192e16d49c6b..57794fc7dfb9c2332082940a136deeb9901f2316 100644
--- a/src/plugins/server/kernel_ast.ml
+++ b/src/plugins/server/kernel_ast.ml
@@ -37,6 +37,15 @@ let () = Request.register ~package
     ~descr:(Md.plain "Ensures that AST is computed")
     ~input:(module Junit) ~output:(module Junit) Ast.compute
 
+let changed_signal = Request.signal ~package ~name:"changed"
+    ~descr:(Md.plain "Emitted when the AST has been changed")
+
+let ast_update_hook f =
+  Ast.add_hook_on_update f;
+  Ast.apply_after_computed (fun _ -> f ())
+
+let () = ast_update_hook (fun _ -> Request.emit changed_signal)
+
 (* -------------------------------------------------------------------------- *)
 (* ---  Printers                                                          --- *)
 (* -------------------------------------------------------------------------- *)
@@ -349,6 +358,12 @@ end
 (* --- Functions                                                          --- *)
 (* -------------------------------------------------------------------------- *)
 
+let () = Request.register ~package
+    ~kind:`GET ~name:"getMainFunction"
+    ~descr:(Md.plain "Get the current 'main' function.")
+    ~input:(module Junit) ~output:(module Kf)
+    (fun () -> fst (Globals.entry_point ()))
+
 let () = Request.register ~package
     ~kind:`GET ~name:"getFunctions"
     ~descr:(Md.plain "Collect all functions in the AST")
@@ -459,7 +474,7 @@ struct
         ~name:"functions"
         ~descr:(Md.plain "AST Functions")
         ~iter
-        ~add_reload_hook:Ast.add_hook_on_update ;
+        ~add_reload_hook:ast_update_hook
     end
 
 end