diff --git a/src/simutrans/gui/simwin.cc b/src/simutrans/gui/simwin.cc
index b2773fea7..025693527 100644
--- a/src/simutrans/gui/simwin.cc
+++ b/src/simutrans/gui/simwin.cc
@@ -154,6 +154,17 @@ static const void * tooltip_owner = 0;   // owner of the registered tooltip
 static const void * tooltip_group = 0;   // group to which the owner belongs
 static uint32 tooltip_register_time = 0; // time at which a tooltip is initially registered
 
+// Contextual build bar: the estimate published by the tool that computed the
+// current preview. The owner is kept so an estimate belonging to a tool that is
+// no longer armed can simply be ignored instead of having to be chased down.
+static const tool_t *tool_estimate_owner = NULL;
+static sint64 tool_estimate_length = 0;
+static sint64 tool_estimate_cost = 0;
+
+// Where the bar was last painted, so the world underneath can be repainted when
+// it shrinks or goes away.
+static scr_rect tool_context_last_rect;
+
 static bool show_ticker=0;
 
 /* if we are inside the event handler,
@@ -1919,6 +1930,180 @@ uint16 win_get_statusbar_height()
 }
 
 
+/**
+ * Contextual build bar: what the armed tool is about to do, painted in a band
+ * next to the status bar.
+ *
+ * Not part of the status bar and not a tooltip.
+ *
+ * The status bar is already at capacity: it has its own ladder for giving up
+ * content when it runs out of width (compact date, then ellipsised player name,
+ * then short money, then money alone - see win_display_flush below), so a
+ * second variable-width tenant would push the money off screen on small
+ * displays. A tooltip is wrong for a different reason: it would tie active
+ * build feedback both to env_t::show_tooltips and to a mouse hovering
+ * something, and neither is true of a build in progress - least of all on
+ * touch, where there is no hover at all.
+ *
+ * Painted over the world rather than reserving space, so appearing and
+ * disappearing never reflows windows or the viewport.
+ *
+ * @param screen current screen size
+ * @param show_ticker_now whether the ticker band is occupying its slot
+ */
+static void display_tool_context_bar(const scr_size &screen, bool show_ticker_now)
+{
+	tool_t *tool = wl ? wl->get_tool( wl->get_active_player_nr() ) : NULL;
+	const char *label = tool ? tool->get_context_label() : NULL;
+
+	// The modifier state as the tool would really see it: tool_t::control_invert
+	// is the "Lock Control key" toggle, and tool->flags is back to 0 by the time
+	// anything gets drawn (see interaction_t::move_view).
+	const uint8 mod_flags = (uint8)(event_get_last_control_shift() ^ tool_t::control_invert);
+
+	tool_hint_t hints[TOOL_MAX_HINTS];
+	uint8 hint_count = label ? tool->get_modifier_hints( hints, mod_flags ) : 0;
+
+	// An estimate only counts while the tool that computed it is still the armed
+	// one. That is what keeps a figure from a previous tool off the screen.
+	const bool has_estimate = label  &&  tool_estimate_owner == tool;
+
+	// Primary line: what is being built, how long it is, what it costs.
+	// Both texts already exist as translations - they are the ones
+	// tooltip_with_price_length() has always used.
+	static cbuffer_t primary;
+	primary.clear();
+	if(  has_estimate  ) {
+		char buf[256];
+		sprintf( buf, translator::translate( "length: %d" ), (int)tool_estimate_length );
+		primary.append( buf );
+		primary.append( " | " );
+		primary.append( translator::translate( "Building costs estimates" ) );
+		primary.append( ": " );
+		money_to_string( buf, (double)tool_estimate_cost / -100.0 );
+		primary.append( buf );
+	}
+
+	const scr_coord_val v_space = 2;
+	const scr_coord_val h_space = 4;
+	const scr_coord_val line_h  = LINESPACE;
+
+	scr_coord_val bar_h = 0;
+	if(  label  ) {
+		bar_h = 2 * v_space + line_h + (hint_count > 0 ? line_h : 0);
+	}
+
+	// Sit next to the status bar, on the far side of the ticker if that is up.
+	const scr_coord_val status_bar_height = win_get_statusbar_height();
+	const scr_coord_val ticker_h = show_ticker_now ? TICKER_HEIGHT : 0;
+	const scr_coord_val bar_y = env_t::menupos == MENU_BOTTOM
+		? status_bar_height + ticker_h
+		: screen.h - status_bar_height - ticker_h - bar_h;
+
+	const scr_rect bar( 0, bar_y, screen.w, bar_h );
+
+	// Repaint the world wherever the bar used to be but no longer is.
+	if(  tool_context_last_rect != bar  &&  tool_context_last_rect.h > 0  ) {
+		gfx->mark_rect_dirty_wc( tool_context_last_rect.x, tool_context_last_rect.y,
+			tool_context_last_rect.x + tool_context_last_rect.w,
+			tool_context_last_rect.y + tool_context_last_rect.h );
+		if(  wl  ) {
+			wl->set_background_dirty();
+		}
+	}
+	tool_context_last_rect = bar;
+
+	if(  bar_h == 0  ||  screen.w <= 0  ) {
+		return;
+	}
+
+	// Degradation policy when the width runs out, in this order:
+	//   1. drop the tool name, keep length and cost;
+	//   2. drop modifier hints from the right, always keeping the first one;
+	//   3. clip what is left.
+	// Cost and length are the numbers a player is actually reading off the bar,
+	// so they are the last thing to go.
+	bool show_label = label != NULL;
+	scr_coord_val avail = screen.w - 2 * h_space;
+
+	static cbuffer_t primary_full;
+	primary_full.clear();
+	if(  show_label  ) {
+		primary_full.append( label );
+		if(  primary.len() > 0  ) {
+			primary_full.append( " | " );
+		}
+	}
+	primary_full.append( primary );
+
+	if(  gfx->calc_text_width( primary_full ) > avail  &&  primary.len() > 0  ) {
+		show_label = false;
+		primary_full.clear();
+		primary_full.append( primary );
+	}
+
+	// Measure the hints, dropping from the right until they fit.
+	const char *mod_sep = "   ";
+	const scr_coord_val sep_w = gfx->calc_text_width( mod_sep );
+	while(  hint_count > 1  ) {
+		scr_coord_val w = 0;
+		for(  uint8 i = 0;  i < hint_count;  i++  ) {
+			w += gfx->calc_text_width( translator::translate( hints[i].key ) )
+			   + gfx->calc_text_width( ": " )
+			   + gfx->calc_text_width( translator::translate( hints[i].text ) );
+			if(  i + 1 < hint_count  ) {
+				w += sep_w;
+			}
+		}
+		if(  w <= avail ) {
+			break;
+		}
+		hint_count--;
+	}
+
+	gfx->set_clip_rect( 0, 0, screen.w, screen.h CLIP_NUM_DEFAULT, false );
+	gfx->draw_rect( bar.x, bar.y, bar.w, bar.h, SYSCOL_STATUSBAR_BACKGROUND, true );
+	gfx->draw_rect( bar.x, env_t::menupos == MENU_BOTTOM ? bar.y + bar.h - 1 : bar.y, bar.w, 1, SYSCOL_STATUSBAR_DIVIDER, true );
+
+	scr_coord_val y = bar.y + v_space;
+	{
+		scr_rect r( h_space, y, avail, line_h );
+		gfx->draw_text_ellipsis( r, primary_full, ALIGN_LEFT, SYSCOL_STATUSBAR_TEXT, true );
+	}
+
+	if(  hint_count > 0  ) {
+		y += line_h;
+		scr_coord_val x = h_space;
+		for(  uint8 i = 0;  i < hint_count;  i++  ) {
+			const char *key  = translator::translate( hints[i].key );
+			const char *text = translator::translate( hints[i].text );
+			const scr_coord_val key_w = gfx->calc_text_width( key );
+
+			// A held modifier marks only its own key, using the theme's own
+			// "this entry is selected" pair rather than a colour of our own.
+			if(  hints[i].active  ) {
+				gfx->draw_rect( x - 1, y, key_w + 2, line_h, SYSCOL_LIST_BACKGROUND_SELECTED_F, true );
+				gfx->draw_text( x, y, key, ALIGN_LEFT, SYSCOL_LIST_TEXT_SELECTED_FOCUS, true );
+			}
+			else {
+				gfx->draw_text( x, y, key, ALIGN_LEFT, SYSCOL_STATUSBAR_TEXT, true );
+			}
+			x += key_w;
+			x += gfx->draw_text( x, y, ": ", ALIGN_LEFT, SYSCOL_STATUSBAR_TEXT, true );
+			x += gfx->draw_text( x, y, text, ALIGN_LEFT, SYSCOL_STATUSBAR_TEXT, true );
+			if(  i + 1 < hint_count  ) {
+				x += gfx->draw_text( x, y, mod_sep, ALIGN_LEFT, SYSCOL_STATUSBAR_TEXT, true );
+			}
+		}
+	}
+	// No set_background_dirty() here on purpose: the band is repainted opaquely
+	// every frame and everything above is drawn dirty, so the world underneath
+	// only needs redrawing where the band shrank away, which is handled above.
+	// Asking for a background redraw every frame instead would pay for a
+	// display_background() on every frame the map edge happens to be in view.
+}
+
+
 // finally updates the display
 void win_display_flush(double konto)
 {
@@ -2048,6 +2233,10 @@ void win_display_flush(double konto)
 
 		POP_CLIP();
 
+		// Deliberately outside the env_t::show_tooltips block above: hover
+		// tooltips stay under that setting, active build feedback does not.
+		display_tool_context_bar( screen, show_ticker );
+
 		if(!wl) {
 			// no infos during loading etc
 			return;
@@ -2355,6 +2544,20 @@ void win_set_static_tooltip(const char *text)
 }
 
 
+void win_set_tool_estimate(const tool_t *owner, sint64 length, sint64 cost)
+{
+	tool_estimate_owner  = owner;
+	tool_estimate_length = length;
+	tool_estimate_cost   = cost;
+}
+
+
+void win_clear_tool_estimate()
+{
+	tool_estimate_owner = NULL;
+}
+
+
 // shows a modal dialoge
 void modal_dialogue(gui_frame_t* gui, ptrdiff_t magic, karte_t* welt, bool (*quit)(), bool dismissible)
 {
diff --git a/src/simutrans/gui/simwin.h b/src/simutrans/gui/simwin.h
index 3b579250b..e151fcf0b 100644
--- a/src/simutrans/gui/simwin.h
+++ b/src/simutrans/gui/simwin.h
@@ -25,6 +25,7 @@ class scr_coord;
 class loadsave_t;
 class gui_frame_t;
 class gui_component_t;
+class tool_t;
 struct event_t;
 
 /* Types for the window */
@@ -226,6 +227,28 @@ void win_set_tooltip(scr_coord pos, const char *text, const void *const owner =
  */
 void win_set_static_tooltip(const char *text);
 
+/**
+ * Publish the cost estimate of the build currently being previewed, for the
+ * contextual build bar.
+ *
+ * Deliberately separate from win_set_static_tooltip(): that one is the hover
+ * channel and stays under env_t::show_tooltips, this one is active build
+ * feedback and does not. Keeping them apart is also what stops the same
+ * numbers being drawn twice when tooltips are on.
+ *
+ * @param owner  the tool the estimate belongs to. The bar ignores an estimate
+ *               whose owner is no longer the active tool, so switching tools
+ *               cannot leave a stale figure on screen even if a tool forgets
+ *               to clear up after itself.
+ * @param length tiles the preview covers
+ * @param cost   in the same sign convention the tools already use, i.e. what
+ *               tooltip_with_price_length() is handed
+ */
+void win_set_tool_estimate(const tool_t *owner, sint64 length, sint64 cost);
+
+/// Drop any published estimate. Called whenever a preview is torn down.
+void win_clear_tool_estimate();
+
 // shows a modal dialoge (blocks other interaction)
 void modal_dialogue(gui_frame_t* gui, ptrdiff_t magic, karte_t* welt, bool (*quit)(), bool dismissible = false);
 
diff --git a/src/simutrans/tool/simmenu.cc b/src/simutrans/tool/simmenu.cc
index 8f219db4f..3c9c39073 100644
--- a/src/simutrans/tool/simmenu.cc
+++ b/src/simutrans/tool/simmenu.cc
@@ -1515,6 +1515,12 @@ void two_click_tool_t::cleanup(bool delete_start_marker)
 
 	// delete tooltip.
 	win_set_static_tooltip(NULL);
+
+	// The preview this estimate described has just been torn down, so the
+	// estimate goes with it. cleanup() runs before every mark_tiles() and from
+	// init()/exit(), which covers cancelling, switching tools, finishing a drag
+	// and being reset by the network layer.
+	win_clear_tool_estimate();
 }
 
 
diff --git a/src/simutrans/tool/simmenu.h b/src/simutrans/tool/simmenu.h
index 756b41cf1..544310774 100644
--- a/src/simutrans/tool/simmenu.h
+++ b/src/simutrans/tool/simmenu.h
@@ -190,6 +190,28 @@ enum {
 };
 
 
+/**
+ * What a modifier key does for the tool that is currently armed, and whether
+ * that effect applies with the keys held right now.
+ *
+ * One entry per *effect*, not per key. A tool where one key drives two
+ * independent effects reports two entries carrying the same key name; that is
+ * how the hints stay truthful about what the tool actually does instead of
+ * pretending a key has a single meaning.
+ */
+struct tool_hint_t {
+	/// Modifier name as a translation key. Use the tokens the keyboard help
+	/// already uses - "[CTRL]", "[SHIFT]" - so the key names stay spelled the
+	/// same everywhere and come out already localised.
+	const char *key;
+	const char *text; ///< translation key describing the effect
+	bool active;      ///< the effect applies with the modifiers currently held
+};
+
+/// Number of hints tool_t::get_modifier_hints() may write.
+#define TOOL_MAX_HINTS (4)
+
+
 class tool_t {
 protected:
 	image_id icon;
@@ -322,6 +344,25 @@ public:
 
 	virtual const char *get_tooltip(const player_t *) const { return NULL; }
 
+	/**
+	 * Short translated name of what this tool builds, for the contextual build
+	 * bar. Returning NULL (the default) keeps the bar hidden for this tool, so
+	 * only tools that opt in ever appear there.
+	 */
+	virtual const char *get_context_label() const { return NULL; }
+
+	/**
+	 * Report what Shift and Ctrl currently do for this tool.
+	 *
+	 * @param hints     array of at least TOOL_MAX_HINTS entries to fill in
+	 * @param mod_flags effective modifier state as WFL_SHIFT|WFL_CTRL bits.
+	 *                  The caller has already applied tool_t::control_invert,
+	 *                  so this is what the tool would really see, not the raw
+	 *                  keyboard state.
+	 * @returns number of hints written
+	 */
+	virtual uint8 get_modifier_hints(tool_hint_t *hints, uint8 mod_flags) const { (void)hints; (void)mod_flags; return 0; }
+
 	/**
 	 * @return true if this tool operates over the grid, not the map tiles.
 	 */
diff --git a/src/simutrans/tool/simtool.cc b/src/simutrans/tool/simtool.cc
index 73da2d00b..38dd42a30 100644
--- a/src/simutrans/tool/simtool.cc
+++ b/src/simutrans/tool/simtool.cc
@@ -2651,6 +2651,60 @@ const char* tool_build_way_t::get_tooltip(const player_t *) const
 	return toolstr;
 }
 
+const char* tool_build_way_t::get_context_label() const
+{
+	const way_desc_t *desc = get_desc();
+	return desc ? translator::translate( desc->get_name() ) : NULL;
+}
+
+
+uint8 tool_build_way_t::get_modifier_hints(tool_hint_t *hints, uint8 mod_flags) const
+{
+	const bool ctrl  = (mod_flags & WFL_CTRL)  != 0;
+	const bool shift = (mod_flags & WFL_SHIFT) != 0;
+
+	uint8 n = 0;
+
+	// Ctrl drives two independent effects here, decided in two different places
+	// in calc_route(): the straight route just below the parallel-way check, and
+	// the overwrite permission right after init_builder(). They are reported
+	// separately because they do not switch together - holding Shift as well
+	// cancels the overwrite but not the straight route - and because
+	// env_t::straight_way_without_control removes one of them from Ctrl
+	// altogether. This is descriptive: nothing here changes the behaviour.
+	bool straight_is_forced = env_t::straight_way_without_control  &&  !env_t::networkmode;
+#ifdef USE_TOWN_ROAD_BUILDER_TOOL
+	straight_is_forced |= get_id() == (TOOL_BUILD_CITYROAD | GENERAL_TOOL);
+#endif
+	if(  !straight_is_forced  ) {
+		hints[n].key    = "[CTRL]";
+		hints[n].text   = "straight route";
+		hints[n].active = ctrl;
+		n++;
+	}
+
+	hints[n].key    = "[CTRL]";
+	hints[n].text   = "replace existing ways";
+	hints[n].active = ctrl  &&  !shift;
+	n++;
+
+	if(  desc  &&  desc->get_styp() == type_flat  &&  desc->get_wtyp() == road_wt  ) {
+		hints[n].key    = "[SHIFT]";
+		hints[n].text   = "keep city roads";
+		hints[n].active = shift;
+		n++;
+	}
+	else if(  desc  &&  desc->get_styp() == type_elevated  &&  desc->get_wtyp() != air_wt  ) {
+		hints[n].key    = "[SHIFT]";
+		hints[n].text   = "stay on this level";
+		hints[n].active = shift;
+		n++;
+	}
+
+	return n;
+}
+
+
 // default ways are not initialized synchronously for different clients
 // always return the name of a way, never the string containing the waytype
 const char* tool_build_way_t::get_default_param(player_t *player) const
@@ -2939,8 +2993,8 @@ void tool_build_way_t::mark_tiles(player_t* player, const koord3d& start, const
 	uint8 offset = is_elevated ? welt->get_settings().get_way_height_clearance() : 0;
 
 	if (bauigel.get_count() > 1) {
-		// Set tooltip first (no dummygrounds, if bauigel.calc_casts() is called).
-		win_set_static_tooltip(tooltip_with_price_length("Building costs estimates", bauigel.calc_costs(), bauigel.get_count()));
+		// Publish the estimate first (no dummygrounds, if bauigel.calc_costs() is called).
+		win_set_tool_estimate(this, bauigel.get_count(), bauigel.calc_costs());
 
 		// make dummy route from bauigel
 		for (uint32 j = 0; j < bauigel.get_count(); j++) {
@@ -3274,7 +3328,14 @@ void tool_build_bridge_t::mark_tiles( player_t *player, const koord3d &start, co
 			}
 		}
 	}
-	win_set_static_tooltip( tooltip_with_price_length("Building costs estimates", costs, koord_distance(start, pos)+1 ) );
+	win_set_tool_estimate( this, koord_distance(start, pos)+1, costs );
+}
+
+
+const char* tool_build_bridge_t::get_context_label() const
+{
+	const bridge_desc_t *desc = bridge_builder_t::get_desc(default_param);
+	return desc ? translator::translate( desc->get_name() ) : NULL;
 }
 
 
@@ -3325,6 +3386,32 @@ waytype_t tool_build_tunnel_t::get_waytype() const
 }
 
 
+const char* tool_build_tunnel_t::get_context_label() const
+{
+	const tunnel_desc_t *desc = tunnel_builder_t::get_desc(default_param);
+	return desc ? translator::translate( desc->get_name() ) : NULL;
+}
+
+
+uint8 tool_build_tunnel_t::get_modifier_hints(tool_hint_t *hints, uint8 mod_flags) const
+{
+	const bool ctrl = (mod_flags & WFL_CTRL) != 0;
+
+	// Ctrl means two things here too, but unlike the way builder they always
+	// switch together: check_pos() prices a portal only, and calc_route() drops
+	// set_keep_existing_faster_ways().
+	hints[0].key    = "[CTRL]";
+	hints[0].text   = "build portal only";
+	hints[0].active = ctrl;
+
+	hints[1].key    = "[CTRL]";
+	hints[1].text   = "replace existing ways";
+	hints[1].active = ctrl;
+
+	return 2;
+}
+
+
 bool tool_build_tunnel_t::init( player_t *player )
 {
 	two_click_tool_t::init( player );
@@ -3354,6 +3441,10 @@ const char *tool_build_tunnel_t::check_pos( player_t *player, koord3d pos)
 			}
 			if( gr->ist_karten_boden() ) {
 				win_set_static_tooltip( translator::translate("No suitable ground!") );
+				// Every path out of here that is not a usable estimate has to
+				// drop the previous one, or hovering off a valid slope leaves
+				// the last cost on screen.
+				win_clear_tool_estimate();
 
 				slope_t::type sl = gr->get_grund_hang();
 				if(  sl == slope_t::flat  ||  !slope_t::is_way( sl ) ) {
@@ -3371,7 +3462,7 @@ const char *tool_build_tunnel_t::check_pos( player_t *player, koord3d pos)
 				// first check for building portal only
 				if(  is_ctrl_pressed()  ) {
 					// estimate costs for tunnel portal
-					win_set_static_tooltip( tooltip_with_price_length("Building costs estimates", (-(sint64)desc->get_price())*2, 1 ) );
+					win_set_tool_estimate( this, 1, (-(sint64)desc->get_price())*2 );
 					return NULL;
 				}
 
@@ -3382,7 +3473,7 @@ const char *tool_build_tunnel_t::check_pos( player_t *player, koord3d pos)
 					return "";
 				}
 				// estimate costs for full tunnel
-				win_set_static_tooltip( tooltip_with_price_length("Building costs estimates", (-(sint64)desc->get_price())*koord_distance(pos,end), koord_distance(pos,end) ) );
+				win_set_tool_estimate( this, koord_distance(pos,end), (-(sint64)desc->get_price())*koord_distance(pos,end) );
 				return NULL;
 			}
 		}
