1. Adding a GUI

Kat has sent me some working text-mode IRC code which needs a modern GUI.

Since the recent disappearance of RDS archives, and the rescue mission, I've noticed that there are a large number of useful programs in the archives which could be GUI-ified to be more easily used.

So, here's a place to discuss the tricks required. Stay tuned.

new topic     » topic index » view message » categorize

2. Re: Adding a GUI

The first consideration is how to interact with the user:

Simple programs which are simply started from the command line with or without some command_line() arguments, and which never interact with the user after that, can simply have a nice window and text display added with very little additional code.

If your program must interact in any way, things change.

First, you may as well begin by removing all Euphoria key handling routines, especially things like get_key() and wait_key(). These will cause all sorts of problems.

Secondly, consider loops. If your code uses while... loops, you will need to take a close look at each, with an eye to redesigning or removing them. Like the key() functions above, they are likely to lock up your GUI user interface, or at least make it appear "unresponsive".

Third, look out for tasks. Tasks are good, but remember that the user interface expects to be allowed some time to look for events and handle them as well, otherwise, it will not just appear to be "locked up" - it will be locked up.

Since the GUI I'll be working with here is GTK3, it already has functions that may substitute for tasks:

1 : an Idle() loop, which can call your Eu function whenever the user isn't actively interacting with your program (not clicking buttons, entering text, or moving/resizing the windows).

2 : a Timeout() loop, which calls your Eu function at set periods every x milliseconds.

new topic     » goto parent     » topic index » view message » categorize

3. Re: Adding a GUI

irv said...

Since the GUI I'll be working with here is GTK3, it already has functions that may substitute for tasks:

1 : an Idle() loop, which can call your Eu function whenever the user isn't actively interacting with your program (not clicking buttons, entering text, or moving/resizing the windows).

2 : a Timeout() loop, which calls your Eu function at set periods every x milliseconds.

You can probably call task_yield() in the idle loop and keep using tasks. Since the tasks jump around in a single thread, that shouldn't cause any issues with calling GUI routines, either.

-Greg

new topic     » goto parent     » topic index » view message » categorize

4. Re: Adding a GUI

So... Kat's text-mode code used the following to wait for user input (just 'q' for quit, here):

--- 
--- main loop here  
--- this is the default task #0  
--- all i have it do is see if i press 'q' to quit 
--- 
while 1 do 
  task_delay(1) 
  if equal(get_key(),'q') then 
    exit -- leave the while-loop, do cleanup and quit 
  end if 
end while 

We'll remove that, and add a Quit button to our window. - But wait, she also allows these other tasks to work while waiting:

junk = task_create(routine_id("listen"), {}) -- haveto listen to the irc server! 
task_schedule(junk,1) -- run it once, it will sleep itself  
 
junk = task_create(routine_id("keepaliveping"), {}) -- optional ping to keep track of lag 
task_schedule(junk,10) -- run it once, it will sleep itself  

Nowhere in these tasks does the user-interface get a chance to do its thing. We could perhaps add another task to check on the user-interface to see if anything needs attention, but that code could get complex real quick. We could, perhaps, add a check inside each of the tasks to poll the user-interface (same complexity).

Better, it seems, is to let GTK do the tasking:

tick = gtk:create(GIdle,call_back(routine_id("IRC_Listen"))) -- "listens" whenever the user isn't doing anything; 
tock = gtk:create(GTimeout,1000,call_back(routine_id("keep_alive_ping"))) -- called once per second (or whatever); 

(Note: I change the names of the routines being called slightly, so that I don't have to remove or modify Kat's code until my version of the routine is working ok.)

new topic     » goto parent     » topic index » view message » categorize

5. Re: Adding a GUI

Next, Kat's program has a hard-coded server name (one of several available, which you could change by editing code). We'll make those into a drop-down list for easy selection:

sequence serverlist = { -- expand as needed; 
  "irc.freenode.net:6667", 
  "hobana.freenode.net:6667", -- does PING-PONG 
  $} 

And attach them to a drop-down Combo control:

object srv = gtk:create(GtkComboBoxText,"tooltip text=Selected Server") 
add(srv,serverlist) -- populate the drop-down list of servers; 
set(srv,"active",1) -- make first server the default shown on startup; 

Kat's code also generates a pseudo-random nick which changes on each run:

sequence my_nick = "katircbotv4-"& sprintf("%d",rand(99)) 

We'll keep this, but show it so that it could be changed if desired:

object nik = gtk:create(GtkEntry,"tooltip text=My Nick,placeholder text=" & my_nick) 

Later, when our connect function runs, we obtain the current nik from that control:

  if length(gtk:get(nik,"text")) > 0 then my_nick = gtk:get(nik,"text") -- use the entered value, if any; 
  else my_nick = gtk:get(nik,"placeholder text") -- if not, use the pre-generated default; 
  end if 
new topic     » goto parent     » topic index » view message » categorize

6. Re: Adding a GUI

Next, we'll need a text display inside our window to show what previously appeared in the terminal:

object  
  scrlwin = gtk:create(GtkScrolledWindow), -- needs to scroll up and down if there's lots of text; 
  viewer = gtk:create(GtkTextView,"editable=FALSE,wrap mode=3"),  
  buffer = gtk:get(viewer,"buffer") 

When we receive text via the IRC_Listen() function, we'll want to show that text here. But also, we may want to log that text to a file, so it's convenient to do both in one step. After receiving and perhaps processing the incoming text, we call:

IRC_Log(text) 
-------------------------------- 
procedure IRC_Log(object msg) -- 
-------------------------------- 
ifdef LOG then puts(logfile,msg) flush(logfile) end ifdef 
set(buffer,"insert at cursor",msg,length(msg)) -- tack new text onto end of existing text; 
set(viewer,"scroll to mark",gtk:get(buffer,"insert")) -- scroll down so latest text is visible in the window; 
set(input,"grab focus") -- set focus back to field for user-typed input (defined later); 
end procedure -- IRC_Log; 
new topic     » goto parent     » topic index » view message » categorize

7. Re: Adding a GUI

We'll need a place for the user to type messages:

object input = gtk:create(GtkEntry,"sensitive=FALSE,placeholder text=Type here and hit <enter> ...") 

Why sensitive=FALSE? We aren't interested in trying to send messages until a connection is achieved. That would result in an error. So we disable it, and in our connect function, after a successful connection is made, we enable it with the following code:

gtk:connect(input,"activate","SendText") -- connect the 'activate' signal (user presses <enter>) to our SendText function; 
gtk:set(input,"sensitive",TRUE) -- enable the input field so we are allowed to type in there; 

Lastly, we'll need a few buttons:
regular buttons have 2 (or 3) parameters: icon/caption, and function to call (optional 3rd is data to pass): Check buttons don't have icons, just the caption.

object 
  btn1 = gtk:create(GtkButton,"gtk-quit","Quit"), 
  btn2 = gtk:create(GtkCheckButton,"Keep Alive Ping"), 
  btn3 = gtk:create(GtkButton,"gtk-connect",_("IRC_Connect")) 

The keep_alive_ping function we set up on a timer previously will continue to run, but we can check the state of btn2 to see if a ping is really desired:

------------------------------ 
function keep_alive_ping() --  
------------------------------ 
if atom(my_socket) then return 1 end if -- no use continuing; 
if gtk:get(btn2,"active") then -- btn2 is 'checked' = 'active'; 
-- ... do the ping; 
new topic     » goto parent     » topic index » view message » categorize

8. Re: Adding a GUI

So, our complete GUI interface consists of:

constant -- INTERFACE -- 
  win = gtk:create(GtkWindow,"size=800x600,border=10,$destroy=Quit"), 
  pan = gtk:create(GtkBox,"spacing=10,orientation=VERTICAL"), 
  srv = gtk:create(GtkComboBoxText,"tooltip text=Selected Server"), 
  nik = gtk:create(GtkEntry,"tooltip text=My Nick,placeholder text=" & my_nick), 
  box = gtk:create(GtkButtonBox), 
  btn1 = gtk:create(GtkButton,"gtk-quit","Quit"), 
  btn2 = gtk:create(GtkCheckButton,"Keep Alive Ping"), 
  btn3 = gtk:create(GtkButton,"gtk-connect",_("IRC_Connect")), 
  scrlwin = gtk:create(GtkScrolledWindow), 
  viewer = gtk:create(GtkTextView,"editable=TRUE,wrap mode=3"), 
  buffer = gtk:get(viewer,"buffer"), 
  input = gtk:create(GtkEntry,"sensitive=FALSE,placeholder text=Type here and hit <enter> ..."), 
  tick =  gtk:create(GIdle,call_back(routine_id("IRC_Listen"))), 
  tock = gtk:create(GTimeout,10000,call_back(routine_id("keep_alive_ping"))) 
 
add(srv,serverlist) -- populate the drop-down list of servers; 
set(srv,"active",1) -- make first server the default shown; 
 
add(win,pan) 
add(pan,{srv,nik}) -- server drop-down and nik entry fields; 
 
pack(pan,scrlwin,TRUE,TRUE) -- the two TRUE's indicate 'expand' and 'fill', 
add(scrlwin,viewer) --  which basically means, "grab all the space you can!"; 
 
add(pan,input) -- field for text entered by the user; 
 
pack_end(pan,box) -- button boxes are always 'packed at end'; 
add(box,{btn1,btn2,btn3}) -- don't forget the buttons; 
   
show_all(win) 
main() 
new topic     » goto parent     » topic index » view message » categorize

9. Re: Adding a GUI

Lastly, there are bound to be places in the code where errors (recoverable or non-recoverable) can occur: Since error messages won't show up in a terminal unless we run our new GUI program from a terminal, we'll need to scatter some popup messages around in appropriate places:

IRC_Log("HOST LOOKUP FAILED " & hostname & "\n") --  may be helpful to log the errors also; 
Error(win,,"Host Lookup Failed",hostname) 
--crash and burn, or whatever is appropriate; 

Finally, you end up with something like this:

Screenshot

That's the beginning, obviously there are other windows and controls needed, but without code to test them with, we'll have to stop here. Kat's the expert on IRC, I would be blundering along wasting time if I tried to implement those other functions.

I'm guessing that there needs to be one or more additional windows, each communicating with the Send and Listen functions, in order to maintain one or more chats, or bots, or whatever. If built as part of this program, that would be easy. If the additions are to run as separate programs, then that gets messy real fast.

new topic     » goto parent     » topic index » view message » categorize

10. Re: Adding a GUI

There's some reasons for doing the code the way i did, which being done five years ago, and not looking at the code since, i don't necessarily remember. I did try to document liberally within irc.e.

I was told irc.e should have the minimum code to connect, and that's it, no more, no code to handle the irc protocol, and certainly no gui built into it. Writing log files has nothing to do with irc. The irc.e code resulted from gutting 95% out of my best irc client.

The pseudo-random nick is to get a better chance of reconnecting to the server if dropped off. Some nets will drop the connection if you have a ghost by the same nick as your reconnect nick, some servers will hang for as long as 4 minutes waiting for you to change to a unique nick. The cheapest way around this without handling the protocols was to connect with a random nick, and then whoever is using irc.e will need to change to the desired nick after killing the ghost. My client had nick changing protocol handled many years before i wrote this irc.e.

I didn't include logging in irc.e because of the many optional ways to log. I agree logging would be in the chosen client. My irc client has logging.

The two endless-loop tasks besides the checking for 'q' are relatively long deadtime routines, 45 seconds for the pinger and 1 sec for listening. In addition, the listener code returns immediately if there's nothing in socks to listen to. The cleanup after 'q' would do the final taskkills and call the client's logfile closures etc. The cleanup procedure wasn't written in irc3-stable.e , it's not in irc6.e either, but irc6.e does have a taskdelay to allow the quit to get sent to the server, because while that's protocol, it was causing issues on irc to not do it during testing, and hence irc6.e isn't stable and i didn't send it to Irv.

