# coding: utf-8 """ Pygame GUI for chatwisted :todo: better get-my-ip method :todo: c'n'p of ip:port -> mouseover: underline -> onClick: copy (no button) :todo: add disconnect option (not only for pygame gui) :todo: create own style :todo: plugin specification: pause/afk mode? :todo: handle focus :todo: save groups in an own list (factory.groups is a dict, unordered) """ import os import sys import pygame import pygame.locals as loc from chatwisted.spg import gui, defaultStyle from twisted.internet import reactor from twisted.web.client import getPage from chatwisted.gui import Gui from chatwisted.client import PluginClient from chatwisted.plugins import PLUGINS from chatwisted import ABOUT class OptQuestionier(gui.Window): """ Helper class to ask the user for some options """ opened = False def __init__(self, parent, title, options, callback): """ :Parameters: parent : widget parent widget title : string title for the dialog window options : list options to ask for as (name, type, default) tuples, eg. ("name", str, "Guest") :todo: bind kp_enter like return """ self.callback_func = callback height = len(options) * 30 + 70 width = 200 screen_x, screen_y = pygame.display.get_surface().get_size() # calculate centered position position = (screen_x - width) / 2, (screen_y - height) / 2 gui.Window.__init__(self, position, (width, height), parent, gui.defaultWindowStyle, title, shadeable=False) self.options = options self.entries = dict() y = 0 for name, dummy, default in self.options: gui.Label((20, y*20+30), (40, 15), self, gui.defaultLabelStyle, name+":") self.entries[name] = gui.TextBox((100, y*20+30), (80, 15), self, gui.defaultTextBoxStyle, text=str(default)) y += 1 gui.Button((50, y*20+30), (45, 15), self, gui.defaultButtonStyle, text="ok").onClick = self.callback gui.Button((100, y*20+30), (45, 15), self, gui.defaultButtonStyle, text="cancel").onClick = self.close if ("IP" in self.entries and "Port" in self.entries): gui.Button((150, y*20+30), (35, 15), self, gui.defaultButtonStyle, text="paste").onClick = self.paste self.lab = gui.Label((10, y*20+60), (160, 15), self, gui.defaultLabelStyle, text="") def close(self, dummy=None): gui.Window.close(self) # update ``opened`` class variable OptQuestionier.opened = False def callback(self, button): out = {} self.callback_func try: for name, convert, dummy in self.options: value = self.entries[name].text if not value: raise ValueError out[name] = convert(value) self.callback_func(out) self.close() except ValueError: self.lab.text = "incorrect values, pleasy try again!" def __new__(typ, *args, **kwargs): #: only show one dialog at once if not typ.opened: typ.opened = True obj = object.__new__(typ, *args, **kwargs) return obj def paste(self, button): #:todo: what type to use? types = ['UTF8_STRING', 'COMPOUND_TEXT', 'TEXT', 'STRING'] for type_ in types: text = pygame.scrap.get(type_) if text: text = text.strip() break print "Paste,", text if (text and ":" in text and "IP" in self.entries and "Port" in self.entries): self.entries["IP"].text, self.entries["Port"].text = text.split(":") class PyGameGui(Gui): name = "pygame" default_size = (850, 600) #: minimum menu alpha value when fading in or out menu_alpha_min = 0 #50 #: maximum menu alpha value when fading in or out menu_alpha_max = 230 #: step size when changing menu alpha value to fade in or out menu_alpha_step = 10 def __init__(self, factory, fps=40): Gui.__init__(self, factory) # init pygame pygame.init() self.screen = pygame.display.set_mode(self.default_size, pygame.DOUBLEBUF) pygame.scrap.init() pygame.display.set_caption('Chatwisted') # init spg self.desktop = gui.Desktop() defaultStyle.init(gui) self.main = main = ChatPluginClient(factory, "main", self.default_size) factory.parser.default_func = main.log self.factory.parser.get_cmds_from_obj(main) self.server_groups = self.plugins = [] # self.plugins = "plugin1, plugin2, plugin3".split(", ") self.indices = {"switch":0, "join":0, "create":0} self.focus = 0 # pygame part self.show_menu = True self.fps = fps self.background = pygame.Surface(self.screen.get_size()) self.menu = self.background.copy().convert()#_alpha() self.background = self.background.convert() img = self.load_image(os.path.join("images", "bg.jpeg")) img_width, img_height = img.get_size() screen_width, screen_height = self.screen.get_size() for xpos in xrange(screen_width/img_width+1): for ypos in xrange(screen_height/img_width+1): self.background.blit(img, (xpos * img_width, ypos * img_height)) self.menu_alpha = 50 # build gui menu_x, menu_y = 50, 50 bstyle = gui.defaultButtonStyle connect= gui.Button(position=(menu_x, menu_y), size=(70,10), parent=self.desktop, text = "connect...", style=bstyle) host = gui.Button(position=(menu_x+80, menu_y), size=(70,10), parent=self.desktop, text = "host...", style=bstyle) self.status = gui.Label((menu_x+150, menu_y), (100, 15), self.desktop, gui.defaultLabelStyle, "not connected") gui.Button((menu_x+250, menu_y), (30, 15), self.desktop, gui.defaultButtonStyle,text="copy").onClick = self.copy_info def callback(opt): print opt err = False # host if len(opt) == 2: self.status.text = "starting server..." if self.factory.start_server(opt["Port"], opt["Your Name"]): self.status.text = "running server" def set_ip_info(ip): self.status.text += "\nat %s:%i" % (ip, opt["Port"]) d = getPage("http://bytemuehle.de/ip/") d.addCallback(set_ip_info) else: err = "Error (1)" elif len(opt) == 3: server_str = "%s:%i" % (opt["IP"], opt["Port"]) self.status.text = "connecting to %s ..." % server_str if self.factory.start_client(opt["IP"], opt["Port"], opt["Your Name"]): self.status.text = "connected to %s" % server_str else: err = "Error (2)" else: err = "Error (3)" if err: self.status.text = err def set_state(): self.status.text = "not connected" reactor.callLater(2, set_state) def show_dialog(widget): title = widget.text options = (("IP", str, self.default_ip), ("Port", int, self.default_port), ("Your Name", str, "anonymous")) if widget == host: options = options[1:] window = OptQuestionier(self.desktop, title, options, callback) connect.onClick = host.onClick = show_dialog selectors_y = 100 selectors_x = 150 self.selectors = {} for cmd in ("switch", "join", "create"): print cmd elements = {} elements["topic"] = b = gui.Button((selectors_x+90, selectors_y), (50, 15), self.desktop, gui.defaultButtonStyle, text=cmd) if cmd == "switch": b.enabled = False else: b.onClick = lambda button: self.menu_action(button.text) elements["bl"] = b = gui.Button((selectors_x, selectors_y+25), (15, 15), self.desktop, gui.defaultButtonStyle, text="<") b.onClick = lambda b, cmd=cmd: self.cycle(-1, cmd) elements["lab"] = gui.Label((selectors_x+20, selectors_y+25), (175, 20), self.desktop, gui.defaultLabelStyle, "") elements["br"] = b = gui.Button((selectors_x+200, selectors_y+25), (15, 15), self.desktop, gui.defaultButtonStyle, text=">") b.onClick = lambda b, cmd=cmd: self.cycle(1, cmd) elements["rect"] = pygame.Rect((selectors_x-5, selectors_y+20), (225, 30)) self.selectors[cmd] = elements self.cycle(0, cmd) selectors_y += 100 # call update and start handling eventloop self.update() def cycle(self, dir, command): group = {"switch": sorted(self.factory.groups.keys()), "join": self.server_groups, "create": self.plugins}[command] if len(group) > 1: self.indices[command] += dir + len(group) self.indices[command] %= len(group) self.selectors[command]["lab"].text = group[self.indices[command]] elif len(group) == 1: self.selectors[command]["lab"].text = group[0] else: self.selectors[command]["lab"].text = "--na-" def menu_action(self, action): if action != "switch": selected = self.selectors[action]["lab"].text if action == "join": self.factory.enter_group(selected) elif action == "create": self.new_group(selected) def move_focus(self, dir): self.focus += dir+3 self.focus %= 3 def get_focus(self): return ["switch", "join", "create"][self.focus] def get_focused(self): return self.selectors[self.get_focus()] def copy_info(self, dummy): #! improve if ":" in self.status.text: pygame.scrap.put(loc.SCRAP_TEXT, self.status.text.split(" ")[-1]) def cmd_error(self, err): print "ERROR: ", err def cmd_plugin_list(self, *plugins): """ update plugin list """ print "got plugin list" #:todo: only show pygame plugins self.plugins = plugins def cmd_group_list(self, *groups): """ update goup list """ self.server_groups = sorted(set(groups)-set(self.factory.groups.keys())) print "got group_list", groups, self.server_groups ############################# def new_group(self, plugin_class): """ create a new plugin group :Parameters: plugin_class : TkPluginClient plugin class """ def callback(opt): name = opt.get("Name", "") if name: self.factory.create_group(plugin_class, name) options = (("Name", str, ""),) self.window = OptQuestionier(self.desktop, "Create new group", options, callback) def add_group(self, plugin_class, group_name): """ create a new plugin obj. and connect to a group :Parameters: group_name : string name of the group to connect to plugin_class : TkPluginClient plugin class """ print "add group", group_name, plugin_class # self.group_surfs[group_name] = plugin_class(self.factory, group_name, self.default_size) self.factory.send_cmd("group_list", "") ############################### def update(self): """ pygame update method handle events, blit, flip """ #Handle Input Events events = gui.setEvents() for event in events: if event.type == loc.QUIT: self.quit() return elif event.type == loc.KEYDOWN: if event.key == loc.K_END: self.quit() return elif event.key == loc.K_ESCAPE: self.show_menu = not self.show_menu # self.menu_alpha = self.menu_alpha_min elif event.key in (loc.K_DOWN, loc.K_TAB): self.move_focus(1) elif event.key == loc.K_UP: self.move_focus(-1) elif event.key == loc.K_RIGHT: self.cycle(1, self.get_focus()) elif event.key == loc.K_LEFT: self.cycle(-1, self.get_focus()) elif event.key in (loc.K_RETURN, loc.K_KP_ENTER): self.menu_action(self.get_focus()) elif event.type == loc.USEREVENT: if event.name == "connection_lost": self.state = "run" self.set_connection_buttons() self.enable_netgui(0) elif event.name == "connected": self.state = "client" self.set_connection_buttons(0, 2) self.enable_netgui() elif event.name == "server_stopped": self.state = "run" self.set_connection_buttons() self.enable_netgui(0) elif event.name == "server_started": self.state = "server" self.set_connection_buttons(2, 0) self.enable_netgui() # if event.key == loc.K_x: # self.start_server(14707) # if event.key == loc.K_y: # self.stop_server() # if event.key == loc.K_n: # self.start_client("JPC", 14707) # if event.key == loc.K_m: # self.stop_client() # if event.key == loc.K_h: # self.send("put 3") # print "update updatesprites" #self.updatesprites.update(events) # self.desktop.update() #Draw Everything for selector in self.selectors: self.cycle(0, selector) plugin_events = events if not self.show_menu else [] current_plugin = self.factory.groups[self.selectors["switch"]["lab"].text] current_plugin.update(plugin_events) self.screen.blit(self.background, (0, 0)) # blit game frame #print self.factory.groups["main"] == self.factory.groups[self.selectors["switch"]["lab"].text] #print self.selectors["switch"]["lab"].text self.screen.blit(self.factory.groups[self.selectors["switch"]["lab"].text], (0, 0)) # (blit menu if menu=on) if self.show_menu: gui.events = events self.desktop.update() #self.menu.fill((150, 150, 150, self.menu_alpha)) self.menu.fill((150, 150, 150)) # draw focus pygame.draw.rect(self.menu, (255,255,255), self.get_focused()["rect"], 2) self.desktop.draw(self.menu) self.menu.set_alpha(self.menu_alpha) if self.menu_alpha < self.menu_alpha_max: self.menu_alpha = min(self.menu_alpha+self.menu_alpha_step, 255) self.screen.blit(self.menu, (0, 0)) elif self.menu_alpha > self.menu_alpha_min: gui.events = events self.desktop.update() #self.menu.fill((150, 150, 150, self.menu_alpha)) self.menu.fill((150, 150, 150)) self.desktop.draw(self.menu) self.menu.set_alpha(self.menu_alpha) self.menu_alpha = max(self.menu_alpha-self.menu_alpha_step, 0) self.screen.blit(self.menu, (0, 0)) else: self.screen.blit(current_plugin, (0, 0)) # self.main.update(events) # self.screen.blit(self.factory.groups[self.indices["switch"]], (0, 0)) # print "draw allsprites" #self.allsprites.draw(self.screen) # print "flip" # self.desktop.draw() pygame.display.flip() # twisted loop # self.clock.tick() # self.ms = self.clock.get_rawtime() # framespeed = (1.0/self.fps) * 1000 # time between 2 frames in ms # lastspeed = self.ms # time since last frame # next = framespeed - lastspeed # time until next frame to fit # the time model # print "framespeed", framespeed, "ms", self.ms, "next", next, "fps",\ # self.clock.get_fps() reactor.callLater((1.0/self.fps) , self.update) def load_image(self, path, colorkey=None, size=None): """ loads an image and returns it as Surface :Parameters: path : String the image path colorkey : int a colorkey for the image. If -1, the value of the first pixel is taken size : (width, height) if given, the image will be resized to this format :rtype: image :todo: use alpha_convert instead of convert? maybe optional? """ try: image = pygame.image.load(path) except pygame.error, message: print 'Cannot load image:', path raise IOError, message image = image.convert() if colorkey is not None: if colorkey == -1: colorkey = image.get_at((0, 0)) image.set_colorkey(colorkey, loc.RLEACCEL) if size: image = pygame.transform.scale(image, size) return image def quit(self): """ Quit the program cleanly """ pygame.quit() self.factory.stop_reactor() class PygamePluginClient(PluginClient, pygame.Surface): """ Parent class for pygame client plugins """ name = "meta" gui = "pygame" #:todo: implement size validation min_size = (200, 100) def __init__(self, factory, name, size): """ """ # call parent class constructors PluginClient.__init__(self, factory, name) pygame.Surface.__init__(self, size, loc.SRCALPHA) self.fill((0,0,0,0)) def update(self, events): pass def pause(self): """ """ def continue_(self): """ """ class ChatPluginClient(PygamePluginClient): """ :todo: change font size for console """ name = "chat" def __init__(self, factory, name, size): PygamePluginClient.__init__(self, factory, name, size) self.desktop = gui.Desktop() width, height = size style = gui.defaultLabelStyle.copy() style["bg-color"] = (150, 150, 150, 150) style["font-color"] = (0, 0, 0) style["autosize"] = False style["offset"] = 20, 20 print style self.console = gui.Label((70, 10), (width-80,height-40), self.desktop, style=style, text="") self.entry = e= gui.TextBox((70, height-20), (width-120, 15), self.desktop, gui.defaultTextBoxStyle) e.onReturn = self.send_message gui.Button((width-40, height-20), (35, 15), self.desktop, gui.defaultButtonStyle, text="send").onClick = self.send_message for i in xrange(20): self.log("test nummer %i" % i) def update(self, events): self.fill((0,0,0,0)) width, height = self.get_size() if len(self.logged_lines) > ((height-40)/15): self.logged_lines = self.logged_lines[-((height-40)/15):] self.console.text = "\n".join(self.logged_lines) # self.gui.events = events self.desktop.update() self.desktop.draw(self) def send_message(self, dummy=None): """ Sends a line to the server """ line = self.entry.text if line: if PluginClient.send_message(self, line): self.entry.text = "" PLUGINS["chat"]["pygame"] = ChatPluginClient