浏览代码

getting closer

David Rose 20 年之前
父节点
当前提交
0f5505382e

+ 2 - 2
pandatool/src/gtk-stats/Sources.pp

@@ -2,9 +2,9 @@
 #define USE_PACKAGES net gtk
 
 #begin bin_target
-  // We rename TARGET to gtk-pstats on Windows, so it won't compete
+  // We rename TARGET to pstats-gtk on Windows, so it won't compete
   // with Windows-native pstats.
-  #define TARGET $[if $[WINDOWS_PLATFORM],gtk-pstats,pstats]
+  #define TARGET $[if $[WINDOWS_PLATFORM],pstats-gtk,pstats]
   #define LOCAL_LIBS \
     progbase pstatserver
   #define OTHER_LIBS \

+ 180 - 38
pandatool/src/gtk-stats/gtkStatsGraph.cxx

@@ -49,33 +49,70 @@ GtkStatsGraph(GtkStatsMonitor *monitor, int thread_index) :
   _parent_window = NULL;
   _window = NULL;
   _graph_window = NULL;
-  //  _sizewe_cursor = LoadCursor(NULL, IDC_SIZEWE);
-  //  _hand_cursor = LoadCursor(NULL, IDC_HAND);
+  _scale_area = NULL;
+
+  GtkWidget *parent_window = monitor->get_window();
+
+  GdkDisplay *display = gdk_drawable_get_display(parent_window->window);
+  _hand_cursor = gdk_cursor_new_for_display(display, GDK_HAND2);
+
   _pixmap = 0;
   _pixmap_gc = 0;
 
-  _graph_left = 0;
-  _graph_top = 0;
   _pixmap_xsize = 0;
   _pixmap_ysize = 0;
 
   _window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
-  GtkWidget *parent_window = monitor->get_window();
 
   gtk_window_set_transient_for(GTK_WINDOW(_window), GTK_WINDOW(parent_window));
   gtk_window_set_destroy_with_parent(GTK_WINDOW(_window), TRUE);
+  gtk_widget_add_events(_window, 
+			GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+			GDK_POINTER_MOTION_MASK);
   g_signal_connect(G_OBJECT(_window), "delete_event",
 		   G_CALLBACK(window_delete_event), this);
   g_signal_connect(G_OBJECT(_window), "destroy",
 		   G_CALLBACK(window_destroy), this);
+  g_signal_connect(G_OBJECT(_window), "button_press_event",  
+		   G_CALLBACK(button_press_event_callback), this);
+  g_signal_connect(G_OBJECT(_window), "button_release_event",  
+		   G_CALLBACK(button_release_event_callback), this);
+  g_signal_connect(G_OBJECT(_window), "motion_notify_event",  
+		   G_CALLBACK(motion_notify_event_callback), this);
 
   _graph_window = gtk_drawing_area_new();
+  gtk_widget_add_events(_graph_window, 
+			GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+			GDK_POINTER_MOTION_MASK);
   g_signal_connect(G_OBJECT(_graph_window), "expose_event",  
-		   G_CALLBACK(expose_event_callback), this);
+		   G_CALLBACK(graph_expose_callback), this);
   g_signal_connect(G_OBJECT(_graph_window), "configure_event",  
-		   G_CALLBACK(configure_event_callback), this);
-
-  gtk_widget_set_size_request(_graph_window, 400, 100);
+		   G_CALLBACK(configure_graph_callback), this);
+  g_signal_connect(G_OBJECT(_graph_window), "button_press_event",  
+		   G_CALLBACK(button_press_event_callback), this);
+  g_signal_connect(G_OBJECT(_graph_window), "button_release_event",  
+		   G_CALLBACK(button_release_event_callback), this);
+  g_signal_connect(G_OBJECT(_graph_window), "motion_notify_event",  
+		   G_CALLBACK(motion_notify_event_callback), this);
+
+  // A Frame to hold the graph.
+  GtkWidget *graph_frame = gtk_frame_new(NULL);
+  gtk_frame_set_shadow_type(GTK_FRAME(graph_frame), GTK_SHADOW_IN);
+  gtk_container_add(GTK_CONTAINER(graph_frame), _graph_window);
+
+  // An HBox to hold the graph's frame, and the scale legend to the
+  // right of it.
+  _graph_hbox = gtk_hbox_new(FALSE, 0);
+  gtk_box_pack_start(GTK_BOX(_graph_hbox), graph_frame,
+		     TRUE, TRUE, 0);
+
+  // An HPaned to hold the label stack and the graph hbox.
+  _hpaned = gtk_hpaned_new();
+  gtk_container_add(GTK_CONTAINER(_window), _hpaned);
+  gtk_container_set_border_width(GTK_CONTAINER(_window), 8);
+
+  gtk_paned_pack1(GTK_PANED(_hpaned), _label_stack.get_widget(), TRUE, TRUE);
+  gtk_paned_pack2(GTK_PANED(_hpaned), _graph_hbox, TRUE, TRUE);
 
   _drag_mode = DM_none;
   _potential_drag_mode = DM_none;