@@ -3532,8 +3623,9 @@ void tool_build_tunnel_t::mark_tiles(  player_t *player, const koord3d &start, c
 	welt->lookup_kartenboden(end.get_2d())->clear_flag(grund_t::marked);
 
 	if(  bauigel.get_count()>1  ) {
-		// Set tooltip first (no dummygrounds, if bauigel.calc_casts() is called).
-		win_set_static_tooltip( tooltip_with_price_length("Building costs estimates", -bauigel.calc_costs(), bauigel.get_count() ) );
+		// Publish the estimate first (no dummygrounds, if bauigel.calc_costs() is called).
+		// Sign kept exactly as it was: this one negates, the way builder does not.
+		win_set_tool_estimate( this, bauigel.get_count(), -bauigel.calc_costs() );
 
 		// make dummy route from bauigel
 		for(  uint32 j=0;  j<bauigel.get_count();  j++  ) {
@@ -4068,11 +4160,36 @@ void tool_build_wayobj_t::mark_tiles( player_t* player, const koord3d &start, co
 				marked.insert( way_obj );
 			}
 		}
-		win_set_static_tooltip( tooltip_with_price_length("Building costs estimates", -cost_estimate, verbindung.get_count() ) );
+		win_set_tool_estimate( this, verbindung.get_count(), -cost_estimate );
 	}
 }
 
 
