[gcompris/gcomprixogoo] New feature in the chat activity. The user has a shared white boards now. This has been developped b



commit 1dd87bd421519e53d065ce6bc4f7f7123778745f
Author: Bruno Coudoin <bruno coudoin free fr>
Date:   Fri Oct 15 01:02:45 2010 +0200

    New feature in the chat activity. The user has a shared white boards now.
    This has been developped by Fionn Ziegler.

 configure.ac                                   |    2 +
 src/chat-activity/Makefile.am                  |    2 +-
 src/chat-activity/chat.py                      |  416 +++++++++++++++++++++---
 src/chat-activity/chat.xml.in                  |    4 +-
 src/chat-activity/resources/Makefile.am        |    1 +
 src/chat-activity/resources/chat/Makefile.am   |    6 +
 src/chat-activity/resources/chat/tool-draw.png |  Bin 0 -> 5316 bytes
 src/chat-activity/resources/chat/tool-line.png |  Bin 0 -> 2703 bytes
 8 files changed, 382 insertions(+), 49 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 8404f49..64483db 100644
--- a/configure.ac
+++ b/configure.ac
@@ -623,6 +623,8 @@ src/canal_lock-activity/Makefile
 src/canal_lock-activity/resources/Makefile
 src/canal_lock-activity/resources/canal_lock/Makefile
 src/chat-activity/Makefile
+src/chat-activity/resources/Makefile
+src/chat-activity/resources/chat/Makefile
 src/chess_computer-activity/Makefile
 src/chess_computer-activity/resources/Makefile
 src/chess_computer-activity/resources/chess/Makefile
diff --git a/src/chat-activity/Makefile.am b/src/chat-activity/Makefile.am
index f0d3415..936a36c 100644
--- a/src/chat-activity/Makefile.am
+++ b/src/chat-activity/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS =
+SUBDIRS = resources
 
 pythondir = $(PYTHON_PLUGIN_DIR)
 
diff --git a/src/chat-activity/chat.py b/src/chat-activity/chat.py
index d01ebd3..af10103 100644
--- a/src/chat-activity/chat.py
+++ b/src/chat-activity/chat.py
@@ -1,6 +1,7 @@
 #  gcompris - Chat
 #
 # Copyright (C) 2006, 2008  Bruno Coudoin
+# Copyright (C) 2009, 2010  Bruno Coudoin, Fionn Ziegler
 #
 #   This program is free software; you can redistribute it and/or modify
 #   it under the terms of the GNU General Public License as published by
@@ -15,6 +16,10 @@
 #   You should have received a copy of the GNU General Public License
 #   along with this program; if not, see <http://www.gnu.org/licenses/>.
 #
+# Drawboard functionality by Fionn Ziegler <fionnziegler gmail com>
+#
+#
+
 import goocanvas
 import gcompris
 from gcompris import gcompris_gettext as _
@@ -26,11 +31,13 @@ import gtk
 import gtk.gdk
 import gobject
 import random
-import math
+import cairo
 
 import socket
 import struct
+import pango
 import sys
+import uuid
 
 from socket import gethostname
 
@@ -40,12 +47,15 @@ class Gcompris_chat:
   def __init__(self, gcomprisBoard):
     self.gcomprisBoard = gcomprisBoard
     self.rootitem = None
-
-    # Randon adress and port
+    #contains the drawboard items (points/lines)
+    self.drawitems = []
+    # Adress and port
     self.mcast_adress = "227.234.253.9"
     self.port = 15922
     self.mcast_timer = 0
     self.sock = None
+    # Used to recognize our own network message
+    self.uuid = uuid.uuid1().hex
 
     # These are used to let us restart only after the bonus is displayed.
     # When the bonus is displayed, it call us first with pause(1) and then with pause(0)
@@ -65,7 +75,7 @@ class Gcompris_chat:
     self.global_area_sw.set_shadow_type(gtk.SHADOW_ETCHED_OUT)
 
     w = gcompris.BOARD_WIDTH - 240.0
-    h = gcompris.BOARD_HEIGHT - 100.0
+    h = gcompris.BOARD_HEIGHT - 300.0
     y = 20.0 # The upper limit of the text boxes
     x  = 20.0
 
@@ -104,8 +114,9 @@ class Gcompris_chat:
     self.friend_area_tv = gtk.TextView(self.friend_area_tb)
     self.friend_area_tv.set_editable(False)
     self.friend_area_sw.add(self.friend_area_tv)
+    # save name and selected color in a map
+    self.friend_map = {}
 
