ecere/gui: Implemented Tooltips, as a ToolTip Window class, and a 'toolTip' String...
authorJerome St-Louis <jerome@ecere.com>
Sat, 4 Feb 2012 16:24:40 +0000 (23:24 +0700)
committerJerome St-Louis <jerome@ecere.com>
Sat, 4 Feb 2012 16:24:40 +0000 (23:24 +0700)
Thanks go to D.Bane for making it happen!

ecere/Makefile
ecere/Makefile.vanilla
ecere/ecere.epj
ecere/src/com/instance.ec
ecere/src/gui/Window.ec
ecere/src/gui/controls/ToolTip.ec [new file with mode: 0644]

index 6255757..798dd51 100644 (file)
@@ -123,6 +123,7 @@ OBJECTS3 = \
        $(OBJ)PaneSplitter.o \
        $(OBJ)Stacker.o \
        $(OBJ)PathBox.o \
+       $(OBJ)ToolTip.o \
        $(OBJ)ColorPicker.o \
        $(OBJ)FileDialog.o \
        $(OBJ)FindDialog.o \
@@ -156,9 +157,9 @@ OBJECTS3 = \
        $(OBJ)Socket.o \
        $(OBJ)Array.o \
        $(OBJ)AVLTree.o \
-       $(OBJ)BuiltInContainer.o \
-       $(OBJ)Container.o
+       $(OBJ)BuiltInContainer.o
 OBJECTS4 = \
+       $(OBJ)Container.o \
        $(OBJ)CustomAVLTree.o \
        $(OBJ)LinkList.o \
        $(OBJ)List.o \
@@ -249,6 +250,7 @@ COBJECTS2 = \
        $(OBJ)PaneSplitter.c \
        $(OBJ)Stacker.c \
        $(OBJ)PathBox.c \
+       $(OBJ)ToolTip.c \
        $(OBJ)ColorPicker.c \
        $(OBJ)FileDialog.c \
        $(OBJ)FindDialog.c \
@@ -258,9 +260,9 @@ COBJECTS2 = \
        $(OBJ)WindowList.c \
        $(if $(or $(LINUX),$(OSX)),$(OBJ)NCursesInterface.c,) \
        $(if $(WINDOWS),$(OBJ)Win32Interface.c,) \
-       $(if $(WINDOWS),$(OBJ)Win32ConsoleInterface.c,) \
-       $(if $(or $(LINUX),$(OSX)),$(OBJ)XInterface.c,)
+       $(if $(WINDOWS),$(OBJ)Win32ConsoleInterface.c,)
 COBJECTS3 = \
+       $(if $(or $(LINUX),$(OSX)),$(OBJ)XInterface.c,) \
        $(OBJ)TVisionSkin.c \
        $(OBJ)WindowsSkin.c \
        $(OBJ)Anchor.c \
@@ -373,6 +375,7 @@ SYMBOLS2 = \
        $(OBJ)PaneSplitter.sym \
        $(OBJ)Stacker.sym \
        $(OBJ)PathBox.sym \
+       $(OBJ)ToolTip.sym \
        $(OBJ)ColorPicker.sym \
        $(OBJ)FileDialog.sym \
        $(OBJ)FindDialog.sym \
@@ -497,6 +500,7 @@ IMPORTS2 = \
        $(OBJ)PaneSplitter.imp \
        $(OBJ)Stacker.imp \
        $(OBJ)PathBox.imp \
+       $(OBJ)ToolTip.imp \
        $(OBJ)ColorPicker.imp \
        $(OBJ)FileDialog.imp \
        $(OBJ)FindDialog.imp \
@@ -644,6 +648,7 @@ SOURCES3 = \
        src/gui/controls/PaneSplitter.ec \
        src/gui/controls/Stacker.ec \
        src/gui/controls/PathBox.ec \
+       src/gui/controls/ToolTip.ec \
        src/gui/dialogs/ColorPicker.ec \
        src/gui/dialogs/FileDialog.ec \
        src/gui/dialogs/FindDialog.ec \
@@ -683,9 +688,9 @@ SOURCES3 = \
        src/com/containers/LinkList.ec \
        src/com/containers/List.ec \
        src/com/containers/Map.ec \
-       src/com/BinaryTree.ec \
-       src/com/BTNode.ec
+       src/com/BinaryTree.ec
 SOURCES4 = \
+       src/com/BTNode.ec \
        src/com/dataTypes.ec \
        src/com/instance.ec \
        src/com/String.ec \
