I'm attempting to make a small GTK 3 irc client in Python 2.7 with twisted. Currently, I have a very basic client that can successfully connect to an irc network and display some stuff in the main text area. I'm currently trying to work on implementing multiple channel support, however populating a GtkListBox with an entry containing the channel when a channel is joined doesn't seem to work.
The python code is below:
main.py
from twisted.internet import gtk3reactor
gtk3reactor.install()
from twisted.internet import reactor
from gi.repository import Gtk, GObject
import time
from ConnectDialog import ConnectDialog
# twisted imports
from twisted.words.protocols import irc
from twisted.internet import protocol
class Client(irc.IRCClient):
def __init__(self):
self.channels = []
def _get_nickname(self):
return self.factory.username
def _get_password(self):
return self.factory.password
nickname = property(_get_nickname)
password = property(_get_password)
def connectionMade(self):
irc.IRCClient.connectionMade(self)
self.log("[Connected established at %s]" %
time.asctime(time.localtime(time.time())))
def connectionLost(self, reason):
irc.IRCClient.connectionLost(self, reason)
self.log("[Disconnected at %s]" %
time.asctime(time.localtime(time.time())))
# callbacks for events
def signedOn(self):
"""Called when bot has succesfully signed on to server."""
self.log("Successfuly connected!")
self.join(self.factory.channel)
def joined(self, channel):
self.addChannel(channel)
self.log("[You have joined %s]" % channel)
def privmsg(self, user, channel, msg):
"""This will get called when the bot receives a message."""
if not any(channel in s for s in self.channels):
self.addChannel(channel) # multiple channels for znc
user = user.split('!', 1)[0]
self.log("<%s> %s" % (user, msg))
def action(self, user, channel, msg):
"""This will get called when the bot sees someone do an action."""
user = user.split('!', 1)[0]
self.log("* %s %s" % (user, msg))
# irc callbacks
def irc_NICK(self, prefix, params):
"""Called when an IRC user changes their nickname."""
old_nick = prefix.split('!')[0]
new_nick = params[0]
self.log("%s is now known as %s" % (old_nick, new_nick))
# For fun, override the method that determines how a nickname is changed on
# collisions. The default method appends an underscore.
def alterCollidedNick(self, nickname):
"""
Generate an altered version of a nickname that caused a collision in an
effort to create an unused related name for subsequent registration.
"""
return nickname + '_'
def log(self, message):
end_iter = self.factory.messages_buffer.get_end_iter()
timestamp = time.strftime("[%H:%M:%S]", time.localtime(time.time()))
self.factory.messages_buffer.insert(end_iter, '%s %s\n' % (timestamp, message))
def addChannel(self, channel):
row = Gtk.ListBoxRow()
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=50)
row.add(hbox)
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
hbox.pack_start(vbox, True, True, 0)
label1 = Gtk.Label(channel, xalign=0)
label2 = Gtk.Label("Some more info text here", xalign=0)
vbox.pack_start(label1, True, True, 0)
vbox.pack_start(label2, True, True, 0)
button = Gtk.Button("Close")
button.props.valign = Gtk.Align.CENTER
hbox.pack_start(button, False, True, 0)
GObject.idle_add(self.add_to_chan_list, row)
#self.factory.chan_list.add(row)
self.channels.append(channel)
def add_to_chan_list(self, row):
self.factory.chan_list.add(row)
return False
class IRCFactory(protocol.ClientFactory):
"""A factory for Clients.
A new protocol instance will be created each time we connect to the server.
"""
# the class of the protocol to build when new connection is made
protocol = Client
def __init__(self, username, channel, password, messages_buffer, chan_list, parent):
self.channel = channel
self.username = username
self.password = password
self.chan_list = chan_list
self.messages_buffer = messages_buffer
self.parent = parent
def clientConnectionLost(self, connector, reason):
"""If we get disconnected, reconnect to server."""
connector.connect()
def clientConnectionFailed(self, connector, reason):
print "connection failed:", reason
reactor.stop()
class MainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Gnome IRC")
self.set_border_width(10)
self.set_default_size(800, 600)
hb = Gtk.HeaderBar()
hb.set_show_close_button(True)
hb.props.title = "Gnome IRC"
self.set_titlebar(hb)
button = Gtk.Button("Connect")
button.connect("clicked", self.on_connect_clicked)
hb.pack_start(button)
builder = Gtk.Builder()
builder.add_from_file("main_view.glade")
self.message_entry = builder.get_object("message_entry")
self.messages_view = builder.get_object("messages")
self.ircview = builder.get_object("ircviewpane") # GtkBox
self.chan_list = builder.get_object("channel_list") # GtkListBox
self.add(self.ircview)
self.connect("delete_event", self.on_quit)
def on_connect_clicked(self, widget):
dialog = ConnectDialog(self)
dialog.connect('response', self.dialog_response_cb)
dialog.show()
def dialog_response_cb(self, dialog, response):
if response == Gtk.ResponseType.OK:
server = dialog.address_entry.get_text()
port = int(dialog.port_entry.get_text())
nickname = dialog.nick_entry.get_text()
password = dialog.password.get_text()
channel = "#rymate"
dialog.destroy()
factory = IRCFactory(nickname, channel, password,
self.messages_view.get_buffer(),
self.chan_list, self)
# connect factory to this host and port
reactor.connectTCP(server, port, factory)
# run bot
# reactor.run()
elif response == Gtk.ResponseType.CANCEL:
dialog.destroy()
def on_quit(self, *args):
Gtk.main_quit()
reactor.callFromThread(reactor.stop)
win = MainWindow()
win.show_all()
#Gtk.main()
reactor.run()
ConnectDialog.py
from gi.repository import Gtk
class ConnectDialog(Gtk.Dialog):
def __init__(self, parent):
Gtk.Dialog.__init__(self, "Connect to a Server", parent, 0,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OK, Gtk.ResponseType.OK), use_header_bar=1)
builder = Gtk.Builder()
builder.add_from_file("server.glade")
self.address_entry = builder.get_object("address")
self.port_entry = builder.get_object("port")
self.nick_entry = builder.get_object("username")
self.password = builder.get_object("password")
self.get_content_area().add(builder.get_object("ServerForm"))
server.glade
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 -->
<interface>
<requires lib="gtk+" version="3.12"/>
<object class="GtkGrid" id="ServerForm">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="hexpand">True</property>
<property name="row_spacing">10</property>
<property name="column_spacing">10</property>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Address</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Port</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">IRC Password</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Username</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Real Name</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">4</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="address">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="input_purpose">url</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="port">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="input_purpose">number</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="password">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="visibility">False</property>
<property name="invisible_char">*</property>
<property name="placeholder_text" translatable="yes">Optional</property>
<property name="input_purpose">password</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="username">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="realname">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">4</property>
</packing>
</child>
</object>
</interface>
main_view.glade
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 -->
<interface>
<requires lib="gtk+" version="3.12"/>
<object class="GtkBox" id="ircviewpane">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">4</property>
<property name="margin_right">4</property>
<property name="margin_top">4</property>
<property name="margin_bottom">4</property>
<property name="spacing">5</property>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow2">
<property name="width_request">310</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkViewport" id="viewport1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkListBox" id="channel_list">
<property name="width_request">290</property>
<property name="visible">True</property>
<property name="app_paintable">True</property>
<property name="can_focus">False</property>
<property name="vexpand">True</property>
<property name="border_width">2</property>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="box1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">never</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTextView" id="messages">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="pixels_above_lines">2</property>
<property name="pixels_below_lines">2</property>
<property name="pixels_inside_wrap">2</property>
<property name="editable">False</property>
<property name="wrap_mode">word</property>
<property name="left_margin">2</property>
<property name="right_margin">2</property>
<property name="cursor_visible">False</property>
<property name="accepts_tab">False</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="message_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</interface>
As far as I can tell, the code to add to the GtkListBox will work in the window, but not in the twisted ircclient class.
Any help is appreciated!
Turns out it was simply a case that I needed to call row.show_all()
in order for the listboxitem to be shown in the UI