-    self.friend_list = []
     self.friend_area_tb.set_text("")
 
     self.friend_area_tv.set_wrap_mode(gtk.WRAP_CHAR)
@@ -186,6 +197,116 @@ class Gcompris_chat:
     self.entry.show()
     self.entry.set_text(_("Type your message here, to send to other GCompris users on your local network."))
 
+    # drawing area
+    x = 20.0
+    w = gcompris.BOARD_WIDTH - 300
+    h = 200.0
+    y = gcompris.BOARD_HEIGHT - 270.0
+    self.colors = {}
+    self.colors['bg'] = 0xFFFFFFFFL
+    self.colors['border'] = 0x000000FFL
+
+    self.drawboard = goocanvas.Rect(
+      parent = self.rootitem,
+      fill_color_rgba = self.colors['bg'],
+      stroke_color_rgba = self.colors['border'],
+      line_width = 1.0,
+      x = x,
+      y = y,
+      width = w,
+      height = h)
+    self.drawboard.connect("button_press_event", self.draw_item_event)
+    self.drawboard.connect("button_release_event", self.draw_item_event)
+    self.drawboard.connect("motion_notify_event", self.draw_item_event)
+    r = random.randint(0,200) # Prevent the possibility of white
+    g = random.randint(0,255)
+    b = random.randint(0,255)
+
+    # colorpicker
+    self.colorpicker = gtk.ColorButton(gtk.gdk.color_parse(self.tohex(r, g, b)))
+
+    x = gcompris.BOARD_WIDTH - 270
+    y = gcompris.BOARD_HEIGHT - 270.0
+    w = 50
+    h = 50
+
+    goocanvas.Widget(
+      parent = self.rootitem,
+      widget = self.colorpicker,
+      x = x,
+      y = y,
+      width = w,
+      height = h,
+      anchor = gtk.ANCHOR_NW)
+
+
+    self.colorpicker.show()
+    self.colorpicker.set_tooltip_text(_("color"))
+    # line and draw radiobuttons
+    self.buttonline = gtk.RadioButton()
+    self.buttondraw = gtk.RadioButton(group=self.buttonline)
+
+    x = gcompris.BOARD_WIDTH - 270
+    y = gcompris.BOARD_HEIGHT - 220.0
+    w = 70
+    h = 50
+
+    goocanvas.Widget(
+      parent = self.rootitem,
+      widget = self.buttonline,
+      x = x,
+      y = y,
+      width = w,
+      height = h,
+      anchor = gtk.ANCHOR_NW)
+
+    goocanvas.Widget(
+      parent = self.rootitem,
+      widget = self.buttondraw,
+      x = x,
+      y = y + 50,
+      width = w,
+      height = h,
+      anchor = gtk.ANCHOR_NW)
+
+    image_line = gtk.Image()
+    image_draw = gtk.Image()
+
+    pixbuf = gcompris.utils.load_pixmap("chat/tool-line.png")
+    image_line.set_from_pixbuf(pixbuf)
+    pixbuf = gcompris.utils.load_pixmap("chat/tool-draw.png")
+    image_draw.set_from_pixbuf(pixbuf)
+
+    self.buttonline.set_image(image_line)
+    self.buttondraw.set_image(image_draw)
+
+    # erase all button
+    self.delAll = gtk.Button()
+    x = gcompris.BOARD_WIDTH - 270
+    y = gcompris.BOARD_HEIGHT - 100.0
+    w = 50
+    h = 30
+
+
+    goocanvas.Widget(
+      parent = self.rootitem,
+      widget = self.delAll,
+      x = x,
+      y = y,
+      width = w,
+      height = h,
+      anchor = gtk.ANCHOR_NW)
+    self.delAll.show()
+    # don't delete this, it's used to change font...
+    self.delAll.set_label("")
+
+    if self.delAll.get_use_stock():
+      label = self.delAll.child.get_children()[1]
+    elif isinstance(self.delAll.child, gtk.Label):
+      label = self.delAll.child
+
+    label.set_markup("<span foreground='red' weight='ultrabold' size='x-large'>X</span>")
+    self.delAll.connect("button_press_event", self.delAllEvent)
     try:
       # Start the server
       self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
@@ -195,7 +316,7 @@ class Gcompris_chat:
 
       self.sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
       self.sock.setblocking(0)