@@ -106,15 +143,7 @@ GtkStatsGraph::
     g_object_unref(gc);
   }
 
-  if (_graph_window != NULL) {
-    gtk_widget_destroy(_graph_window);
-    _graph_window = NULL;
-  }
-
-  if (_window != NULL) {
-    gtk_widget_destroy(_window);
-    _window = NULL;
-  }
+  gtk_widget_destroy(_window);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -208,10 +237,10 @@ set_pause(bool pause) {
 ////////////////////////////////////////////////////////////////////
 void GtkStatsGraph::
 user_guide_bars_changed() {
-  /*
-  InvalidateRect(_window, NULL, TRUE);
-  InvalidateRect(_graph_window, NULL, TRUE);
-  */
+  if (_scale_area != NULL) {
+    gtk_widget_queue_draw(_scale_area);
+  }
+  gtk_widget_queue_draw(_graph_window);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -267,22 +296,27 @@ get_collector_gc(int collector_index) {
   return gc;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GtkStatsGraph::additional_graph_window_paint
+//       Access: Protected, Virtual
+//  Description: This is called during the servicing of expose_event;
+//               it gives a derived class opportunity to do some
+//               further painting into the graph window.
+////////////////////////////////////////////////////////////////////
+void GtkStatsGraph::
+additional_graph_window_paint() {
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GtkStatsGraph::consider_drag_start
 //       Access: Protected, Virtual
-//  Description: Based on the mouse position within the window's
-//               client area, look for draggable things the mouse
-//               might be hovering over and return the apprioprate
-//               DragMode enum or DM_none if nothing is indicated.
+//  Description: Based on the mouse position within the graph window,
+//               look for draggable things the mouse might be hovering
+//               over and return the appropriate DragMode enum or
+//               DM_none if nothing is indicated.
 ////////////////////////////////////////////////////////////////////
 GtkStatsGraph::DragMode GtkStatsGraph::
-consider_drag_start(int mouse_x, int mouse_y, int width, int height) {
-  if (mouse_x >= _left_margin - 2 && mouse_x <= _left_margin + 2) {
-    return DM_left_margin;
-  } else if (mouse_x >= width - _right_margin - 2 && mouse_x <= width - _right_margin + 2) {
-    return DM_right_margin;
-  }
-
+consider_drag_start(int graph_x, int graph_y) {
   return DM_none;
 }
 
@@ -298,6 +332,58 @@ set_drag_mode(GtkStatsGraph::DragMode drag_mode) {
   _drag_mode = drag_mode;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GtkStatsGraph::handle_button_press
+//       Access: Protected, Virtual
+//  Description: Called when the mouse button is depressed within the
+//               window, or any nested window.
+////////////////////////////////////////////////////////////////////
+gboolean GtkStatsGraph::
+handle_button_press(GtkWidget *widget, int graph_x, int graph_y) {
+  if (_potential_drag_mode != DM_none) {
+    set_drag_mode(_potential_drag_mode);
+    _drag_start_x = graph_x;
+    _drag_start_y = graph_y;
+    //    SetCapture(_window);
+  }
+  return TRUE;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GtkStatsGraph::handle_button_release
+//       Access: Protected, Virtual
+//  Description: Called when the mouse button is released within the
+//               window, or any nested window.
+////////////////////////////////////////////////////////////////////
+gboolean GtkStatsGraph::
+handle_button_release(GtkWidget *widget, int graph_x, int graph_y) {
+  set_drag_mode(DM_none);
+  //  ReleaseCapture();
+
+  return handle_motion(widget, graph_x, graph_y);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GtkStatsGraph::handle_motion
+//       Access: Protected, Virtual, Static
+//  Description: Called when the mouse is moved within the
+//               window, or any nested window.
+////////////////////////////////////////////////////////////////////
+gboolean GtkStatsGraph::
+handle_motion(GtkWidget *widget, int graph_x, int graph_y) {
+  _potential_drag_mode = consider_drag_start(graph_x, graph_y);
+
+  if (_potential_drag_mode == DM_guide_bar ||
+      _drag_mode == DM_guide_bar) {
+    gdk_window_set_cursor(_window->window, _hand_cursor);
+
+  } else {
+    gdk_window_set_cursor(_window->window, NULL);
+  }
+
+  return TRUE;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GtkStatsGraph::setup_pixmap
 //       Access: Private
@@ -347,7 +433,7 @@ window_delete_event(GtkWidget *widget, GdkEvent *event, gpointer data) {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: GtkStatsGraph::window_delete_event
+//     Function: GtkStatsGraph::window_destroy
 //       Access: Private, Static
 //  Description: Callback when the window is destroyed by the system
 //               (or by delete_event).
@@ -359,12 +445,12 @@ window_destroy(GtkWidget *widget, gpointer data) {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: GtkStatsGraph::expose_event_callback
+//     Function: GtkStatsGraph::graph_expose_callback
 //       Access: Private, Static
 //  Description: Fills in the graph window.
 ////////////////////////////////////////////////////////////////////
 gboolean GtkStatsGraph::
-expose_event_callback(GtkWidget *widget, GdkEventExpose *event, gpointer data) {
+graph_expose_callback(GtkWidget *widget, GdkEventExpose *event, gpointer data) {
   GtkStatsGraph *self = (GtkStatsGraph *)data;
 
   if (self->_pixmap != NULL) {
@@ -373,17 +459,19 @@ expose_event_callback(GtkWidget *widget, GdkEventExpose *event, gpointer data) {
 		      self->_pixmap, 0, 0, 0, 0,
 		      self->_pixmap_xsize, self->_pixmap_ysize);
   }
+
+  self->additional_graph_window_paint();
     
   return TRUE;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: GtkStatsGraph::configure_event_callback
+//     Function: GtkStatsGraph::configure_graph_callback
 //       Access: Private, Static
 //  Description: Changes the size of the graph window
 ////////////////////////////////////////////////////////////////////
 gboolean GtkStatsGraph::
-configure_event_callback(GtkWidget *widget, GdkEventConfigure *event, 
+configure_graph_callback(GtkWidget *widget, GdkEventConfigure *event, 
 			 gpointer data) {
   GtkStatsGraph *self = (GtkStatsGraph *)data;
 
@@ -393,3 +481,57 @@ configure_event_callback(GtkWidget *widget, GdkEventConfigure *event,
     
   return TRUE;
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: GtkStatsGraph::button_press_event_callback
+//       Access: Private, Static
+//  Description: Called when the mouse button is depressed within the
+//               graph window or main window.
+////////////////////////////////////////////////////////////////////
+gboolean GtkStatsGraph::
+button_press_event_callback(GtkWidget *widget, GdkEventButton *event, 
+			    gpointer data) {
+  GtkStatsGraph *self = (GtkStatsGraph *)data;
+  int graph_x, graph_y;
+  gtk_widget_translate_coordinates(widget, self->_graph_window,
+				   (int)event->x, (int)event->y,
+				   &graph_x, &graph_y);
+
+  return self->handle_button_press(widget, graph_x, graph_y);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GtkStatsGraph::button_release_event_callback
+//       Access: Private, Static
+//  Description: Called when the mouse button is released within the
+//               graph window or main window.
+////////////////////////////////////////////////////////////////////
+gboolean GtkStatsGraph::
+button_release_event_callback(GtkWidget *widget, GdkEventButton *event, 
+			      gpointer data) {
+  GtkStatsGraph *self = (GtkStatsGraph *)data;
+  int graph_x, graph_y;
+  gtk_widget_translate_coordinates(widget, self->_graph_window,
+				   (int)event->x, (int)event->y,
+				   &graph_x, &graph_y);
+
+  return self->handle_button_release(widget, graph_x, graph_y);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GtkStatsGraph::motion_notify_event_callback
+//       Access: Private, Static
+//  Description: Called when the mouse is moved within the
+//               graph window or main window.
+////////////////////////////////////////////////////////////////////
+gboolean GtkStatsGraph::
+motion_notify_event_callback(GtkWidget *widget, GdkEventMotion *event, 
+			     gpointer data) {
+  GtkStatsGraph *self = (GtkStatsGraph *)data;
+  int graph_x, graph_y;
+  gtk_widget_translate_coordinates(widget, self->_graph_window,
+				   (int)event->x, (int)event->y,
+				   &graph_x, &graph_y);
+
+  return self->handle_motion(widget, graph_x, graph_y);
+}

+ 23 - 19
pandatool/src/gtk-stats/gtkStatsGraph.h

@@ -39,8 +39,6 @@ public:
   enum DragMode {
     DM_none,
     DM_scale,
-    DM_left_margin,
-    DM_right_margin,
     DM_guide_bar,
     DM_new_guide_bar,
     DM_sizing,
@@ -68,14 +66,14 @@ protected:
   void close();
   GdkGC *get_collector_gc(int collector_index);
 
-  /*
-  virtual void additional_window_paint(HDC hdc);
-  virtual void additional_graph_window_paint(HDC hdc);
-  */
-  virtual DragMode consider_drag_start(int mouse_x, int mouse_y, 
-                                       int width, int height);
+  virtual void additional_graph_window_paint();
+  virtual DragMode consider_drag_start(int graph_x, int graph_y);
   virtual void set_drag_mode(DragMode drag_mode);
 
+  virtual gboolean handle_button_press(GtkWidget *widget, int graph_x, int graph_y);
+  virtual gboolean handle_button_release(GtkWidget *widget, int graph_x, int graph_y);
+  virtual gboolean handle_motion(GtkWidget *widget, int graph_x, int graph_y);
+
 protected:
   // Table of GC's for our various collectors.
   typedef pmap<int, GdkGC *> Brushes;
@@ -86,20 +84,16 @@ protected:
   GtkWidget *_parent_window;
   GtkWidget *_window;
   GtkWidget *_graph_window;
+  GtkWidget *_graph_hbox;
+  GtkWidget *_hpaned;
+  GtkWidget *_scale_area;
   GtkStatsLabelStack _label_stack;
 
-  /*
-  HCURSOR _sizewe_cursor;
-  HCURSOR _hand_cursor;
-  */
+  GdkCursor *_hand_cursor;
 
   GdkPixmap *_pixmap;
   GdkGC *_pixmap_gc;
-
-  int _graph_left, _graph_top;
   int _pixmap_xsize, _pixmap_ysize;
-  int _left_margin, _right_margin;
-  int _top_margin, _bottom_margin;
 
   /*
   COLORREF _dark_color;
@@ -127,15 +121,25 @@ protected:
 private:
   void setup_pixmap(int xsize, int ysize);
   void release_pixmap();
-  void create_graph_window();
 
   static gboolean window_delete_event(GtkWidget *widget, GdkEvent *event, 
 				      gpointer data);
   static void window_destroy(GtkWidget *widget, gpointer data);
-  static gboolean expose_event_callback(GtkWidget *widget, 
+  static gboolean graph_expose_callback(GtkWidget *widget, 
 					GdkEventExpose *event, gpointer data);
-  static gboolean configure_event_callback(GtkWidget *widget, 
+  static gboolean configure_graph_callback(GtkWidget *widget, 
 					   GdkEventConfigure *event, gpointer data);
+
+protected:
+  static gboolean button_press_event_callback(GtkWidget *widget, 
+					      GdkEventButton *event, 
+					      gpointer data);
+  static gboolean button_release_event_callback(GtkWidget *widget, 
+						GdkEventButton *event, 
+						gpointer data);
+  static gboolean motion_notify_event_callback(GtkWidget *widget, 
+					       GdkEventMotion *event, 
+					       gpointer data);
 };
 
 #endif

+ 2 - 1
pandatool/src/gtk-stats/gtkStatsLabel.cxx

@@ -223,7 +223,8 @@ leave_notify_event_callback(GtkWidget *widget, GdkEventCrossing *event,
 ////////////////////////////////////////////////////////////////////
 //     Function: GtkStatsLabel::button_press_event_callback
 //       Access: Private, Static
-//  Description: Called when the mouse leaves the label region
+//  Description: Called when the mouse button is depressed within the
+//               label.
 ////////////////////////////////////////////////////////////////////
 gboolean GtkStatsLabel::
 button_press_event_callback(GtkWidget *widget, GdkEventButton *event, 

+ 1 - 1
pandatool/src/gtk-stats/gtkStatsMonitor.cxx

@@ -550,7 +550,7 @@ window_delete_event(GtkWidget *widget, GdkEvent *event, gpointer data) {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: GtkStatsMonitor::window_delete_event
+//     Function: GtkStatsMonitor::window_destroy
 //       Access: Private, Static
 //  Description: Callback when the window is destroyed by the system
 //               (or by delete_event).

+ 250 - 27
pandatool/src/gtk-stats/gtkStatsStripChart.cxx

@@ -41,11 +41,6 @@ GtkStatsStripChart(GtkStatsMonitor *monitor, int thread_index,
 {
   _brush_origin = 0;
 
-  _left_margin = 96;
-  _right_margin = 32;
-  _top_margin = 16;
-  _bottom_margin = 8;
-
   if (show_level) {
     // If it's a level-type graph, show the appropriate units.
     if (_unit_name.empty()) {
@@ -61,17 +56,25 @@ GtkStatsStripChart(GtkStatsMonitor *monitor, int thread_index,
 
   _smooth_check_box = 0;
 
-  GtkWidget *hbox = gtk_hbox_new(FALSE, 0);
-  gtk_box_pack_start(GTK_BOX(hbox), _label_stack.get_widget(),
+  // Add a DrawingArea widget to display all of the scale units.
+  _scale_area = gtk_drawing_area_new();
+  g_signal_connect(G_OBJECT(_scale_area), "expose_event",  
+		   G_CALLBACK(expose_event_callback), this);
+  gtk_box_pack_start(GTK_BOX(_graph_hbox), _scale_area,
 		     FALSE, FALSE, 0);
-  gtk_box_pack_start(GTK_BOX(hbox), _graph_window,
-		     TRUE, TRUE, 0);
+  gtk_widget_set_size_request(_scale_area, 40, 0);
+
 
-  gtk_container_add(GTK_CONTAINER(_window), hbox);
+  gtk_widget_set_size_request(_graph_window, 400, 100);
 
   gtk_widget_show_all(_window);  
   gtk_widget_show(_window);
 
+  // Allow the window to be resized as small as the user likes.  We
+  // have to do this after the window has been shown; otherwise, it
+  // will affect the window's initial size.
+  gtk_widget_set_size_request(_window, 0, 0);
+
   clear_region();
 }
 
@@ -238,6 +241,7 @@ set_vertical_scale(float value_height) {
   PStatStripChart::set_vertical_scale(value_height);
 
   gtk_widget_queue_draw(_graph_window);
+  gtk_widget_queue_draw(_scale_area);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -319,14 +323,13 @@ draw_slice(int x, int w, const PStatStripChart::FrameData &fdata) {
     if (overall_time > get_vertical_scale()) {
       // Off the top.  Go ahead and clamp it by hand, in case it's so
       // far off the top we'd overflow the 16-bit pixel value.
-      gdk_draw_rectangle(_pixmap, gc, TRUE, x, 0, w + 1, y);
-
+      gdk_draw_rectangle(_pixmap, gc, TRUE, x, 0, w, y);
       // And we can consider ourselves done now.
       return;
     }
 
     int top_y = height_to_pixel(overall_time);
-    gdk_draw_rectangle(_pixmap, gc, TRUE, x, top_y, w + 1, y);
+    gdk_draw_rectangle(_pixmap, gc, TRUE, x, top_y, w, y - top_y);
     y = top_y;
   }
 }
@@ -367,7 +370,7 @@ end_draw(int from_x, int to_x) {
   // Draw in the guide bars.
   int num_guide_bars = get_num_guide_bars();
   for (int i = 0; i < num_guide_bars; i++) {
-    draw_guide_bar(from_x, to_x, get_guide_bar(i));
+    draw_guide_bar(_pixmap, from_x, to_x, get_guide_bar(i));
   }
 
   GdkRectangle rect = {
@@ -376,20 +379,35 @@ end_draw(int from_x, int to_x) {
   gdk_window_invalidate_rect(_graph_window->window, &rect, FALSE);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GtkStatsStripChart::additional_graph_window_paint
+//       Access: Protected, Virtual
+//  Description: This is called during the servicing of expose_event;
+//               it gives a derived class opportunity to do some
+//               further painting into the graph window.
+////////////////////////////////////////////////////////////////////
+void GtkStatsStripChart::
+additional_graph_window_paint() {
+  int num_user_guide_bars = get_num_user_guide_bars();
+  for (int i = 0; i < num_user_guide_bars; i++) {
+    draw_guide_bar(_graph_window->window, 0, get_xsize(), get_user_guide_bar(i));
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GtkStatsStripChart::consider_drag_start
 //       Access: Protected, Virtual
-//  Description: Based on the mouse position within the window's
-//               client area, look for draggable things the mouse
-//               might be hovering over and return the apprioprate
-//               DragMode enum or DM_none if nothing is indicated.
+//  Description: Based on the mouse position within the graph window,
+//               look for draggable things the mouse might be hovering
+//               over and return the appropriate DragMode enum or
+//               DM_none if nothing is indicated.
 ////////////////////////////////////////////////////////////////////
 GtkStatsGraph::DragMode GtkStatsStripChart::
-consider_drag_start(int mouse_x, int mouse_y, int width, int height) {
-  if (mouse_x >= _graph_left && mouse_x < _graph_left + get_xsize()) {
-    if (mouse_y >= _graph_top && mouse_y < _graph_top + get_ysize()) {
+consider_drag_start(int graph_x, int graph_y) {
+  if (graph_x >= 0 && graph_x < get_xsize()) {
+    if (graph_y >= 0 && graph_y < get_ysize()) {
       // See if the mouse is over a user-defined guide bar.
-      int y = mouse_y - _graph_top;
+      int y = graph_y;
       float from_height = pixel_to_height(y + 2);
       float to_height = pixel_to_height(y - 2);
       _drag_guide_bar = find_user_guide_bar(from_height, to_height);
@@ -404,7 +422,7 @@ consider_drag_start(int mouse_x, int mouse_y, int width, int height) {
     }
   }
 
-  return GtkStatsGraph::consider_drag_start(mouse_x, mouse_y, width, height);
+  return GtkStatsGraph::consider_drag_start(graph_x, graph_y);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -420,8 +438,6 @@ set_drag_mode(GtkStatsGraph::DragMode drag_mode) {
 
   switch (_drag_mode) {
   case DM_scale:
-  case DM_left_margin:
-  case DM_right_margin:
   case DM_sizing:
     // Disable smoothing for these expensive operations.
     set_average_mode(false);
@@ -438,6 +454,110 @@ set_drag_mode(GtkStatsGraph::DragMode drag_mode) {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GtkStatsStripChart::handle_button_press
+//       Access: Protected, Virtual
+//  Description: Called when the mouse button is depressed within the
+//               graph window.
+////////////////////////////////////////////////////////////////////
+gboolean GtkStatsStripChart::
+handle_button_press(GtkWidget *widget, int graph_x, int graph_y) {
+  if (_potential_drag_mode == DM_none) {
+    set_drag_mode(DM_scale);
+    _drag_scale_start = pixel_to_height(graph_y);
+    //SetCapture(_graph_window);
+    return TRUE;
+
+  } else if (_potential_drag_mode == DM_guide_bar && _drag_guide_bar >= 0) {
+    set_drag_mode(DM_guide_bar);
+    _drag_start_y = graph_y;
+    //SetCapture(_graph_window);
+    return TRUE;
+  }
+
+  return GtkStatsGraph::handle_button_press(widget, graph_x, graph_y);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GtkStatsStripChart::handle_button_release
+//       Access: Protected, Virtual
+//  Description: Called when the mouse button is released within the
+//               graph window.
+////////////////////////////////////////////////////////////////////
+gboolean GtkStatsStripChart::
+handle_button_release(GtkWidget *widget, int graph_x, int graph_y) {
+  if (_drag_mode == DM_scale) {
+    set_drag_mode(DM_none);
+    //ReleaseCapture();
+    return handle_motion(widget, graph_x, graph_y);
+    
+  } else if (_drag_mode == DM_guide_bar) {
+    if (graph_y < 0 || graph_y >= get_ysize()) {
+      remove_user_guide_bar(_drag_guide_bar);
+    } else {
+      move_user_guide_bar(_drag_guide_bar, pixel_to_height(graph_y));
+    }
+    set_drag_mode(DM_none);
+    //ReleaseCapture();
+    return handle_motion(widget, graph_x, graph_y);
+  }
+
+  return GtkStatsGraph::handle_button_release(widget, graph_x, graph_y);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GtkStatsStripChart::ns_motion_notify_event_callback
+//       Access: Protected, Virtual
+//  Description: Called when the mouse is moved within the
+//               graph window.
+////////////////////////////////////////////////////////////////////
+gboolean GtkStatsStripChart::
+handle_motion(GtkWidget *widget, int graph_x, int graph_y) {
+  if (_drag_mode == DM_none && _potential_drag_mode == DM_none) {
+    // When the mouse is over a color bar, highlight it.
+    _label_stack.highlight_label(get_collector_under_pixel(graph_x, graph_y));
+
+    /*
+    // Now we want to get a WM_MOUSELEAVE when the mouse leaves the
+    // graph window.
+    TRACKMOUSEEVENT tme = {
+      sizeof(TRACKMOUSEEVENT),
+      TME_LEAVE,
+      _graph_window,
+      0
+    };
+    TrackMouseEvent(&tme);
+    */
+
+  } else {
+    // If the mouse is in some drag mode, stop highlighting.
+    _label_stack.highlight_label(-1);
+  }
+
+  if (_drag_mode == DM_scale) {
+    float ratio = 1.0f - ((float)graph_y / (float)get_ysize());
+    if (ratio > 0.0f) {
+      set_vertical_scale(_drag_scale_start / ratio);
+    }
+    return TRUE;
+
+  } else if (_drag_mode == DM_new_guide_bar) {
+    // We haven't created the new guide bar yet; we won't until the
+    // mouse comes within the graph's region.
+    if (graph_y >= 0 && graph_y < get_ysize()) {
+      set_drag_mode(DM_guide_bar);
+      _drag_guide_bar = add_user_guide_bar(pixel_to_height(graph_y));
+      return TRUE;
+    }
+
+  } else if (_drag_mode == DM_guide_bar) {
+    move_user_guide_bar(_drag_guide_bar, pixel_to_height(graph_y));
+    return TRUE;
+  }
+
+  return GtkStatsGraph::handle_motion(widget, graph_x, graph_y);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GtkStatsStripChart::draw_guide_bar
 //       Access: Private
@@ -445,7 +565,7 @@ set_drag_mode(GtkStatsGraph::DragMode drag_mode) {
 //               graph.
 ////////////////////////////////////////////////////////////////////
 void GtkStatsStripChart::
-draw_guide_bar(int from_x, int to_x, 
+draw_guide_bar(GdkDrawable *surface, int from_x, int to_x, 
                const PStatGraph::GuideBar &bar) {
   int y = height_to_pixel(bar._height);
 
@@ -464,6 +584,109 @@ draw_guide_bar(int from_x, int to_x,
       gdk_gc_set_rgb_fg_color(_pixmap_gc, &rgb_dark_gray);
       break;
     }
-    gdk_draw_line(_pixmap, _pixmap_gc, from_x, y, to_x, y);
+    gdk_draw_line(surface, _pixmap_gc, from_x, y, to_x, y);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GtkStatsStripChart::draw_guide_labels
+//       Access: Private
+//  Description: This is called during the servicing of expose_event.
+////////////////////////////////////////////////////////////////////
+void GtkStatsStripChart::
+draw_guide_labels() {
+  // Draw in the labels for the guide bars.
+  int last_y = -100;
+
+  int i;
+  int num_guide_bars = get_num_guide_bars();
+  for (i = 0; i < num_guide_bars; i++) {
+    last_y = draw_guide_label(get_guide_bar(i), last_y);
+  }
+
+  GuideBar top_value = make_guide_bar(get_vertical_scale());
+  draw_guide_label(top_value, last_y);
+
+  last_y = -100;
+  int num_user_guide_bars = get_num_user_guide_bars();
+  for (i = 0; i < num_user_guide_bars; i++) {
+    last_y = draw_guide_label(get_user_guide_bar(i), last_y);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GtkStatsStripChart::draw_guide_label
+//       Access: Private
+//  Description: Draws the text for the indicated guide bar label to
+//               the right of the graph, unless it would overlap with
+//               the indicated last label, whose top pixel value is
+//               given.  Returns the top pixel value of the new label.
+////////////////////////////////////////////////////////////////////
+int GtkStatsStripChart::
+draw_guide_label(const PStatGraph::GuideBar &bar, int last_y) {
+  GdkGC *gc = gdk_gc_new(_scale_area->window);
+
+  switch (bar._style) {
+  case GBS_target:
+    gdk_gc_set_rgb_fg_color(gc, &rgb_light_gray);
+    break;
+    
+  case GBS_user:
+    gdk_gc_set_rgb_fg_color(gc, &rgb_user_guide_bar);
+    break;
+    
+  case GBS_normal:
+    gdk_gc_set_rgb_fg_color(gc, &rgb_dark_gray);
+    break;
+  }
+
+  int y = height_to_pixel(bar._height);
+  const string &label = bar._label;
+
+  PangoLayout *layout = gtk_widget_create_pango_layout(_window, label.c_str());
+  int width, height;
+  pango_layout_get_pixel_size(layout, &width, &height);
+
+  if (bar._style != GBS_user) {
+    float from_height = pixel_to_height(y + height);
+    float to_height = pixel_to_height(y - height);
+    if (find_user_guide_bar(from_height, to_height) >= 0) {
+      // Omit the label: there's a user-defined guide bar in the same space.
+      g_object_unref(layout);
+      g_object_unref(gc);
+      return last_y;
+    }
   }
+
+  // Now convert our y to a coordinate within our drawing area.
+  int junk_x;
+
+  // The y coordinate comes from the graph_window.
+  gtk_widget_translate_coordinates(_graph_window, _scale_area,
+				   0, y,
+				   &junk_x, &y);
+
+  int this_y = y - height / 2;
+  if (y >= 0 && y <= _scale_area->allocation.height &&
+      (last_y < this_y || last_y > this_y + height)) {
+    gdk_draw_layout(_scale_area->window, gc, 0, this_y, layout);
+    last_y = this_y;
+  }
+
+  g_object_unref(layout);
+  g_object_unref(gc);
+  return last_y;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GtkStatsStripChart::expose_event_callback
+//       Access: Private, Static
+//  Description: Draws in the scale labels.
+////////////////////////////////////////////////////////////////////
+gboolean GtkStatsStripChart::
+expose_event_callback(GtkWidget *widget, GdkEventExpose *event, gpointer data) {
+  GtkStatsStripChart *self = (GtkStatsStripChart *)data;
+  self->draw_guide_labels();
+
+  return TRUE;
 }

+ 12 - 3
pandatool/src/gtk-stats/gtkStatsStripChart.h

@@ -60,13 +60,22 @@ protected:
   virtual void draw_cursor(int x);
   virtual void end_draw(int from_x, int to_x);
 
-  virtual DragMode consider_drag_start(int mouse_x, int mouse_y, 
-                                       int width, int height);
+  virtual void additional_graph_window_paint();
+  virtual DragMode consider_drag_start(int graph_x, int graph_y);
   virtual void set_drag_mode(DragMode drag_mode);
 
+  virtual gboolean handle_button_press(GtkWidget *widget, int graph_x, int graph_y);
+  virtual gboolean handle_button_release(GtkWidget *widget, int graph_x, int graph_y);
+  virtual gboolean handle_motion(GtkWidget *widget, int graph_x, int graph_y);
+
 private:
-  void draw_guide_bar(int from_x, int to_x, 
+  void draw_guide_bar(GdkDrawable *surface, int from_x, int to_x, 
 		      const PStatGraph::GuideBar &bar);
+  void draw_guide_labels();
+  int draw_guide_label(const PStatGraph::GuideBar &bar, int last_y);
+  
+  static gboolean expose_event_callback(GtkWidget *widget, 
+					GdkEventExpose *event, gpointer data);
 
 private: