[Home] [Downloads] [Search] [Help/forum]


Register forum user name Search FAQ

Gammon Forum

Notice: Any messages purporting to come from this site telling you that your password has expired, or that you need to "verify" your details, making threats, or asking for money, are spam. We do not email users with any such messages. If you have lost your password you can obtain a new one by using the password reset link.
 Entire forum ➜ MUSHclient ➜ Python ➜ Ooh I got a big book!

Ooh I got a big book!

It is now over 60 days since the last post. This thread is closed.     Refresh page


Posted by Chronoti   USA  (27 posts)  Bio
Date Fri 17 Oct 2003 04:23 AM (UTC)
Message
So I am here learning Python now, just wondering if it would be possible if i had the TCL/TK with Python, could i use it all with mushclient...? Just pondering :)
Top

Posted by Shadowfyr   USA  (1,787 posts)  Bio
Date Reply #1 on Fri 17 Oct 2003 05:09 AM (UTC)

Amended on Fri 17 Oct 2003 05:17 AM (UTC) by Shadowfyr

Message
If there is a library wrapper for it, then just install the Python library that supports TCL/TK. Same with just about anything else. I even managed to nearly get stuff from wxPython to work, but since I had no way to get a reference to Mushclient's window, it blew up the way I tried to do it. If there is a library that can be used, and doesn't need a window to work, it should be usable.

In fact, I just looked into it. The basic Python install includes something called Tkinter, which provides a mean to run TCL/TK, but if you don't already have it then you need to download the TCL/TK files themselves from: http://www.tcl.tk/
Top

Posted by Ked   Russia  (524 posts)  Bio
Date Reply #2 on Fri 17 Oct 2003 06:27 AM (UTC)
Message
As Shadowfyr just said, Tkinter is Python's standard TCL/TK implementation. However, I've never used it for real, so can only say that using Tkinter with Mushclient is no different than using any other windowing library with it. The basic trick is that you can't pop up custom made windows directly from Mushclient. Instead, you need to create a stand-alone window as a separate process, callable from Mushclient via COM. I've done this with wxPython, and although that stuff is still buggy, and I can't make myself dive into Python's debugging issues right now, I could post the full program here, if you are interested.

The general process of opening a separate window from Mushclient using wxPython is rather tricky at start, but no big deal once you figure out what's going on. Basically, you make a program that opens a top level, always-on-top window in response to a COM call, and then accepts further COM calls to control its behavior. The hardest part is to tie the COM server and the application's MainLoop together. Obviously they can't co-exist in the same thread, since MainLoop will hang the server, rendering the application useless. The answer to this is to start the COM server in the main thread and the graph application in a secondary one. A gotcha here is that wxPython refuses to run in any thread other than the main one, but ironically - wxPython will buy any thread for a main one as long as that thread was the first to import it. So you start the server, have it start the graph thread, import wxPython in that thread and enter the message loop. All communication between the server and the window is accomplished through standard queue means of communicating between threads. My current problem is not being able to exit the program without leaving garbage around. Frustratingly, it used to work just fine, but then I decided to throw a couple of Mushclient callbacks into it (just to live up to Nick's Super Health Bar standard) and it went haywire - won't start more than once in the same MC session, so I put it to rest until I cool off enough to look at it again :)
Top

Posted by Shadowfyr   USA  (1,787 posts)  Bio
Date Reply #3 on Fri 17 Oct 2003 08:09 PM (UTC)
Message
Hmm. Do you mean that if you use CreateObject, it still runs in an unusable thread? That makes no sense unless you are creating an instance of the engine and running it there, in which case you haven't solved the problem, which is that events are unusable when the script is inactive. If you mean you are creating an instance that does have a seperate thread, then I don't get what could be wrong. But one thing is certain, you need to instance it in such a way that Python runs it as a completely independent thread.

Of course, Nick has talked about including in the next version a way to get the window handle for the current world, which will help, but it can't solve every problem. It is too bad they didn't make scripts extensible so you could code something like this:

Parallel Sub OnButton1_Click()
End Sub

And have mushclient dump those things identified as parallel into a seperate script process that uses the same plugin or global variables, but being parallel to the main thread, can respond to events like the button click above. Of course, once someone works all the bugs out of using a Python script the way you are trying, then this becomes less relevent, but it would be soooo much easier. lol

As it is, being able to use Python scripts to create a window that is the child of the current one will let you display a picture or dump text into a Richtext control, but not interact in any way with those things or animate anything (unless for the animation you can get by with 1 frame per second from a timer).
Top

Posted by Ked   Russia  (524 posts)  Bio
Date Reply #4 on Sat 18 Oct 2003 12:29 PM (UTC)
Message
Here's what I am talking about exactly. The form won't let me put everything in one post, so it comes in 2 installments: StatusGauge.py (COM server) and status_gauge_gui.py (sec thread - gui stuff, wxPython).
StatusGauge.py is first:


import win32com.client
import thread, Queue
import pythoncom


#This is the actual COM server. 
class StatusGauge:
    _public_methods_ = ['Create', 'Update', 'Destroy']
    _public_attrs_ = ['pos','size','title']
    _readonly_attrs_ = ['title','pos','size']
    _reg_clsid_ = "{26417C84-E596-11D7-B993-444553540000}"
    _reg_desc_ = "Status gauge for Mushclient"
    _reg_progid_ = "Status.Gauge"

    def __init__(self):
        #These are the queues used for communication between
        #the COM object and the GUI thread. Currently only the
        #inQueue is used, and outQueue (used by the GUI to talk
        #back to the COM) is just here for future enhancements
        self.inQueue = Queue.Queue(10) 
        self.outQueue = Queue.Queue(10)
        
        
    def Create(self,
               callingworld,
               maxMana,
               maxHealth,
               pluginID,
               xpos = 10,
               ypos = 400,
               title = "Status gauge"):
        """ This is the method to call when you want to actually display
        the gauge. It will set up all the supplied parameters for visual
        display and start the GUI thread. """
        
        self.size = (270, 60)
        self.pos = [xpos, ypos]
        self.MaxMana = maxMana
        self.MaxHealth = maxHealth
        self.pluginID = pluginID
        self.title = title
        self.thread = None
        self.thread = NewThread(callingworld,
                                pluginID,
                                self.inQueue,
                                self.outQueue,
                                self.title,
                                self.pos,
                                self.size,
                                self.MaxHealth,
                                self.MaxMana)
        self.thread.Start()
        return
        
    def Update(self, barNum, val):
        """ Call this to update the bars on the gauge. Use: <comobject>.Update(<barNum>, <value>)
        where <barNum> denotes which bar to update and <value> sets the new value for the bar.
        <barNum> is currently 0 for mana (lower bar) and a positive integer for health (upper bar) """
        
        self.values = [barNum, val]
        self.inQueue.put(self.values)
        self.thread.Wake()
        
        
class NewThread:
    def __init__(self, callingworld, pluginid, inQueue, outQueue, title, pos, size, maxHealth, maxMana):
        """ This creates the new thread where the GUI frame will run """
        self.pluginid = pluginid
        self.callingworld = callingworld
        self.inQueue = inQueue
        self.outQueue = outQueue
        self.title = title
        self.pos = pos
        self.size = size
        self.WakeUp = None
        self.maxHealth = maxHealth
        self.maxMana = maxMana
      
    def Start(self):
        """ Starts the thread. """
        thread.start_new_thread(self.Run, ())
        
    def Wake(self):
        """ calls the GUI's WakeUp method to make it look into the inQueue for new
        data. This should be called by COM object's Update() method right after putting
        data into the inQueue """
        self._app.WakeUp()
        
    def Run(self):
        """ Imports and runs the GUI app. When the window is closed, MainLoop exits killing
        the thread automagically, however without explicitly terminating the method with
        'return', the thread will keep running even after the app is closed and destroyed. """
        import status_gauge_gui
        self._app = status_gauge_gui.GUI(self.callingworld,
                                         self.pluginid, 
                                         self.title, 
                                         self.pos, 
                                         self.size, 
                                         self.maxHealth,
                                         self.maxMana)
        self._app.inQueue = self.inQueue
        self._app.outQueue = self.outQueue
        self._app.MainLoop()
        return

    def KillGauge(self):
        """ This is called by COM's Destroy() method, doesn't work yet."""
        self._app.KillGauge()


#This registers the program with Windows when run from the prompt or Explorer
#Need to run it at least once before it is usable through COM
if __name__ =='__main__':
        import win32com.server.register
        win32com.server.register.UseCommandLine(StatusGauge)