-      self.timer_interval = 500
+      self.timer_interval = 10
       self.mcast_timer = gobject.timeout_add(self.timer_interval, self.mcast_read)
     except:
       self.display_message("GCompris",
@@ -244,7 +365,11 @@ class Gcompris_chat:
 
 
   def cleanup(self):
-
+    #tell the others to remove me from the friend-lists of the other clients:
+    prop = gcompris.get_properties()
+    self.send_message("GCOMPRIS:LEAVE:%s:%s:%s" % (self.channel.get_text(),
+                                                   prop.logged_user.login,
+                                                   self.get_selectedcolor() ))
     if self.mcast_timer :
       gobject.source_remove(self.mcast_timer)
       self.mcast_timer = 0
@@ -268,13 +393,15 @@ class Gcompris_chat:
                                        True,
                                        0,
                                        0)
+    self.set_colors(self.global_area_tb)
 
   def mcast_read(self):
 
       if not self.mcast_timer:
           return
 
-      self.mcast_timer = gobject.timeout_add(self.timer_interval, self.mcast_read)
+      self.mcast_timer = gobject.timeout_add(self.timer_interval,
+                                             self.mcast_read)
 
       text = ""
       try:
@@ -284,61 +411,258 @@ class Gcompris_chat:
           return
 
       # Parse it
-      textl = text.split(":", 4)
+      textl = text.split(":", 10)
 
       # Is this a message for us
       if(textl[0] != "GCOMPRIS"):
           return
 
-      if(textl[1] != "CHAT"):
+      if(textl[1] != "CHAT" and textl[1] != "DRAW" and textl[1] != "LEAVE"):
           return
-
       if(textl[2] != self.channel.get_text()):
           return
-
+      # if color is available if not use black (e.g. chat mode)
+      try:
+          color = textl[4]
+      except:
+          color = "#000000"
+      # map->name:color
+      self.friend_map[textl[3]] = color
       # Build the friend list
-      gotit = False
-      for name in self.friend_list:
-          if name == textl[3]:
-              gotit = True
+      if textl[1] == "LEAVE":
+          self.display_message(textl[3], _("Has left the chat."))
+          del self.friend_map[textl[3]]
 
-      if not gotit:
-          self.friend_list.append(textl[3])
-          self.friend_list.sort()
+      friends=""
+      for name in self.friend_map.keys():
+          friends = "%s%s\n" % (friends, name)
 
-      friends="\n"
-      friends = friends.join(self.friend_list)
       self.friend_area_tb.set_text(friends)
+      self.set_colors(self.friend_area_tb)
 
       gcompris.sound.play_ogg("sounds/receive.wav")
 
-      # Display the message
-      self.display_message(textl[3], textl[4])
+      if (textl[1] == "CHAT"):
+          # Display the message - user: message
+          self.display_message(textl[3], textl[5])
+      if (textl[1] == "DRAW"):
+        # don't paint our own drawing
+        if self.uuid == textl[5]:
+          return False
+
+        # draw received point or line...
+        x = self.convertStr(textl[7])
+        y = self.convertStr(textl[8])
+        # check type
+        if textl[6] == "point":
+          self.draw_point(x, y, color)
+        if textl[6] == "line":
+          x2 = self.convertStr(textl[9])
+          y2 = self.convertStr(textl[10])
+          self.draw_line(x, y, x2, y2, color)
 
       return False
 
+  def set_colors(self, widget):
+      """sets the name in the list with the selected color."""
+      # Get text buffer from the end and mark last entry
+      start_iter = widget.get_end_iter()
+
+      for name, color in self.friend_map.iteritems():
+        # Always just search the latest occurrence of 'name' to avoid that we
+        # Search every time the whole text!
+        # This would be a lot of cpu work
+        result = start_iter.backward_search(name, 0,None)
+        if not result:
+            return
+        match_start_iter, match_end_iter = result
+
+        match_start_iter.starts_word()
+        match_end_iter.ends_word()
+
+        # Create color tag
+        ctag = widget.get_tag_table().lookup(color)
+        if ctag == None:
+           self.color_tag = widget.create_tag(color)
+           self.color_tag.set_property("foreground", color)
+           self.color_tag.set_property("weight", pango.WEIGHT_BOLD)
+        else:
+           self.color_tag = ctag
+
+        widget.apply_tag(self.color_tag ,match_start_iter,
+               match_end_iter)
+
+
+  def convertStr(self,s):
+    """Convert string to either int or float."""
+    try:
+        ret = int(s)
+    except ValueError:
+        #Try float.
+        ret = float(s)
+    return ret
+
+
   def enter_callback(self, widget, entry):
-      gcompris.sound.play_ogg("sounds/bleep.wav")
-
-      if(not self.channel.get_text()):
-        self.display_message(
-          "GCompris",
-          _("You must set a channel in your channel entry box first.\n") +
-          _("Your friends must set the same channel in order to communicate with you") )
-        return
-
-      entry_text = entry.get_text()
-      sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM,
-                           socket.IPPROTO_UDP)
-      sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
-
-      Prop = gcompris.get_properties()
-
-      # format the message
-      entry_text = ( "GCOMPRIS:CHAT:" +
-                     self.channel.get_text() + ":" +
-                      Prop.logged_user.login + ":" + entry_text )
-      sock.sendto(entry_text, (self.mcast_adress, self.port))
-      entry.set_text("")
-      sock.close()
+    gcompris.sound.play_ogg("sounds/bleep.wav")
+
+    if(not self.channel.get_text()):
+      self.display_message(
+        "GCompris",
+        _("You must set a channel in your channel entry box first.\n") +
+        _("Your friends must set the same channel in order to communicate with you") )
+      return
+    Prop = gcompris.get_properties()
+    entry_text = entry.get_text()
+
+    # format the message
+    entry_text = ("GCOMPRIS:CHAT:" +
+                  self.channel.get_text() + ":" +
+                  Prop.logged_user.login + ":" + self.get_selectedcolor() +
+                  ":" + entry_text)
+    self.send_message(entry_text)
+    entry.set_text("")
+
+  def send_drawboard_content(self, x, y, linex=None, liney=None, color=""):
+    """send either line or point depending the given attributes."""
+
+    Prop = gcompris.get_properties()
+    type = ""
+    if linex == None:
+        type = "point"
+        linex = ""
+        liney = ""
+    else:
+        type = "line"
+
+
+    message = ("GCOMPRIS:DRAW:" + self.channel.get_text() + ":" +
+               Prop.logged_user.login + ":" + color + ":" +
+               self.uuid + ":" +
+               type + ":" + repr(x) + ":" + repr(y) +
+               ":" +str(linex) + ":"+ str(liney))
+    self.send_message(message)
+
+
+  def send_message(self, message):
+    """sends the given message."""
+
+    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM,
+                         socket.IPPROTO_UDP)
+    sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
+
+    sock.sendto(message, (self.mcast_adress, self.port))
+    sock.close()
+
+  def delAllEvent(self, widget, target, event=None):
+     """removes the draw board content."""
+     for point in self.drawitems:
+         point.remove()
+
+  def draw_item_event(self, widget, target, event=None):
+    bounds = self.drawboard.get_bounds()
+
+    if event.type == gtk.gdk.BUTTON_PRESS and event.button == 1:
+      self.pos_x = event.x
+      self.pos_y = event.y
+      # -------- LINE
+      if self.buttonline.get_active():
+        self.line = self.draw_line(self.pos_x, self.pos_y,
+                                   event.x, event.y,
+                                   self.get_selectedcolor())
+        return True
+      if self.buttondraw.get_active():
+        self.lastx =self.pos_x
+        self.lasty =self.pos_y
+        self.draw_point(self.pos_x, self.pos_y, self.get_selectedcolor())
+        self.send_drawboard_content(self.pos_x, self.pos_y,
+                                    None, None, self.get_selectedcolor())
+    # do action while mouse button is pressed and in movement
+    elif event.type == gtk.gdk.MOTION_NOTIFY \
+          and event.state & gtk.gdk.BUTTON1_MASK:
+      if event.x < bounds.x2 and event.x > bounds.x1 \
+            and  event.y < bounds.y2 and event.y > bounds.y1:
+        if self.buttonline.get_active(): # line selected
+          # to draw the line while mouse button is pressed
+          self.line.set_properties(points=goocanvas.Points
+                                   ([(self.pos_x, self.pos_y),
+                                     (event.x, event.y)]))
+        elif self.buttondraw.get_active():
+          self.difx = event.x - self.lastx
+          self.dify = event.y - self.lasty
+          # because there is a space between points
+          # if you draw moving the mouse fast,
+          # lets make a line between them:
+          if self.difx > 1 or self.difx < -1 \
+                or self.dify > 1 or self.dify < -1:
+            self.draw_line(self.lastx, self.lasty,
+                           event.x, event.y,
+                           self.get_selectedcolor())
+            self.send_drawboard_content(self.lastx, self.lasty,
+                                        event.x, event.y,
+                                        self.get_selectedcolor())
+
+            self.draw_point(event.x, event.y, self.get_selectedcolor())
+            self.send_drawboard_content(event.x, event.y,
+                                        None, None,
+                                        self.get_selectedcolor())
+            self.lastx = event.x
+            self.lasty = event.y
+
+    elif event.type == gtk.gdk.BUTTON_RELEASE \
+          and self.buttonline.get_active() \
+          and event.button == 1:
+      if event.x < bounds.x2 and event.x > bounds.x1 \
+            and event.y < bounds.y2 and event.y > bounds.y1:
+        # send line when mouse button released
+        self.send_drawboard_content(self.pos_x, self.pos_y,
+                                    event.x, event.y,
+                                    self.get_selectedcolor())
+        return True
+
+    return False
+
+  def draw_point(self, x, y, color):
+    """draws the given point."""
+    point = goocanvas.Rect(parent = self.rootitem,
+                           x=x,
+                           y=y,
+                           width=1,
+                           height=1,
+                           stroke_color = color)
+    self.drawitems.append(point)
+    #to be able to draw by clicking on the new point
+    point.connect("button_press_event", self.draw_item_event)
+    point.connect("button_release_event", self.draw_item_event)
+    point.connect("motion_notify_event", self.draw_item_event)
+    return point
+
+  def draw_line(self, x, y, destx, desty, color):
+    line =goocanvas.Polyline(parent = self.rootitem,
+                             points = goocanvas.Points([(x, y),
+                                                        (destx, desty)]),
+                             stroke_color = color,
+                             line_cap = cairo.LINE_CAP_ROUND,
+                             line_width = 4.0)
+    self.drawitems.append(line)
+    #to be able to draw by clicking on the new line
+    line.connect("button_press_event", self.draw_item_event)
+    line.connect("button_release_event", self.draw_item_event)
+    line.connect("motion_notify_event", self.draw_item_event)
+    return line
+
+
+  def get_selectedcolor(self):
+    """returns the selected color of the GDK ColorButton in hex form."""
+    color = self.colorpicker.get_color();
+    g = int((color.green / 65535.0) *255)
+    r = int((color.red   / 65535.0) *255)
+    b = int((color.blue  / 65535.0) *255)
+    return self.tohex(r, g, b)
+
+  def tohex(self,r,g,b):
+    """rgb to hex string."""
+    hexchars = "0123456789ABCDEF"
+    return "#" + hexchars[r / 16] + hexchars[r % 16] + hexchars[g / 16] \
+        + hexchars[g % 16] + hexchars[b / 16] + hexchars[b % 16]
 
diff --git a/src/chat-activity/chat.xml.in b/src/chat-activity/chat.xml.in
index 8275fb9..ad59383 100644
--- a/src/chat-activity/chat.xml.in
+++ b/src/chat-activity/chat.xml.in
@@ -8,9 +8,9 @@
         difficulty="3"
         author="Bruno coudoin (bruno coudoin free fr)"
         boarddir="">
-        <_title>Chat with your friends</_title>
+        <_title>Chat and draw with your friends</_title>
         <_description>This chat activity only works on the local network</_description>
-        <_manual>This chat activity will only work with other GCompris users on your local network, not on the Internet. To use it, just type in your message and hit Enter. Your message is then broadcast on the local network, and any GCompris program running the chat activity on that local network will receive and display your message.</_manual>
+        <_manual>This chat activity will only work with other GCompris users on your local network, not on the Internet. To use it, just type in your message and hit Enter. Your message is then broadcasted on the local network, and any GCompris program running the chat activity on that local network will receive and display your message.</_manual>
   </Board>
   <Data directory=""/>
 </GCompris>
diff --git a/src/chat-activity/resources/Makefile.am b/src/chat-activity/resources/Makefile.am
new file mode 100644
index 0000000..96c9b09
--- /dev/null
+++ b/src/chat-activity/resources/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = chat
\ No newline at end of file
diff --git a/src/chat-activity/resources/chat/Makefile.am b/src/chat-activity/resources/chat/Makefile.am
new file mode 100644
index 0000000..d64c3d4
--- /dev/null
+++ b/src/chat-activity/resources/chat/Makefile.am
@@ -0,0 +1,6 @@
+imgdir = $(pkgdatadir)/@PACKAGE_DATA_DIR@/chat
+img_DATA = \
+	tool-draw.png \
+	tool-line.png
+
+EXTRA_DIST = $(img_DATA)
diff --git a/src/chat-activity/resources/chat/tool-draw.png b/src/chat-activity/resources/chat/tool-draw.png
new file mode 100644
index 0000000..2b8f716
Binary files /dev/null and b/src/chat-activity/resources/chat/tool-draw.png differ
diff --git a/src/chat-activity/resources/chat/tool-line.png b/src/chat-activity/resources/chat/tool-line.png
new file mode 100644
index 0000000..7c8b86d
Binary files /dev/null and b/src/chat-activity/resources/chat/tool-line.png differ



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]