Search code examples
pythontwistedgtk3pygobject

Unable to add item to GtkListBox outside of my Gtk.Window


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!


Solution

  • 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