Repository: bokeh/bokeh

Issue:
Allow defining custom tooltip for certain tools
When using some of the more specific tools, e.g. the drawing tools, you might want to add multiple copies of the same tool and assign different semantics to each tool instance. As a simple example I wrote a demo that allows drawing two types of polygons to annotate the foreground and background of an image. In this case it is difficult to distinguish the two tool instances. It would therefore be nice if it were possible to assign the tools custom tooltips to distinguish between them.

Currently the tooltip simply reflects the ``tool_name`` on the model but it would be great if that could be overridden by another property. I think this only makes sense for certain types of tools, most likely the draw tools and maybe also the select tools.


Discussion:
It would also be great if the icon could be overridden, for the same reason.  It might be difficult to support (and to provide) arbitrary new icons for each new usage, but at least some of the drawing tools can be used for quite different things and we could provide a selection of such icons to be chosen when instantiating the tool...
> Currently the tooltip simply reflects the `tool_name`

No, tooltip is generated by a getter, which you can override. This is done in e.g. pan tool, which comes in three variants, pan, x-pan and y-pan, and all have different tooltips. 

> It would also be great if the icon could be overridden, for the same reason.

It can be. `icon` is defined as a class attribute, but one can override it with a getter. Once again pan tool is a great example, as it has different icons for its each variant.
Thanks @mattpap, great to know this is already overridable at the JS level.
But it sounds like there has to be a subclass for each variant?  Here it's about a Python user wanting to instantiate the same tool multiple times with different info each time.
No I was wrong about that before, at the JS level you can define a getter, for the pan tool it looks like this:

```javascript

  get tooltip(): string {
    return this._get_dim_tooltip("Pan", this.dimensions)
  }

  get icon(): string {
    switch (this.dimensions) {
      case "both":   return "bk-tool-icon-pan"
      case "width":  return "bk-tool-icon-xpan"
      case "height": return "bk-tool-icon-ypan"
    }
  }
```
Ok, I guess then we can put in a few canned variants to choose from...

Resolving patch:
diff --git a/bokeh/models/tools.py b/bokeh/models/tools.py
--- a/bokeh/models/tools.py
+++ b/bokeh/models/tools.py
@@ -1187,6 +1187,10 @@ class EditTool(Gesture):
 
     '''
 
+    custom_tooltip = String(None, help="""
+    A custom tooltip label to override the default name.
+    """)
+
     empty_value = Either(Bool, Int, Float, Date, Datetime, Color, help="""
     Defines the value to insert on non-coordinate columns when a new
     glyph is inserted into the ColumnDataSource columns, e.g. when a
@@ -1195,6 +1199,13 @@ class EditTool(Gesture):
     the color column will be filled with the defined empty value.
     """)
 
+    custom_icon = Image(help="""
+    An icon to display in the toolbar.
+
+    The icon can provided as a string filename for an image, a PIL ``Image``
+    object, or an RGB(A) NumPy array.
+    """)
+
     renderers = List(Instance(Renderer), help="""
     An explicit list of renderers corresponding to scatter glyphs
     that may be edited.


Test patch:
diff --git a/bokehjs/test/models/tools/edit/box_edit_tool.ts b/bokehjs/test/models/tools/edit/box_edit_tool.ts
--- a/bokehjs/test/models/tools/edit/box_edit_tool.ts
+++ b/bokehjs/test/models/tools/edit/box_edit_tool.ts
@@ -77,7 +77,18 @@ const make_testcase = function(): BoxEditTestCase {
   }
 }
 
-describe("BoxEditTool", () =>
+describe("BoxEditTool", function(): void {
+
+  describe("Model", function(): void {
+
+    it("should create proper tooltip", function(): void {
+      const tool = new BoxEditTool()
+      expect(tool.tooltip).to.be.equal('Box Edit Tool')
+
+      const custom_tool = new BoxEditTool({custom_tooltip: 'Box Edit Custom'})
+      expect(custom_tool.tooltip).to.be.equal('Box Edit Custom')
+    });
+  })
 
   describe("View", function(): void {
 
@@ -242,6 +253,6 @@ describe("BoxEditTool", () =>
       const drag_event = make_gesture_event(300, 300, true);
       testcase.draw_tool_view._doubletap(drag_event);
       expect(testcase.draw_tool_view._draw_basepoint).to.be.equal(undefined);
-    });
-  }),
-);
+    })
+  })
+});
diff --git a/bokehjs/test/models/tools/edit/freehand_draw_tool.ts b/bokehjs/test/models/tools/edit/freehand_draw_tool.ts
--- a/bokehjs/test/models/tools/edit/freehand_draw_tool.ts
+++ b/bokehjs/test/models/tools/edit/freehand_draw_tool.ts
@@ -75,6 +75,17 @@ const make_testcase = function(): FreehandDrawTestCase {
 
 describe("FreehandDrawTool", (): void => {
 
+  describe("Model", function(): void {
+
+    it("should create proper tooltip", function(): void {
+      const tool = new FreehandDrawTool()
+      expect(tool.tooltip).to.be.equal('Freehand Draw Tool')
+
+      const custom_tool = new FreehandDrawTool({custom_tooltip: 'Freehand Draw Custom'})
+      expect(custom_tool.tooltip).to.be.equal('Freehand Draw Custom')
+    });
+  })
+
   describe("View", function(): void {
 
     it("should select patches on tap", function(): void {
diff --git a/bokehjs/test/models/tools/edit/point_draw_tool.ts b/bokehjs/test/models/tools/edit/point_draw_tool.ts
--- a/bokehjs/test/models/tools/edit/point_draw_tool.ts
+++ b/bokehjs/test/models/tools/edit/point_draw_tool.ts
@@ -72,6 +72,17 @@ const make_testcase = function(): PointDrawTestCase {
 
 describe("PointDrawTool", (): void => {
 
+  describe("Model", function(): void {
+
+    it("should create proper tooltip", function(): void {
+      const tool = new PointDrawTool()
+      expect(tool.tooltip).to.be.equal('Point Draw Tool')
+
+      const custom_tool = new PointDrawTool({custom_tooltip: 'Point Draw Custom'})
+      expect(custom_tool.tooltip).to.be.equal('Point Draw Custom')
+    });
+  })
+
   describe("View", function(): void {
 
     it("should select point on tap", function(): void {
diff --git a/bokehjs/test/models/tools/edit/poly_draw_tool.ts b/bokehjs/test/models/tools/edit/poly_draw_tool.ts
--- a/bokehjs/test/models/tools/edit/poly_draw_tool.ts
+++ b/bokehjs/test/models/tools/edit/poly_draw_tool.ts
@@ -75,6 +75,17 @@ const make_testcase = function(): PolyDrawTestCase {
 
 describe("PolyDrawTool", (): void => {
 
+  describe("Model", function(): void {
+
+    it("should create proper tooltip", function(): void {
+      const tool = new PolyDrawTool()
+      expect(tool.tooltip).to.be.equal('Polygon Draw Tool')
+
+      const custom_tool = new PolyDrawTool({custom_tooltip: 'Poly Draw Custom'})
+      expect(custom_tool.tooltip).to.be.equal('Poly Draw Custom')
+    });
+  })
+
   describe("View", function(): void {
 
     it("should select patches on tap", function(): void {
diff --git a/bokehjs/test/models/tools/edit/poly_edit_tool.ts b/bokehjs/test/models/tools/edit/poly_edit_tool.ts
--- a/bokehjs/test/models/tools/edit/poly_edit_tool.ts
+++ b/bokehjs/test/models/tools/edit/poly_edit_tool.ts
@@ -105,6 +105,17 @@ const make_testcase = function(): PolyEditTestCase {
 
 describe("PolyEditTool", (): void => {
 
+  describe("Model", function(): void {
+
+    it("should create proper tooltip", function(): void {
+      const tool = new PolyEditTool()
+      expect(tool.tooltip).to.be.equal('Poly Edit Tool')
+
+      const custom_tool = new PolyEditTool({custom_tooltip: 'Poly Edit Custom'})
+      expect(custom_tool.tooltip).to.be.equal('Poly Edit Custom')
+    });
+  })
+
   describe("View", function(): void {
 
     it("should select patches on tap", function(): void {