diff --git a/Makefile b/Makefile
index 2fc80ff..b50d96d 100644
--- a/Makefile
+++ b/Makefile
@@ -25,6 +25,7 @@ SSOBJS   = $(OBJDIR)\solvespace.obj \
            $(OBJDIR)\expr.obj \
            $(OBJDIR)\constraint.obj \
            $(OBJDIR)\draw.obj \
+           $(OBJDIR)\toolbar.obj \
            $(OBJDIR)\drawconstraint.obj \
            $(OBJDIR)\file.obj \
            $(OBJDIR)\undoredo.obj \
@@ -67,3 +68,8 @@ $(RES): win32/$(@B).rc icon.ico
 	rc win32/$(@B).rc
 	mv win32/$(@B).res $(OBJDIR)/$(@B).res
 
+toolbar.cpp: $(OBJDIR)/icons.h
+    
+$(OBJDIR)/icons.h: icons/* png2c.pl
+    perl png2c.pl > $(OBJDIR)/icons.h
+
diff --git a/draw.cpp b/draw.cpp
index 1f59ff6..cdfc13d 100644
--- a/draw.cpp
+++ b/draw.cpp
@@ -25,6 +25,13 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown,
         shiftDown = !shiftDown;
     }
 
+    if(SS.showToolbar) {
+        if(ToolbarMouseMoved((int)x, (int)y)) {
+            hover.Clear();
+            return;
+        }
+    }
+
     Point2d mp = { x, y };
 
     // If the middle button is down, then mouse movement is used to pan and
@@ -337,6 +344,10 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) {
     if(GraphicsEditControlIsVisible()) return;
     HideTextEditControl();
 
+    if(SS.showToolbar) {
+        if(ToolbarMouseDown((int)mx, (int)my)) return;
+    }
+
     // Make sure the hover is up to date.
     MouseMoved(mx, my, false, false, false, false, false);
     orig.mouse.x = mx;
@@ -682,6 +693,14 @@ void GraphicsWindow::MouseScroll(double x, double y, int delta) {
     InvalidateGraphics();
 }
 
+void GraphicsWindow::MouseLeave(void) {
+    // Un-hover everything when the mouse leaves our window.
+    hover.Clear();
+    toolbarTooltipped = 0;
+    toolbarHovered = 0;
+    PaintGraphics();
+}
+
 bool GraphicsWindow::Selection::Equals(Selection *b) {
     if(entity.v     != b->entity.v)     return false;
     if(constraint.v != b->constraint.v) return false;
@@ -1006,5 +1025,10 @@ void GraphicsWindow::Paint(int w, int h) {
     for(i = 0; i < MAX_SELECTED; i++) {
         selection[i].Draw();
     }
+
+    // And finally the toolbar.
+    if(SS.showToolbar) {
+        ToolbarDraw();
+    }
 }
 
diff --git a/graphicswin.cpp b/graphicswin.cpp
index 49db02c..1da1df6 100644
--- a/graphicswin.cpp
+++ b/graphicswin.cpp
@@ -43,6 +43,7 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = {
 { 1, "Nearest &Iso View\tF2",               MNU_NEAREST_ISO,    F(2),   mView },
 { 1,  NULL,                                 0,                          NULL  },
 { 1, "Show Text &Window\tTab",              MNU_SHOW_TEXT_WND,  '\t',   mView },
+{ 1, "Show &Toolbar",                       MNU_SHOW_TOOLBAR,   0,      mView },
 { 1,  NULL,                                 0,                          NULL  },
 { 1, "Dimensions in &Inches",               MNU_UNITS_INCHES,   0,      mView },
 { 1, "Dimensions in &Millimeters",          MNU_UNITS_MM,       0,      mView },
@@ -395,6 +396,12 @@ void GraphicsWindow::MenuView(int id) {
             SS.GW.EnsureValidActives();
             break;
 
+        case MNU_SHOW_TOOLBAR:
+            SS.showToolbar = !SS.showToolbar;
+            SS.GW.EnsureValidActives();
+            PaintGraphics();
+            break;
+
         case MNU_UNITS_MM:
             SS.viewUnits = SolveSpace::UNIT_MM;
             SS.later.showTW = true;
@@ -475,6 +482,8 @@ void GraphicsWindow::EnsureValidActives(void) {
     ShowTextWindow(SS.GW.showTextWindow);
     CheckMenuById(MNU_SHOW_TEXT_WND, SS.GW.showTextWindow);
 
+    CheckMenuById(MNU_SHOW_TOOLBAR, SS.showToolbar);
+
     if(change) SS.later.showTW = true;
 }
 
diff --git a/icons/perpendicular.png b/icons/perpendicular.png
index 8782fd5..b93d8b3 100644
Binary files a/icons/perpendicular.png and b/icons/perpendicular.png differ
diff --git a/png2c.pl b/png2c.pl
new file mode 100644
index 0000000..fa3a3a1
--- /dev/null
+++ b/png2c.pl
@@ -0,0 +1,35 @@
+#!/usr/bin/perl
+
+use GD;
+
+for $file (<icons/*.png>) {
+   
+    $file =~ m#.*/(.*)\.png#;
+    $base = "Icon_$1";
+    $base =~ y/-/_/;
+
+    open(PNG, $file) or die "$file: $!\n";
+    $img = newFromPng GD::Image(\*PNG) or die;
+    $img->trueColor(1);
+
+    close PNG;
+
+    ($width, $height) = $img->getBounds();
+    die "$file: $width, $height"  if ($width != 24) or ($height != 24);
+
+    print "unsigned char $base\[24*24*3] = {\n";
+
+    for($y = 0; $y < 24; $y++) {
+        for($x = 0; $x < 24; $x++) {
+            $index = $img->getPixel($x, 23-$y);
+            ($r, $g, $b) = $img->rgb($index);
+            if($r + $g + $b < 11) {
+                ($r, $g, $b) = (30, 30, 30);
+            }
+            printf "    0x%02x, 0x%02x, 0x%02x,\n", $r, $g, $b;
+        }
+    }
+
+    print "};\n\n";
+
+}
diff --git a/solvespace.cpp b/solvespace.cpp
index 52b73be..656a126 100644
--- a/solvespace.cpp
+++ b/solvespace.cpp
@@ -53,6 +53,8 @@ void SolveSpace::Init(char *cmdLine) {
     exportOffset = CnfThawFloat(0.0f, "ExportOffset");
     // Draw back faces of triangles (when mesh is leaky/self-intersecting)
     drawBackFaces = CnfThawDWORD(1, "DrawBackFaces");
+    // Show toolbar in the graphics window
+    showToolbar = CnfThawDWORD(1, "ShowToolbar");
     // Recent files menus
     for(i = 0; i < MAX_RECENT; i++) {
         char name[100];
@@ -115,6 +117,8 @@ void SolveSpace::Exit(void) {
     CnfFreezeFloat(exportOffset, "ExportOffset");
     // Draw back faces of triangles (when mesh is leaky/self-intersecting)
     CnfFreezeDWORD(drawBackFaces, "DrawBackFaces");
+    // Show toolbar in the graphics window
+    CnfFreezeDWORD(showToolbar, "ShowToolbar");
 
     ExitNow();
 }
diff --git a/solvespace.h b/solvespace.h
index 468021c..28896e9 100644
--- a/solvespace.h
+++ b/solvespace.h
@@ -99,8 +99,12 @@ void dbp(char *str, ...);
 void SetWindowTitle(char *str);
 void Message(char *str, ...);
 void Error(char *str, ...);
+void SetTimerFor(int milliseconds);
 void ExitNow(void);
 
+void DrawWithBitmapFont(char *str);
+void GetBitmapFontExtent(char *str, int *w, int *h);
+
 void CnfFreezeString(char *str, char *name);
 void CnfFreezeDWORD(DWORD v, char *name);
 void CnfFreezeFloat(float v, char *name);
@@ -372,6 +376,7 @@ public:
     float   exportScale;
     float   exportOffset;
     int     drawBackFaces;
+    int     showToolbar;
 
     int CircleSides(double r);
     typedef enum {
diff --git a/toolbar.cpp b/toolbar.cpp
new file mode 100644
index 0000000..1ff8edb
--- /dev/null
+++ b/toolbar.cpp
@@ -0,0 +1,263 @@
+#include "solvespace.h"
+#include "obj/icons.h"
+
+BYTE SPACER[1];
+static const struct {
+    BYTE        *image;
+    int          menu;
+    char        *tip;
+} Toolbar[] = {
+    { Icon_line,            GraphicsWindow::MNU_LINE_SEGMENT,   "Sketch line segment"                               },
+    { Icon_rectangle,       GraphicsWindow::MNU_RECTANGLE,      "Sketch rectangle"                                  },
+    { Icon_circle,          GraphicsWindow::MNU_CIRCLE,         "Sketch circle"                                     },
+    { Icon_arc,             GraphicsWindow::MNU_ARC,            "Sketch arc, or tangent arc at selected point"      },
+    { Icon_bezier,          GraphicsWindow::MNU_CUBIC,          "Sketch cubic Bezier section"                       },
+    { Icon_point,           GraphicsWindow::MNU_DATUM_POINT,    "Sketch datum point"                                },
+    { Icon_construction,    GraphicsWindow::MNU_CONSTRUCTION,   "Toggle construction"                               },
+    { Icon_trim,            GraphicsWindow::MNU_CONSTRUCTION,   "Split lines / curves where they intersect"         },
+    { SPACER  },
+
+    { Icon_length,          GraphicsWindow::MNU_DISTANCE_DIA,   "Constrain distance / diameter / length"            },
+    { Icon_angle,           GraphicsWindow::MNU_ANGLE,          "Constrain angle"                                   },
+    { Icon_horiz,           GraphicsWindow::MNU_HORIZONTAL,     "Constrain to be horizontal"                        },
+    { Icon_vert,            GraphicsWindow::MNU_VERTICAL,       "Constrain to be vertical"                          },
+    { Icon_parallel,        GraphicsWindow::MNU_PARALLEL,       "Constrain to be parallel or tangent"               },
+    { Icon_perpendicular,   GraphicsWindow::MNU_PERPENDICULAR,  "Constrain to be perpendicular"                     },
+    { Icon_pointonx,        GraphicsWindow::MNU_ON_ENTITY,      "Constrain point on line / curve / plane / face"    },
+    { Icon_symmetric,       GraphicsWindow::MNU_SYMMETRIC,      "Constrain symmetric"                               },
+    { Icon_ref,             GraphicsWindow::MNU_REFERENCE,      "Toggle reference dimension"                        },
+    { SPACER  },
+
+    { Icon_extrude,         GraphicsWindow::MNU_GROUP_EXTRUDE,  "New group extruding active sketch"                 },
+    { Icon_sketch_in_plane, GraphicsWindow::MNU_GROUP_WRKPL,    "New group in new workplane (thru given entities)"  },
+    { Icon_sketch_in_3d,    GraphicsWindow::MNU_GROUP_3D,       "New group in 3d"                                   },
+    { Icon_assemble,        GraphicsWindow::MNU_GROUP_IMPORT,   "New group importing / assembling file"             },
+    { SPACER  },
+
+    { Icon_in3d,            GraphicsWindow::MNU_FREE_IN_3D,     "Sketch / constrain in 3d"                          },
+    { Icon_ontoworkplane,   GraphicsWindow::MNU_SEL_WORKPLANE,  "Sketch / constrain in workplane"                   },
+    { NULL  },
+};
+
+void GraphicsWindow::ToolbarDraw(void) {
+    ToolbarDrawOrHitTest(0, 0, true, NULL);
+}
+
+bool GraphicsWindow::ToolbarMouseMoved(int x, int y) {
+    x += ((int)width/2);
+    y += ((int)height/2);
+   
+    int nh;
+    bool withinToolbar = ToolbarDrawOrHitTest(x, y, false, &nh);
+    if(!withinToolbar) nh = 0;
+
+    if(nh != toolbarTooltipped) {
+        // Don't let the tool tip move around if the mouse moves within the
+        // same item.
+        toolbarMouseX = x;
+        toolbarMouseY = y;
+        toolbarTooltipped = 0;
+    }
+
+    if(nh != toolbarHovered) {
+        toolbarHovered = nh;
+        SetTimerFor(1000);
+        PaintGraphics();
+    }
+    // So if we moved off the toolbar, then toolbarHovered is now equal to
+    // zero, so it doesn't matter if the tool tip timer expires. And if
+    // we moved from one item to another, we reset the timer, so also okay.
+    return withinToolbar;
+}
+
+bool GraphicsWindow::ToolbarMouseDown(int x, int y) {
+    x += ((int)width/2);
+    y += ((int)height/2);
+   
+    int nh;
+    bool withinToolbar = ToolbarDrawOrHitTest(x, y, false, &nh);
+    if(withinToolbar) {
+        for(int i = 0; SS.GW.menu[i].level >= 0; i++) {
+            if(nh == SS.GW.menu[i].id) {
+                (SS.GW.menu[i].fn)((GraphicsWindow::MenuId)SS.GW.menu[i].id);
+                break;
+            }
+        }
+    }
+    return withinToolbar;
+}
+
+bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my,
+                                          bool paint, int *menu)
+{
+    int i;
+    int x = 17, y = (int)(height - 52);
+
+    int fudge = 8;
+    int h = 32*12 + 3*16 + fudge;
+    int aleft = 0, aright = 66, atop = y+16+fudge/2, abot = y+16-h;
+
+    bool withinToolbar =
+        (mx >= aleft && mx <= aright && my <= atop && my >= abot);
+
+    if(!paint && !withinToolbar) {
+        // This gets called every MouseMove event, so return quickly.
+        return false;
+    }
+    
+    if(paint) {
+        glMatrixMode(GL_MODELVIEW);
+        glLoadIdentity();
+        glMatrixMode(GL_PROJECTION);
+        glLoadIdentity();
+        glTranslated(-1, -1, 0);
+        glScaled(2.0/width, 2.0/height, 0);
+        glDisable(GL_LIGHTING);
+
+        double c = 30.0/255;
+        glColor4d(c, c, c, 1.0);
+        glBegin(GL_QUADS);
+            glVertex2d(aleft,  atop);
+            glVertex2d(aleft,  abot);
+            glVertex2d(aright, abot);
+            glVertex2d(aright, atop);
+        glEnd();
+    }
+
+    struct {
+        bool show;
+        char *str;
+    } toolTip = { false, NULL };
+
+    bool leftpos = true;
+    for(i = 0; Toolbar[i].image; i++) {
+        if(Toolbar[i].image == SPACER) {
+            if(!leftpos) {
+                leftpos = true;
+                y -= 32;
+                x -= 32;
+            }
+            y -= 16;
+
+            if(paint) {
+                // Draw a separator bar in a slightly different color.
+                int divw = 30, divh = 2;
+                glColor4d(0.17, 0.17, 0.17, 1);
+                x += 16;
+                y += 24;
+                glBegin(GL_QUADS);
+                    glVertex2d(x+divw, y+divh);
+                    glVertex2d(x+divw, y-divh);
+                    glVertex2d(x-divw, y-divh);
+                    glVertex2d(x-divw, y+divh);
+                glEnd(); 
+                x -= 16;
+                y -= 24;
+            }
+
+            continue;
+        }
+
+        if(paint) {
+            glRasterPos2i(x - 12, y - 12);
+            glDrawPixels(24, 24, GL_RGB, GL_UNSIGNED_BYTE, Toolbar[i].image);
+
+            if(toolbarHovered == Toolbar[i].menu) {
+                // Highlight the hovered or pending item.
+                glColor4d(1, 1, 0, 0.3);
+                int boxhw = 15;
+                glBegin(GL_QUADS);
+                    glVertex2d(x+boxhw, y+boxhw);
+                    glVertex2d(x+boxhw, y-boxhw);
+                    glVertex2d(x-boxhw, y-boxhw);
+                    glVertex2d(x-boxhw, y+boxhw);
+                glEnd(); 
+            }
+
+            if(toolbarTooltipped == Toolbar[i].menu) {
+                // Display the tool tip for this item; postpone till later
+                // so that no one draws over us. Don't need position since
+                // that's just wherever the mouse is.
+                toolTip.show = true;
+                toolTip.str = Toolbar[i].tip;
+            }
+        } else {
+            int boxhw = 16;
+            if(mx < (x+boxhw) && mx > (x - boxhw) &&
+               my < (y+boxhw) && my > (y - boxhw))
+            {
+                if(menu) *menu = Toolbar[i].menu;
+            }
+        }
+
+        if(leftpos) {
+            x += 32;
+            leftpos = false;
+        } else {
+            x -= 32;
+            y -= 32;
+            leftpos = true;
+        }
+    }
+
+    if(paint) {
+        // Do this last so that nothing can draw over it.
+        if(toolTip.show) {
+            char str[1024];
+            if(strlen(toolTip.str) >= 200) oops();
+            strcpy(str, toolTip.str);
+
+            for(i = 0; SS.GW.menu[i].level >= 0; i++) {
+                if(toolbarTooltipped == SS.GW.menu[i].id) {
+                    int accel = SS.GW.menu[i].accel;
+                    int ac = accel & 0xff;
+                    if(isalnum(ac) || ac == '[') {
+                        char *s = str+strlen(str);
+                        if(accel & 0x100) {
+                            sprintf(s, " (Shift+%c)", ac);
+                        } else if((accel & ~0xff) == 0) {
+                            sprintf(s, " (%c)", ac);
+                        }
+                    }
+                    break;
+                }
+            }
+
+            int tw, th;
+            GetBitmapFontExtent(str, &tw, &th);
+            tw += 10;
+            th += 2;
+            
+            double ox = toolbarMouseX + 3, oy = toolbarMouseY + 3;
+            glColor4d(1.0, 1.0, 0.6, 1.0);
+            glBegin(GL_QUADS);
+                glVertex2d(ox, oy);
+                glVertex2d(ox+tw, oy);
+                glVertex2d(ox+tw, oy+th);
+                glVertex2d(ox, oy+th);
+            glEnd();
+            glColor4d(0.0, 0.0, 0.0, 1.0);
+            glBegin(GL_LINE_LOOP);
+                glVertex2d(ox, oy);
+                glVertex2d(ox+tw, oy);
+                glVertex2d(ox+tw, oy+th);
+                glVertex2d(ox, oy+th);
+            glEnd();
+
+            glColor4d(0, 0, 0, 1);
+            glPushMatrix();
+                glRasterPos2d(ox+6, oy+6);
+                DrawWithBitmapFont(str);
+            glPopMatrix();
+        }
+        glxDepthRangeLockToFront(false);
+    }
+
+    return withinToolbar;
+}
+
+void GraphicsWindow::TimerCallback(void) {
+    SS.GW.toolbarTooltipped = SS.GW.toolbarHovered;
+    PaintGraphics();
+}
+
diff --git a/ui.h b/ui.h
index 157b94f..c2f4cd2 100644
--- a/ui.h
+++ b/ui.h
@@ -187,6 +187,7 @@ public:
         MNU_NEAREST_ORTHO,
         MNU_NEAREST_ISO,
         MNU_SHOW_TEXT_WND,
+        MNU_SHOW_TOOLBAR,
         MNU_UNITS_INCHES,
         MNU_UNITS_MM,
         // Edit
@@ -371,6 +372,16 @@ public:
 
     void ClearSuper(void);
 
+    // The toolbar, in toolbar.cpp
+    bool ToolbarDrawOrHitTest(int x, int y, bool paint, int *menu);
+    void ToolbarDraw(void);
+    bool ToolbarMouseMoved(int x, int y);
+    bool ToolbarMouseDown(int x, int y);
+    static void TimerCallback(void);
+    int toolbarHovered;
+    int toolbarTooltipped;
+    int toolbarMouseX, toolbarMouseY;
+
     // This sets what gets displayed.
     bool    showWorkplanes;
     bool    showNormals;
@@ -395,6 +406,7 @@ public:
     void MouseLeftDoubleClick(double x, double y);
     void MouseMiddleOrRightDown(double x, double y);
     void MouseScroll(double x, double y, int delta);
+    void MouseLeave(void);
     void EditControlDone(char *s);
 };
 
diff --git a/win32/w32main.cpp b/win32/w32main.cpp
index b997bdd..02b0852 100644
--- a/win32/w32main.cpp
+++ b/win32/w32main.cpp
@@ -22,6 +22,9 @@
 #define EDIT_WIDTH  220
 #define EDIT_HEIGHT 21
 
+// The list representing glyph with ASCII code zero, for bitmap fonts
+#define BITMAP_GLYPH_BASE 1000
+
 HINSTANCE Instance;
 
 HWND TextWnd;
@@ -95,6 +98,27 @@ void Message(char *str, ...)
     va_end(f);
 }
 