Beware, opening the window more than once in one MC session results in a crash!
Top

Posted by Ked   Russia  (524 posts)  Bio
Date Reply #5 on Sat 18 Oct 2003 12:33 PM (UTC)
Message
And status_gauge_gui.py won't even fit in a single post - need to rewrite this stuff :( It comes in 2 installments of its own:

Part I:


from wxPython.wx import *
import wx
import Mushclient
import win32com.client

#FIXME: get rid of this, hide everything in class instances
class GenStruct:
    pass

struct = GenStruct()
struct.MaxBarWidth = 200
struct.ManaBar = struct.MaxBarWidth
struct.HealthBar = struct.MaxBarWidth
struct.MaxMana = 2500
struct.MaxHealth = 3500
struct.barHeight = 15

#This block of code defines the Update event, called when we want to repaint a bar
wxEVT_UPDATE_BARGRAPH = wxNewEventType()

def EVT_UPDATE_BARGRAPH(win, func):
    win.Connect(-1, -1, wxEVT_UPDATE_BARGRAPH, func)

class UpdateBarEvent(wxPyEvent):
    def __init__(self, barNum, value):
        wxPyEvent.__init__(self)
        self.SetEventType(wxEVT_UPDATE_BARGRAPH)
        self.barNum = barNum
        self.value = value
#Block ends here
        

class MainWindow(wxMiniFrame):
    """ Creates a miniframe by deriving a new instance of wxMiniFrame """
    def __init__(self, callingworld, pluginid, parent,id,title, pos, size, maxHealth, maxMana):
        wxMiniFrame.__init__(self,parent,-1, title, pos, size = (400,80),
                                     style=wxSYSTEM_MENU|wxSTAY_ON_TOP|wxTINY_CAPTION_VERT, name="MiniFrame")
        panel = wxPanel(self, -1)
        panel.Fit()

        self.pluginID = pluginid
        self.callingworld = callingworld
        struct.Health = maxHealth
        struct.Mana = maxMana
        struct.MaxBarWidth = int(float(size[0])*0.80)
        
        self.graph = GraphWindow(self, ['H:','M:'])
        self.graph.SetSize(size)
        
        sizer = wxBoxSizer(wxVERTICAL)
        sizer.Add(panel, 0, wxEXPAND)
        sizer.Add(self.graph, 1, wxEXPAND)

        self.SetSizer(sizer)
        self.SetAutoLayout(True)
        sizer.Fit(self)
       
        
        EVT_UPDATE_BARGRAPH(self, self.OnUpdate)
        EVT_CLOSE(self, self.OnCloseWindow)

        self.Show(true)         

    def OnCloseWindow(self, event):
        self.NotifyIsDead()
        self.Destroy()
        

    def OnUpdate(self, evt):
        self.graph.SetValue(evt.barNum, evt.value)
        self.graph.Refresh(False)

    def NotifyIsDead(self):
        try:
            world = win32com.client.Dispatch(self.callingworld)
            world.Callplugin(self.pluginID, "WindowClosed", "y")
            world = None
            return
        except:
            pass
Top

Posted by Ked   Russia  (524 posts)  Bio
Date Reply #6 on Sat 18 Oct 2003 12:34 PM (UTC)
Message
Part II of status_gauge_gui.py:


class GraphWindow(wxWindow):
    def __init__(self, parent, labes):
        wxWindow.__init__(self, parent, -1)
        self.labels = []
        self.values = [struct.MaxBarWidth, struct.MaxBarWidth]
        self.healthChange = 0
        self.manaChange = 0
        
        for label in labes:
            self.labels.append(label)

        font = wxFont(8, wxSWISS, wxNORMAL, wxBOLD)
        self.SetFont(font)

        EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)
        EVT_PAINT(self, self.OnPaint)
        
    def SetValue(self, type, value):

        if type:
            if struct.Health != value:
                self.healthChange = value - struct.Health
            struct.Health = value
            if struct.Health > struct.MaxHealth:
                struct.MaxHealth = struct.Health
            self.values[0] = int((float(struct.Health)/(float(struct.MaxHealth)/100.0))*float(struct.MaxBarWidth)/100.0)
        else:
            if struct.Mana != value:
                self.manaChange = value - struct.Mana
            struct.Mana = value
            if struct.Mana > struct.MaxMana:
                struct.MaxMana = struct.Health
            self.values[1] = int((float(struct.Mana)/(float(struct.MaxMana)/100.0))*float(struct.MaxBarWidth)/100.0)
            
        
        
    def OnPaint(self, evt):
        size = self.GetSize()
        bmp = wxEmptyBitmap(size.width, size.height)
        dc = wxMemoryDC()
        dc.SelectObject(bmp)
        self.DrawBars(dc, size)
    
        wdc = wxPaintDC(self)
        wdc.BeginDrawing()
        wdc.Blit(0,0, size.width, size.height, dc, 0,0)
        wdc.EndDrawing()
        dc.SelectObject(wxNullBitmap)
        dc.Clear()

    def DrawBars(self, dc, size):
        dc.SetFont(self.GetFont())
        dc.SetBackground(wxBrush(wxBLACK))
        dc.Clear()
        self.DrawHealthBar(dc, size)
        self.DrawManaBar(dc, size)
        
    def DrawHealthBar(self, dc, size):
        ypos = int(float(size.height)/4.0) - int(float(struct.barHeight)/2.0)
        label = self.labels[0]

        dc.SetTextForeground(wxWHITE)
        dc.DrawText(label, 2, ypos)
        val = self.values[0]
        
        if val:
            if val < struct.MaxBarWidth/3:
                color = wxRED
            elif struct.MaxBarWidth/3 <= val <= (struct.MaxBarWidth/3)*2:
                color = 'Yellow'
            elif val > (struct.MaxBarWidth/3)*2:
                color = wxGREEN

            dc.SetPen(wxPen(color))
            dc.SetBrush(wxBrush(color))
            dc.DrawRectangle(20, ypos, val, struct.barHeight)

        if self.healthChange < 0:
            dc.SetTextForeground(wxRED)
            string = str(self.healthChange)
            dc.DrawText(string, val + 25, ypos)
        elif self.healthChange > 0:
            dc.SetTextForeground(wxGREEN)
            string = "+" + str(self.healthChange)
            dc.DrawText(string, val + 25, ypos)


    def DrawManaBar(self, dc, size):
        ypos = int((float(size.height)/4.0) - int(float(struct.barHeight)/2.0))*4
        label = self.labels[1]

        dc.SetTextForeground(wxWHITE)
        dc.DrawText(label, 2, ypos)
        val = self.values[1]
        
        if val:
            if val < struct.MaxBarWidth/3:
                color = wxRED
            elif struct.MaxBarWidth/3 <= val <= (struct.MaxBarWidth/3)*2:
                color = 'Yellow'
            elif val > (struct.MaxBarWidth/3)*2:
                color = wxGREEN

            dc.SetPen(wxPen(color))
            dc.SetBrush(wxBrush(color))
            dc.DrawRectangle(20, ypos, val, struct.barHeight)

        if self.manaChange < 0:
            dc.SetTextForeground(wxRED)
            string = str(self.manaChange)
            dc.DrawText(string, val + 25, ypos)
        elif self.manaChange > 0:
            dc.SetTextForeground(wxGREEN)
            string = "+" + str(self.manaChange)
            dc.DrawText(string, val + 25, ypos)



    def OnEraseBackground(self, evt):
        pass


class GUI(wxPySimpleApp):
    def __init__(self, callingworld, pluginid, title, pos, size, maxHealth, maxMana):
        wxPySimpleApp.__init__(self)
        self.frame = MainWindow(callingworld, pluginid, NULL, -1, title, pos, size, maxHealth, maxMana)
                
        
    def WakeUp(self):
        if self.inQueue != None:
            try:
                value = self.inQueue.get_nowait()
            except Queue.Empty:
                pass
            self.barNum = value[0]
            self.val = value[1]
            evt = UpdateBarEvent(self.barNum, self.val)
            wxPostEvent(self.frame, evt)

Top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


18,598 views.

It is now over 60 days since the last post. This thread is closed.     Refresh page

Go to topic:           Search the forum


[Go to top] top

Quick links: MUSHclient. MUSHclient help. Forum shortcuts. Posting templates. Lua modules. Lua documentation.

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.

[Home]