I was very much hoping Irv wrote a multi-purpose gui, so it didn't patch directly into irc3-stable.e. A gui isn't required in a bot, and the human-client does need a gui. If the gui is dedicated into irc3-stable, chances are i will break something when trying to take it out of irc3-stable and move it into a client using irc8-stable. I need a gui, but it should be standalone, so someone can use it with any irc#-stable.e or painlessly replace it with their own gui.

EDIT: the servers are hardcoded in irc3-stable.e, and the motd is handled, because i simply had not stripped them out yet. And i needed some way to prove the code works, because i had handed out code and it failed and no one could say why (i suspect that like the hard-coded "ctcp_magicbytes" used for the keep-alive pings, i was the only one who knew how to do certain things).

Kat

new topic     » goto parent     » topic index » view message » categorize

11. Re: Adding a GUI

I just saw the gui pic, looks nice. The keep-alive ping button would go into a popup menu of operating parameters, generally set if needed on each network you visit, and then forgotten. Most networks would have a server-operated PING-PONG handshake, which is protocol, and so it's not handled in irc.e (altho frankly i would prefer it being in irc.e than in the client).

One of my OE irc clients can log onto more than one irc network at a time, and join several channels at once, so it would need to have control of several of those windows, OR the ability to completely re-write the window contents under user control.

About tasks, are you saying having the gui breaks tasks, and/or wait() and/or sleep(), and/or vice versa? What is the work-around, will the gui be required to call vanilla procedures in irc.e? If so, the bot using irc.e will need a separate include to do the background work of repetitive tasks, which is ok, i just need to know this.

Kat

new topic     » goto parent     » topic index » view message » categorize

12. Re: Adding a GUI

I know of a bot on irc, a guy wrote to log the channel. It has no keep-alive ping, and the server he connects to doesn't do PING-PONG protocol, so when the channel is quiet for 245 seconds, the server disconnects the bot. He wrote it in Perl or Python or C or something, on nix. I cannot write his code for him, but have told him why it's happening. A couple people have written Eu bots that also disconnect, same reason.

The conflict i have is that while the issue is incomplete implementation of the protocol in the client, this is such a common basic issue that every connection must handle, it should be embedded in irc.e. But there's the slippery slope of handling everything and spoon feeding it half-digested to the client.

But if the gui must handle timing and the order of all the asynchronous procedure calls, then the gui code must be modified for each change in the rest of the code? I am afraid that if people cannot deal with the simple keep-alive protocols, writing gui code will be as much a deal-breaker for them as it has been for me.

I think i have mentioned this before, but can't there be a common interface between existing code and an attachable gui? For instance, shared memory? I have attached mirc to Eu code using dde and socks. I've attached three Eu console apps using shared memory. Socks is the most common way to attach different apps to the internet.

new topic     » goto parent     » topic index » view message » categorize

13. Re: Adding a GUI

irv said...

Next, Kat's program has a hard-coded server name (one of several available, which you could change by editing code).

It's a worse situation than that. What if the user needs to add/delete servers?

And then there's the protocol issue of connecting. See the line

  addr_ips = addrinfo[3] -- for irc.freenode.net, this could be a dozen ips to choose from 

When you connect to any network at "irc.networkname.ext", they reply with one or more server names. You are expected to parse the list and run thru the list until you connect to one. I removed that intelligence from the client to make irc.e. Ideally, those names should be used to auto-populate the menu of servers which client can connect to, for the case where people prefer to be on the same server (lan parties, for instance), and that's a client-gui thing. So anyhow, i hardcoded a server name to prove the code connects, and the user of irc.e is expected to override with their own list of networks and/or servers.

Strictly speaking, per what i have been told over and over on this forum, the whole procedure at_connected() should be removed, as well as the abbreviated handling of raw numeric 376 (the end of MOTD), because all that is not the connection, it's the protocol of using the connection, and should be in the client. But it's part of the slippery slope of handling the keep-alive and PING-PONG. It's extremely frustrating trying to make the code do less.

new topic     » goto parent     » topic index » view message » categorize

14. Re: Adding a GUI

katsmeow said...