@@ -1165,6 +1170,9 @@ $(OBJ)Stacker.sym: src/gui/controls/Stacker.ec
 $(OBJ)PathBox.sym: src/gui/controls/PathBox.ec
        $(ECP) $(CECFLAGS) $(ECFLAGS) $(CFLAGS) -c src/gui/controls/PathBox.ec -o $(OBJ)PathBox.sym
 
+$(OBJ)ToolTip.sym: src/gui/controls/ToolTip.ec
+       $(ECP) $(CECFLAGS) $(ECFLAGS) $(CFLAGS) -c src/gui/controls/ToolTip.ec -o $(OBJ)ToolTip.sym
+
 $(OBJ)ColorPicker.sym: src/gui/dialogs/ColorPicker.ec
        $(ECP) $(CECFLAGS) $(ECFLAGS) $(CFLAGS) -c src/gui/dialogs/ColorPicker.ec -o $(OBJ)ColorPicker.sym
 
@@ -1524,6 +1532,9 @@ $(OBJ)Stacker.c: src/gui/controls/Stacker.ec $(OBJ)Stacker.sym | $(SYMBOLS)
 $(OBJ)PathBox.c: src/gui/controls/PathBox.ec $(OBJ)PathBox.sym | $(SYMBOLS)
        $(ECC) $(CECFLAGS) $(ECFLAGS) $(CFLAGS) $(FVISIBILITY) -c src/gui/controls/PathBox.ec -o $(OBJ)PathBox.c -symbols $(OBJ)
 
+$(OBJ)ToolTip.c: src/gui/controls/ToolTip.ec $(OBJ)ToolTip.sym | $(SYMBOLS)
+       $(ECC) $(CECFLAGS) $(ECFLAGS) $(CFLAGS) $(FVISIBILITY) -c src/gui/controls/ToolTip.ec -o $(OBJ)ToolTip.c -symbols $(OBJ)
+
 $(OBJ)ColorPicker.c: src/gui/dialogs/ColorPicker.ec $(OBJ)ColorPicker.sym | $(SYMBOLS)
        $(ECC) $(CECFLAGS) $(ECFLAGS) $(CFLAGS) $(FVISIBILITY) -c src/gui/dialogs/ColorPicker.ec -o $(OBJ)ColorPicker.c -symbols $(OBJ)
 
@@ -1949,6 +1960,9 @@ $(OBJ)Stacker.o: $(OBJ)Stacker.c
 $(OBJ)PathBox.o: $(OBJ)PathBox.c
        $(CC) $(CFLAGS) $(FVISIBILITY) -c $(OBJ)PathBox.c -o $(OBJ)PathBox.o
 
+$(OBJ)ToolTip.o: $(OBJ)ToolTip.c
+       $(CC) $(CFLAGS) $(FVISIBILITY) -c $(OBJ)ToolTip.c -o $(OBJ)ToolTip.o
+
 $(OBJ)ColorPicker.o: $(OBJ)ColorPicker.c
        $(CC) $(CFLAGS) $(FVISIBILITY) -c $(OBJ)ColorPicker.c -o $(OBJ)ColorPicker.o
 
index 1ded631..59497e6 100644 (file)
@@ -87,6 +87,7 @@ OBJECTS2 = \
        $(OBJ)StatusBar.o \
        $(OBJ)Stacker.o \
        $(OBJ)PathBox.o \
+       $(OBJ)ToolTip.o \
        $(OBJ)ColorPicker.o \
        $(OBJ)FileDialog.o \
        $(OBJ)FindDialog.o \
@@ -179,6 +180,7 @@ COBJECTS1 = \
 COBJECTS2 = \
        $(OBJ)Stacker.c \
        $(OBJ)PathBox.c \
+       $(OBJ)ToolTip.c \
        $(OBJ)ColorPicker.c \
        $(OBJ)FileDialog.c \
        $(OBJ)FindDialog.c \
@@ -268,6 +270,7 @@ SYMBOLS2 = \
        $(OBJ)StatusBar.sym \
        $(OBJ)Stacker.sym \
        $(OBJ)PathBox.sym \
+       $(OBJ)ToolTip.sym \
        $(OBJ)ColorPicker.sym \
        $(OBJ)FileDialog.sym \
        $(OBJ)FindDialog.sym \
@@ -357,6 +360,7 @@ IMPORTS2 = \
        $(OBJ)StatusBar.imp \
        $(OBJ)Stacker.imp \
        $(OBJ)PathBox.imp \