+void CALLBACK TimerCallback(HWND hwnd, UINT msg, UINT_PTR id, DWORD time)
+{
+    SS.GW.TimerCallback();
+}
+void SetTimerFor(int milliseconds)
+{
+    SetTimer(GraphicsWnd, 1, milliseconds, TimerCallback);
+}
+
+void DrawWithBitmapFont(char *str)
+{
+    // These lists were created in CreateGlContext
+    glListBase(BITMAP_GLYPH_BASE); 
+    glCallLists(strlen(str), GL_UNSIGNED_BYTE, str);
+}
+void GetBitmapFontExtent(char *str, int *w, int *h)
+{
+    // Easy since that's a fixed-width font for now.
+    *h = TEXT_HEIGHT;
+    *w = TEXT_WIDTH*strlen(str);
+}
 
 void OpenWebsite(char *url) {
     ShellExecute(GraphicsWnd, "open", url, NULL, NULL, SW_SHOWNORMAL);
@@ -598,6 +622,10 @@ static void CreateGlContext(void)
 
     GraphicsHpgl = wglCreateContext(hdc); 
     wglMakeCurrent(hdc, GraphicsHpgl); 
+
+    // Create a bitmap font in a display list, for DrawWithBitmapFont().
+    SelectObject(hdc, FixedFont);
+    wglUseFontBitmaps(hdc, 0, 255, BITMAP_GLYPH_BASE); 
 }
 
 void InvalidateGraphics(void)
@@ -702,6 +730,10 @@ LRESULT CALLBACK GraphicsWndProc(HWND hwnd, UINT msg, WPARAM wParam,
             break;
         }
 
+        case WM_MOUSELEAVE:
+            SS.GW.MouseLeave();
+            break;
+
         case WM_MOUSEMOVE:
         case WM_LBUTTONDOWN:
         case WM_LBUTTONUP:
@@ -711,6 +743,15 @@ LRESULT CALLBACK GraphicsWndProc(HWND hwnd, UINT msg, WPARAM wParam,
             int x = LOWORD(lParam);
             int y = HIWORD(lParam);
 
+            // We need this in order to get the WM_MOUSELEAVE
+            TRACKMOUSEEVENT tme;
+            ZERO(&tme);
+            tme.cbSize = sizeof(tme);
+            tme.dwFlags = TME_LEAVE;
+            tme.hwndTrack = GraphicsWnd;
+            TrackMouseEvent(&tme);
+
+            // Convert to xy (vs. ij) style coordinates, with (0, 0) at center
             RECT r;
             GetClientRect(GraphicsWnd, &r);
             x = x - (r.right - r.left)/2;