( I didn't choose this idiot markup language )

It's frustrating, I know. I hope to fix that. I've corrected your markup in that post. If you have an suggestions, I'd love to hear them in the survey thread.

-Greg

new topic     » goto parent     » topic index » view message » categorize

15. Re: Adding a GUI

katsmeow said...
irv said...

Next, Kat's program has a hard-coded server name (one of several available, which you could change by editing code).

It's a worse situation than that. What if the user needs to add/delete servers?

And then there's the protocol issue of connecting. See the line " addr_ips = addrinfo[3] for irc.freenode.net, this could be a dozen ips to choose from " ( I didn't chose this idiot markup language )

When you connect to any network at "irc.networkname.ext", they reply with one or more server names. You are expected to parse the list and run thru the list until you connect to one. I removed that intelligence from the client to make irc.e. Ideally, those names should be used to auto-populate the menu of servers which client can connect to, for the case where people prefer to be on the same server (lan parties, for instance), and that's a client-gui thing. So anyhow, i hardcoded a server name to prove the code connects, and the user of irc.e is expected to override with their own list of networks and/or servers.

Strictly speaking, per what i have been told over and over on this forum, the whole procedure at_connected() should be removed, as well as the abbreviated handling of raw numeric 376 (the end of MOTD), because all that is not the connection, it's the protocol of using the connection, and should be in the client. But it's part of the slippery slope of handling the keep-alive and PING-PONG. It's extremely frustrating trying to make the code do less.

I'll be first to admit that everything I know about IRC will fit in this box: []
After downloading an trying a couple of IRC clients, I'm content to leave it that way. Too much like the old party-line phones we had when I was a kid, except back then, we didn't shout out every time we entered or left a room. People could that see for themselves, ifn' they was interested.

As for adding/deleting servers, the code to do that is pretty simple. Just a few more (dozen) lines. Can't code it if I don't know you need it.

Anyway, even if you want to use a stand-alone server, using sockets or shared memory to transfer data back and forth, then the same things need to be taken into account: you can't wait for the data to be available, you'll have to put some "check to see if the socket is ready" code into the GUI client, and call that check via a timer, just as the demo called the listen() function.

new topic     » goto parent     » topic index » view message » categorize

16. Re: Adding a GUI

Well, i do not know what to do next. I do not know what you need me to supply. I cannot make use of the code you posted in this thread.

EDIT: also, GTK3 is 64-bit platform only.

Kat

new topic     » goto parent     » topic index » view message » categorize

17. Re: Adding a GUI

katsmeow said...

Well, i do not know what to do next. I do not know what you need me to supply. I cannot make use of the code you posted in this thread.

EDIT: also, GTK3 is 64-bit platform only.

Kat

Since I know nothing about IRC, all I could do was to implement the code you supplied - your code works fine, BTW.

Beyond that, I'm lost. You will have to spell out exactly what you want to accomplish. You would need to do that even if you were hiring an expert to write the whole thing from scratch.

I could build an interface complete with menus and buttons - given a picture of what you want. Connecting the buttons and menu items to code you write is something you could do (in most cases).

As for the 64-bit problem, if we have a working demo, then someone who is still conversant with Windows programming might duplicate it using Win32lib or wxWindows. And there's still 32-bit GTK around, just older versions, which might work.

new topic     » goto parent     » topic index » view message » categorize

18. Re: Adding a GUI

I guess i could use shared memory or IPC to operate three consoles. One as text entry, one to show each channel text, and one to show the nicks in the channel. With such a powerfull gui operating system like windows, it's amazing those three windows cannot be grouped together in Euphoria and used exactly like the three basic console windows.

I am sorry to have bothered you, Irv.

new topic     » goto parent     » topic index » view message » categorize

19. Re: Adding a GUI

katsmeow said...

I guess i could use shared memory or IPC to operate three consoles. One as text entry, one to show each channel text, and one to show the nicks in the channel. With such a powerfull gui operating system like windows, it's amazing those three windows cannot be grouped together in Euphoria and used exactly like the three basic console windows.

I am sorry to have bothered you, Irv.

A shame indeed. This works perfectly fine with GTK on Linux.

new topic     » goto parent     » topic index » view message » categorize

Search



Quick Links

User menu

Not signed in.

Misc Menu