+       $(OBJ)ToolTip.imp \
        $(OBJ)ColorPicker.imp \
        $(OBJ)FileDialog.imp \
        $(OBJ)FindDialog.imp \
@@ -468,6 +472,7 @@ SOURCES2 = \
        src/gui/controls/StatusBar.ec \
        src/gui/controls/Stacker.ec \
        src/gui/controls/PathBox.ec \
+       src/gui/controls/ToolTip.ec \
        src/gui/dialogs/ColorPicker.ec \
        src/gui/dialogs/FileDialog.ec \
        src/gui/dialogs/FindDialog.ec \
@@ -849,6 +854,9 @@ $(OBJ)Stacker.sym: src/gui/controls/Stacker.ec
 $(OBJ)PathBox.sym: src/gui/controls/PathBox.ec
        $(ECP) $(CECFLAGS) $(ECFLAGS) $(CFLAGS) -c src/gui/controls/PathBox.ec -o $(OBJ)PathBox.sym
 
+$(OBJ)ToolTip.sym: src/gui/controls/ToolTip.ec
+       $(ECP) $(CECFLAGS) $(ECFLAGS) $(CFLAGS) -c src/gui/controls/ToolTip.ec -o $(OBJ)ToolTip.sym
+
 $(OBJ)ColorPicker.sym: src/gui/dialogs/ColorPicker.ec
        $(ECP) $(CECFLAGS) $(ECFLAGS) $(CFLAGS) -c src/gui/dialogs/ColorPicker.ec -o $(OBJ)ColorPicker.sym
 
@@ -1106,6 +1114,9 @@ $(OBJ)Stacker.c: src/gui/controls/Stacker.ec $(OBJ)Stacker.sym | $(SYMBOLS)
 $(OBJ)PathBox.c: src/gui/controls/PathBox.ec $(OBJ)PathBox.sym | $(SYMBOLS)
        $(ECC) $(CECFLAGS) $(ECFLAGS) $(CFLAGS) $(FVISIBILITY) -c src/gui/controls/PathBox.ec -o $(OBJ)PathBox.c -symbols $(OBJ)
 
+$(OBJ)ToolTip.c: src/gui/controls/ToolTip.ec $(OBJ)ToolTip.sym | $(SYMBOLS)
+       $(ECC) $(CECFLAGS) $(ECFLAGS) $(CFLAGS) $(FVISIBILITY) -c src/gui/controls/ToolTip.ec -o $(OBJ)ToolTip.c -symbols $(OBJ)
+
 $(OBJ)ColorPicker.c: src/gui/dialogs/ColorPicker.ec $(OBJ)ColorPicker.sym | $(SYMBOLS)
        $(ECC) $(CECFLAGS) $(ECFLAGS) $(CFLAGS) $(FVISIBILITY) -c src/gui/dialogs/ColorPicker.ec -o $(OBJ)ColorPicker.c -symbols $(OBJ)
 
@@ -1429,6 +1440,9 @@ $(OBJ)Stacker.o: $(OBJ)Stacker.c
 $(OBJ)PathBox.o: $(OBJ)PathBox.c
        $(CC) $(CFLAGS) $(FVISIBILITY) -c $(OBJ)PathBox.c -o $(OBJ)PathBox.o
 
+$(OBJ)ToolTip.o: $(OBJ)ToolTip.c
+       $(CC) $(CFLAGS) $(FVISIBILITY) -c $(OBJ)ToolTip.c -o $(OBJ)ToolTip.o
+
 $(OBJ)ColorPicker.o: $(OBJ)ColorPicker.c
        $(CC) $(CFLAGS) $(FVISIBILITY) -c $(OBJ)ColorPicker.c -o $(OBJ)ColorPicker.o
 