+const char* tool_build_wayobj_t::get_context_label() const
+{
+	if(  !build  ) {
+		waytype_t wt = (waytype_t)atoi( default_param );
+		sprintf( toolstr, translator::translate("Remove wayobj %s"), translator::translate(weg_t::waytype_to_string(wt)) );
+		return toolstr;
+	}
+	const way_obj_desc_t *desc = get_desc();
+	return desc ? translator::translate( desc->get_name() ) : NULL;
+}
+
+
+uint8 tool_build_wayobj_t::get_modifier_hints(tool_hint_t *hints, uint8 mod_flags) const
+{
+	if(  !build  ) {
+		// the remover reads no modifiers at all
+		return 0;
+	}
+	hints[0].key    = "[CTRL]";
+	hints[0].text   = "replace faster wayobjs";
+	hints[0].active = (mod_flags & WFL_CTRL) != 0;
+	return 1;
+}
+
+
 const char *tool_build_wayobj_t::do_work( player_t* player, const koord3d &start, const koord3d &end )
 {
 	route_t verbindung;
@@ -5399,6 +5516,13 @@ const char *tool_build_roadsign_t::get_tooltip(const player_t *) const
 }
 
 
+const char *tool_build_roadsign_t::get_context_label() const
+{
+	const roadsign_desc_t *rs_desc = roadsign_t::find_desc(default_param);
+	return rs_desc ? translator::translate( rs_desc->get_name() ) : NULL;
+}
+
+
 bool tool_build_roadsign_t::init(player_t *player)
 {
 	desc = roadsign_t::find_desc(default_param);
@@ -5545,7 +5669,7 @@ void tool_build_roadsign_t::mark_tiles(player_t *player, const koord3d &start, c
 	}
 
 	delete dummy_rs;
-	win_set_static_tooltip( tooltip_with_price_length("Building costs estimates", cost, route.get_count() ) );
+	win_set_tool_estimate( this, route.get_count(), cost );
 }
 
 