index ad6e338..b49df5b 100644 (file)
@@ -1,66 +1,6 @@
 {
    "Version" : 0.2,
    "ModuleName" : "ecere",
-   "Description" : "The allmighty Ecere Runtime library.",
-   "License" : "Ecere SDK v0.44 Draft 1 - www.ecere.com
-
-The Ecere SDK is Free Open Source Software. It is provided with NO WARRANTY 
-expressed or implied to the extent permitted by law.
-
-Applications built with it can be distributed both commercially and non
-commercially, along with the supporting Ecere runtime library(ies):
-
-   * libecere.so / ecere.dll
-      - Core eC framework + GUI toolkit, 2D/3D graphics engine, networking
-   * libecereCOM.so / ecereCOM.dll
-      - Core eC framework only
-   * libEDA.so / EDA.dll
-      - Data Access System
-
-The Ecere SDK is distributed under a revised BSD license:
-
-================================================================================
-
-   Copyright (c) 1996-2008, Jerome Jacovella-St-Louis
-   Copyright (c) 2005-2008, Ecere Corporation
-
-   All rights reserved.
-
-   Redistribution and use in source and binary forms, with or without
-   modification, are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright notice,
-      this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above copyright notice,
-      this list of conditions and the following disclaimer in the documentation
-      and/or other materials provided with the distribution.
-    * Neither the name of Ecere Corporation nor the names of its contributors
-      may be used to endorse or promote products derived from this software 
-      without specific prior written permission.
-
-   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-   \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
-   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
-   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-===============================================================================
-
-This software makes use of other software components whose licenses may also 
-apply, which are found in their respective source directories (most under
-deps/). Among them: zlib, libpng, libjpeg, giflib, harfbuzz, freetype...
-
-The IDE also communicates with GCC for compiling (MinGW on Windows), GDB for 
-debugging, and UPX for optionally compressing executables. Each of these have 
-their own license which can be found in their respective documentation folder 
-if distributed along with the SDK (in the case of the Windows version) or
-from wherever you obtained them.",
    "Options" : {
       "Warnings" : "All",
       "IncludeDirs" : [
@@ -1314,7 +1254,8 @@ from wherever you obtained them.",
                            ]
                         },
                         "Stacker.ec",
-                        "PathBox.ec"
+                        "PathBox.ec",
+                        "ToolTip.ec"
                      ]
                   },
                   {
@@ -1874,5 +1815,65 @@ from wherever you obtained them.",
             }
          ]
       }
-   ]
+   ],
+   "Description" : "The allmighty Ecere Runtime library.",
+   "License" : "Ecere SDK v0.44 Draft 1 - www.ecere.com
+
+The Ecere SDK is Free Open Source Software. It is provided with NO WARRANTY 
+expressed or implied to the extent permitted by law.
+
+Applications built with it can be distributed both commercially and non
+commercially, along with the supporting Ecere runtime library(ies):
+
+   * libecere.so / ecere.dll
+      - Core eC framework + GUI toolkit, 2D/3D graphics engine, networking
+   * libecereCOM.so / ecereCOM.dll
+      - Core eC framework only
+   * libEDA.so / EDA.dll
+      - Data Access System
+
+The Ecere SDK is distributed under a revised BSD license:
+
+================================================================================
+
+   Copyright (c) 1996-2008, Jerome Jacovella-St-Louis
+   Copyright (c) 2005-2008, Ecere Corporation
+
+   All rights reserved.
+
+   Redistribution and use in source and binary forms, with or without
+   modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice,
+      this list of conditions and the following disclaimer in the documentation
+      and/or other materials provided with the distribution.
+    * Neither the name of Ecere Corporation nor the names of its contributors
+      may be used to endorse or promote products derived from this software 
+      without specific prior written permission.
+
+   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+   \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+===============================================================================
+
+This software makes use of other software components whose licenses may also 
+apply, which are found in their respective source directories (most under
+deps/). Among them: zlib, libpng, libjpeg, giflib, harfbuzz, freetype...
+
+The IDE also communicates with GCC for compiling (MinGW on Windows), GDB for 
+debugging, and UPX for optionally compressing executables. Each of these have 
+their own license which can be found in their respective documentation folder 
+if distributed along with the SDK (in the case of the Windows version) or
+from wherever you obtained them."
 }
\ No newline at end of file
index 03e4066..5a62679 100644 (file)
@@ -1762,6 +1762,12 @@ public void CheckMemory()
       memoryErrorsCount++;
    }
    printf("Starting Memory Check\n");