diff --git a/src/simutrans/tool/simtool.h b/src/simutrans/tool/simtool.h
index 08e3dc40c..e58c177b1 100644
--- a/src/simutrans/tool/simtool.h
+++ b/src/simutrans/tool/simtool.h
@@ -313,6 +313,8 @@ public:
 	image_id get_icon(player_t*) const OVERRIDE;
 	static const way_desc_t* get_default_desc(waytype_t wt);
 	char const* get_tooltip(player_t const*) const OVERRIDE;
+	char const* get_context_label() const OVERRIDE;
+	uint8 get_modifier_hints(tool_hint_t*, uint8) const OVERRIDE;
 	char const* get_default_param(player_t*) const OVERRIDE;
 	bool is_selected() const OVERRIDE;
 	bool init(player_t*) OVERRIDE;
@@ -348,6 +350,8 @@ public:
 	tool_build_bridge_t() : two_click_tool_t(TOOL_BUILD_BRIDGE | GENERAL_TOOL) {}
 	image_id get_icon(player_t*) const OVERRIDE { return grund_t::underground_mode==grund_t::ugm_all ? IMG_EMPTY : icon; }
 	char const* get_tooltip(player_t const*) const OVERRIDE;
+	char const* get_context_label() const OVERRIDE;
+	// no get_modifier_hints(): the bridge builder reads neither Ctrl nor Shift
 	bool is_init_keeps_game_state() const OVERRIDE { return true; }
 	waytype_t get_waytype() const OVERRIDE;
 	bool remove_preview_necessary() const OVERRIDE { return !is_first_click(); }
@@ -364,6 +368,8 @@ private:
 public:
 	tool_build_tunnel_t() : two_click_tool_t(TOOL_BUILD_TUNNEL | GENERAL_TOOL) {}
 	char const* get_tooltip(player_t const*) const OVERRIDE;
+	char const* get_context_label() const OVERRIDE;
+	uint8 get_modifier_hints(tool_hint_t*, uint8) const OVERRIDE;
 	char const* check_pos(player_t*, koord3d) OVERRIDE;
 	bool is_init_keeps_game_state() const OVERRIDE { return true; }
 	waytype_t get_waytype() const OVERRIDE;
@@ -405,6 +411,8 @@ private:
 public:
 	tool_build_wayobj_t(uint16 const id = TOOL_BUILD_WAYOBJ | GENERAL_TOOL, bool b = true) : two_click_tool_t(id), build(b) {}
 	char const* get_tooltip(player_t const*) const OVERRIDE;
+	char const* get_context_label() const OVERRIDE;
+	uint8 get_modifier_hints(tool_hint_t*, uint8) const OVERRIDE;
 	bool is_selected() const OVERRIDE;
 	bool init(player_t*) OVERRIDE;
 	bool is_init_keeps_game_state() const OVERRIDE { return true; }
@@ -481,6 +489,12 @@ public:
 	/// @copydoc tool_t::get_tooltip
 	const char *get_tooltip(const player_t *player) const OVERRIDE;
 
+	/// @copydoc tool_t::get_context_label
+	const char *get_context_label() const OVERRIDE;
+
+	// no get_modifier_hints(): Ctrl opens the spacing dialogue at init() time,
+	// it is not a modifier that changes what a drag builds
+
 	/// @copydoc tool_t::init
 	bool init(player_t*) OVERRIDE;
 