+   for(block = (MemInfo)memBlocks.first; block; block = block.next)
+   {
+      if(!block.freed && block._class)
+         leakedObjects++;
+   }
+
    for(block = (MemInfo)memBlocks.root; block;)
    {
       if(block.freed)
@@ -1770,14 +1776,13 @@ public void CheckMemory()
       {
          if(block._class)
          {
-            leakedObjects++;
             // if(!block.internal)
             {
                // printf("Object of class %s\n", block._class);
                block.OutputStacks(false);
             }
          }
-         else
+         else if(!leakedObjects)
          {
             printf("Memory Leak\n");
             block.OutputStacks(false);
index 9def755..fa37505 100644 (file)
@@ -23,6 +23,7 @@ import "StatusBar"
 import "ProgressBar"
 import "EditBox"
 import "DataBox"
+import "ToolTip"
 
 #if !defined(ECERE_VANILLA) && !defined(ECERE_NO3D)
 import "Desktop3D"
@@ -9341,6 +9342,39 @@ private:
 public class CommonControl : Window
 {
    // creationActivation = doNothing;
+
+   ToolTip toolTip;
+   public property String toolTip
+   {
+      property_category "Appearance"
+      set
+      {
+         delete toolTip;
+         toolTip = value ? ToolTip { tip = value; } : null;
+         incref toolTip;
+      }
+      get { return toolTip ? toolTip.tip : null; }
+   }
+
+   void OnDestroy()
+   {
+      if(toolTip)
+         // (Very) Ugly work around for the fact that the parent watcher
+         // won't fire when it's already been disconnected...
+         eInstance_FireSelfWatchers(toolTip,
+            __ecereProp___ecereNameSpace__ecere__gui__Window_parent);
+   }
+
+   bool OnCreate()
+   {
+      if(toolTip)
+         toolTip.parent = this;
+      return true;
+   }
+   ~CommonControl()
+   {
+      delete toolTip;
+   }
 };
 
 public class Percentage : float
diff --git a/ecere/src/gui/controls/ToolTip.ec b/ecere/src/gui/controls/ToolTip.ec
new file mode 100644 (file)
index 0000000..3dc0459
--- /dev/null
@@ -0,0 +1,244 @@
+import "Window"
+
+struct ToolTipTextLine
+{
+   char * string;
+   int len;
+   int width;
+};
+
+public class ToolTip : Window
+{
+   borderStyle = contour;
+   background = lightYellow;
+   autoCreate = false;
+   interim = true;
+   clickThrough = true;
+   creationActivation = doNothing;
+
+   Window tippedWindow;
+   Point pos;
+
+   bool (* OrigOnMouseOver)(Window window, int x, int y, Modifiers mods);
+   bool (* OrigOnMouseLeave)(Window window, Modifiers mods);
+   bool (* OrigOnMouseMove)(Window window, int x, int y, Modifiers mods);
+   bool (* OrigOnLeftButtonDown)(Window window, int x, int y, Modifiers mods);
+
+   int lh, maxW;
+   Array<ToolTipTextLine> lines { };
+   String tip;
+   int margin; margin = 2;
+   Point offset; offset = { 0, 20 };
+
+   public property String tip
+   {
+      set
+      {
+         delete tip;
+         lines.Free();
+         if(value)
+         {
+            int c;
+            char ch, next;
+            int start = 0;
+
+            tip = CopyString(value);
+            next = tip[0];
+            for(c = 0; ch = next; c++)
+            {
+               next = tip[c+1];
+               if(ch == '\n' || next == '\0')
+               {
+                  lines.Add({ tip + start, c - start + (next == '\0') });
+                  start = c+1;
+               }
+            }
+         }
+      }
+      get { return tip; }
+   }
+
+   bool OnLoadGraphics()
+   {
+      maxW = 0;
+      display.FontExtent(fontObject, " ", 1, null, &lh);
+      for(l : lines)
+      {
+         int w;
+         display.FontExtent(fontObject, l.string, l.len, &w, null);
+         l.width = w;
+         if(w > maxW) maxW = w;
+      }
+      clientSize = { maxW + 2*margin, lh * lines.count + 2*margin };
+      return true;
+   }
+
+   bool OnMouseLeave(Modifiers mods)
+   {
+      closeTimer.Start();
+      return true;
+   }
+
+   bool OnMouseOver(int x, int y, Modifiers mods)
+   {
+      closeTimer.Stop();
+      return true;
+   }
+
+   bool OnLeftButtonDown(int x, int y, Modifiers mods)
+   {
+      Destroy(0);
+      return true;
+   }
+
+   Timer timer
+   {
+      this, 0.5, userData = this;
+
+      bool DelayExpired()
+      {
+         timer.Stop();
+         position =
+         {
+            pos.x + offset.x + tippedWindow.clientStart.x + 
+               tippedWindow.absPosition.x - parent.position.x;
+            pos.y + offset.y + tippedWindow.clientStart.y + 
+               tippedWindow.absPosition.y - parent.position.y;
+         };
+         Create();
+         return true;
+      }
+   };
+
+   Timer closeTimer
+   {
+      this, 0.3, userData = this;
+
+      bool DelayExpired()
+      {
+         closeTimer.Stop();
+         Destroy(0);
+
+         return true;
+      }
+   };
+
+   watch(parent)
+   {
+      if(tippedWindow && tippedWindow == master)
+      {
+         tippedWindow.OnMouseOver = OrigOnMouseOver;
+         tippedWindow.OnMouseLeave = OrigOnMouseLeave;
+         tippedWindow.OnMouseMove = OrigOnMouseMove;
+         tippedWindow.OnLeftButtonDown = OrigOnLeftButtonDown;
+         master = null;
+         delete tippedWindow;
+      }
+      if(parent && parent.parent)
+      {
+         Window value = parent;
+         parent = null;
+         tippedWindow = value;
+         incref tippedWindow;
+         master = tippedWindow;
+         OrigOnMouseOver = value.OnMouseOver;
+         OrigOnMouseLeave = value.OnMouseLeave;
+         OrigOnMouseMove = value.OnMouseMove;
+         OrigOnLeftButtonDown = value.OnLeftButtonDown;
+         tippedWindow.OnMouseOver = OnMouseOverHandler;
+         tippedWindow.OnMouseLeave = OnMouseLeaveHandler;
+         tippedWindow.OnMouseMove = OnMouseMoveHandler;
+         tippedWindow.OnLeftButtonDown = OnLeftButtonDownHandler;
+      }
+   };
+   ~ToolTip()
+   {
+      timer.Stop();
+      closeTimer.Stop();
+      if(tippedWindow)
+      {
+         tippedWindow.OnMouseOver = OrigOnMouseOver;
+         tippedWindow.OnMouseLeave = OrigOnMouseLeave;
+         tippedWindow.OnMouseMove = OrigOnMouseMove;
+         tippedWindow.OnLeftButtonDown = OrigOnLeftButtonDown;
+         delete tippedWindow;
+      }
+      delete tip;
+   }
+
+   ToolTip ::Find(Window window)
+   {
+      Window w;
+      for(w = window.firstSlave; w; w = w.next)
+      {
+         if(eClass_IsDerived(w._class, class(ToolTip)))
+            break;
+      }
+      return (ToolTip)w;
+   }
+
+   bool Window::OnMouseOverHandler(int x, int y, Modifiers mods)
+   {
+      ToolTip toolTip = ToolTip::Find(this);
+      if(toolTip)
+      {
+         toolTip.pos = { x, y };
+         toolTip.closeTimer.Stop();
+         if(!mods.isSideEffect && !toolTip.created && rootWindow.active)
+            toolTip.timer.Start();
+         return toolTip.OrigOnMouseOver(this, x, y, mods);
+      }
+      return true;
+   }
+
+   bool Window::OnMouseLeaveHandler(Modifiers mods)
+   {
+      ToolTip toolTip = ToolTip::Find(this);
+      if(toolTip)
+      {
+         toolTip.timer.Stop();
+         toolTip.closeTimer.Start();
+         return toolTip.OrigOnMouseLeave(this, mods);
+      }
+      return true;
+   }
+
+   bool Window::OnLeftButtonDownHandler(int x, int y, Modifiers mods)
+   {
+      ToolTip toolTip = ToolTip::Find(this);
+      if(toolTip)
+      {
+         toolTip.timer.Stop();
+         toolTip.Destroy(0);
+         return toolTip.OrigOnLeftButtonDown(this, x, y, mods);
+      }
+      return true;
+   }
+
+   bool Window::OnMouseMoveHandler(int x, int y, Modifiers mods)
+   {
+      ToolTip toolTip = ToolTip::Find(this);
+      if(toolTip)
+      {
+         toolTip.pos = { x, y };
+         toolTip.closeTimer.Stop();
+         if(!mods.isSideEffect && !toolTip.created && rootWindow.active)
+         {
+            toolTip.timer.Stop();
+            toolTip.timer.Start();
+         }
+         return toolTip.OrigOnMouseMove(this, x, y, mods);
+      }
+      return true;
+   }
+
+   void OnRedraw(Surface surface)
+   {
+      int y = margin;
+      for(l : lines)
+      {
+         surface.WriteText(margin, y, l.string, l.len);
+         y += lh;
+      }
+   }
+}